Merge pull request #2020 from overleaf/ta-recurly-sca

SCA Integration

GitOrigin-RevId: d7935584f87ec7c7339d050430efc87420a39de1
This commit is contained in:
Timothée Alby
2019-08-22 12:10:46 +00:00
committed by sharelatex
parent b90d07fc6f
commit 03460ba229
10 changed files with 327 additions and 108 deletions
@@ -21,6 +21,7 @@ const querystring = require('querystring')
const modulePath = '../../../../app/src/Features/Subscription/RecurlyWrapper'
const SandboxedModule = require('sandboxed-module')
const tk = require('timekeeper')
const SubscriptionErrors = require('../../../../app/src/Features/Subscription/Errors')
const fixtures = {
'subscriptions/44f83d7cba354d5b84812419f923ea96':
@@ -156,7 +157,8 @@ describe('RecurlyWrapper', function() {
log: sinon.stub()
},
request: sinon.stub(),
xml2js: require('xml2js')
xml2js: require('xml2js'),
'./Errors': SubscriptionErrors
}
}
))
@@ -498,12 +500,15 @@ describe('RecurlyWrapper', function() {
}
}
this.subscription = {}
this.recurly_token_id = 'a-token-id'
this.recurlyTokenIds = {
billing: 'a-token-id',
threeDSecureActionResult: 'a-3d-token-id'
}
return (this.call = callback => {
return this.RecurlyWrapper.createSubscription(
this.user,
this.subscriptionDetails,
this.recurly_token_id,
this.recurlyTokenIds,
callback
)
})
@@ -647,7 +652,10 @@ describe('RecurlyWrapper', function() {
}
}
this.subscription = {}
this.recurly_token_id = 'a-token-id'
this.recurlyTokenIds = {
billing: 'a-token-id',
threeDSecureActionResult: 'a-3d-token-id'
}
this.apiRequest = sinon.stub(this.RecurlyWrapper, 'apiRequest')
this.response = { statusCode: 200 }
this.body = '<xml>is_bad</xml>'
@@ -661,7 +669,7 @@ describe('RecurlyWrapper', function() {
return this.RecurlyWrapper._createCreditCardSubscription(
this.user,
this.subscriptionDetails,
this.recurly_token_id,
this.recurlyTokenIds,
callback
)
})
@@ -687,6 +695,7 @@ describe('RecurlyWrapper', function() {
<last_name>Johnson</last_name>
<billing_info>
<token_id>a-token-id</token_id>
<three_d_secure_action_result_token_id>a-3d-token-id</three_d_secure_action_result_token_id>
</billing_info>
</account>
</subscription>\
@@ -724,6 +733,41 @@ describe('RecurlyWrapper', function() {
})
})
describe('when api request returns 422', function() {
beforeEach(function() {
const body = `\
<?xml version="1.0" encoding="UTF-8"?>
<errors>
<transaction_error>
<error_code>three_d_secure_action_required</error_code>
<error_category>3d_secure_action_required</error_category>
<merchant_message>Your payment gateway is requesting that the transaction be completed with 3D Secure in accordance with PSD2.</merchant_message>
<customer_message>Your card must be authenticated with 3D Secure before continuing.</customer_message>
<gateway_error_code nil="nil"></gateway_error_code>
<three_d_secure_action_token_id>mock_three_d_secure_action_token</three_d_secure_action_token_id>
</transaction_error>
<error field="subscription.account.base" symbol="three_d_secure_action_required">Your card must be authenticated with 3D Secure before continuing.</error>
</errors>
`
this.apiRequest.yields(null, { statusCode: 422 }, body)
})
it('should produce an error', function(done) {
return this.call((err, sub) => {
expect(err).to.be.instanceof(
SubscriptionErrors.RecurlyTransactionError
)
expect(err.info.public.message).to.be.equal(
'Your card must be authenticated with 3D Secure before continuing.'
)
expect(err.info.public.threeDSecureActionTokenId).to.be.equal(
'mock_three_d_secure_action_token'
)
return done()
})
})
})
describe('when api request produces an error', function() {
beforeEach(function() {
return this.apiRequest.callsArgWith(1, new Error('woops'))
@@ -802,31 +846,34 @@ describe('RecurlyWrapper', function() {
}
}
this.subscription = {}
this.recurly_token_id = 'a-token-id'
this.recurlyTokenIds = {
billing: 'a-token-id',
threeDSecureActionResult: 'a-3d-token-id'
}
// set up data callbacks
const { user } = this
const { subscriptionDetails } = this
const { recurly_token_id } = this
const { recurlyTokenIds } = this
this.checkAccountExists.callsArgWith(1, null, {
user,
subscriptionDetails,
recurly_token_id,
recurlyTokenIds,
userExists: false,
account: { accountCode: 'xx' }
})
this.createAccount.callsArgWith(1, null, {
user,
subscriptionDetails,
recurly_token_id,
recurlyTokenIds,
userExists: false,
account: { accountCode: 'xx' }
})
this.createBillingInfo.callsArgWith(1, null, {
user,
subscriptionDetails,
recurly_token_id,
recurlyTokenIds,
userExists: false,
account: { accountCode: 'xx' },
billingInfo: { token_id: 'abc' }
@@ -834,7 +881,7 @@ describe('RecurlyWrapper', function() {
this.setAddress.callsArgWith(1, null, {
user,
subscriptionDetails,
recurly_token_id,
recurlyTokenIds,
userExists: false,
account: { accountCode: 'xx' },
billingInfo: { token_id: 'abc' }
@@ -842,7 +889,7 @@ describe('RecurlyWrapper', function() {
this.createSubscription.callsArgWith(1, null, {
user,
subscriptionDetails,
recurly_token_id,
recurlyTokenIds,
userExists: false,
account: { accountCode: 'xx' },
billingInfo: { token_id: 'abc' },
@@ -853,7 +900,7 @@ describe('RecurlyWrapper', function() {
return this.RecurlyWrapper._createPaypalSubscription(
this.user,
this.subscriptionDetails,
this.recurly_token_id,
this.recurlyTokenIds,
callback
)
})
@@ -937,7 +984,10 @@ describe('RecurlyWrapper', function() {
first_name: 'Foo',
last_name: 'Bar'
}),
recurly_token_id: (this.recurly_token_id = 'some_token'),
recurlyTokenIds: (this.recurlyTokenIds = {
billing: 'a-token-id',
threeDSecureActionResult: 'a-3d-token-id'
}),
subscriptionDetails: (this.subscriptionDetails = {
currencyCode: 'EUR',
plan_code: 'some_plan_code',
@@ -1255,7 +1305,7 @@ describe('RecurlyWrapper', function() {
const { body } = this.apiRequest.lastCall.args[0]
expect(body).to.equal(`\
<billing_info>
<token_id>some_token</token_id>
<token_id>a-token-id</token_id>
</billing_info>\
`)
return done()
@@ -19,6 +19,9 @@ const MockRequest = require('../helpers/MockRequest')
const MockResponse = require('../helpers/MockResponse')
const modulePath =
'../../../../app/src/Features/Subscription/SubscriptionController'
const SubscriptionErrors = require('../../../../app/src/Features/Subscription/Errors')
const OError = require('@overleaf/o-error')
const HttpErrors = require('@overleaf/o-error/http')
const mockSubscriptions = {
'subscription-123-active': {
@@ -89,7 +92,9 @@ describe('SubscriptionController', function() {
gaExperiments: {}
}
this.GeoIpLookup = { getCurrencyCode: sinon.stub() }
this.UserGetter = { getUser: sinon.stub().callsArgWith(2, null, this.user) }
this.UserGetter = {
getUser: sinon.stub().callsArgWith(2, null, this.user)
}
this.SubscriptionController = SandboxedModule.require(modulePath, {
globals: {
console: console
@@ -111,7 +116,9 @@ describe('SubscriptionController', function() {
'./RecurlyWrapper': (this.RecurlyWrapper = {}),
'./FeaturesUpdater': (this.FeaturesUpdater = {}),
'./GroupPlansData': (this.GroupPlansData = {}),
'./V1SubscriptionManager': (this.V1SubscriptionManager = {})
'./V1SubscriptionManager': (this.V1SubscriptionManager = {}),
'./Errors': SubscriptionErrors,
'@overleaf/o-error/http': HttpErrors
}
})
@@ -378,7 +385,12 @@ describe('SubscriptionController', function() {
card: '1234',
cvv: '123'
}
this.req.body.recurly_token_id = '1234'
this.recurlyTokenIds = {
billing: '1234',
threeDSecureActionResult: '5678'
}
this.req.body.recurly_token_id = this.recurlyTokenIds.billing
this.req.body.recurly_three_d_secure_action_result_token_id = this.recurlyTokenIds.threeDSecureActionResult
this.req.body.subscriptionDetails = this.subscriptionDetails
this.LimitationsManager.userHasV1OrV2Subscription.yields(null, false)
return this.SubscriptionController.createSubscription(this.req, this.res)
@@ -386,10 +398,10 @@ describe('SubscriptionController', function() {
it('should send the user and subscriptionId to the handler', function(done) {
this.SubscriptionHandler.createSubscription
.calledWith(
.calledWithMatch(
this.user,
this.subscriptionDetails,
this.req.body.recurly_token_id
this.recurlyTokenIds
)
.should.equal(true)
return done()
@@ -401,6 +413,27 @@ describe('SubscriptionController', function() {
})
})
describe('createSubscription with errors', function() {
it('should handle 3DSecure errors', function(done) {
this.next = sinon.stub()
this.LimitationsManager.userHasV1OrV2Subscription.yields(null, false)
this.SubscriptionHandler.createSubscription.yields(
new SubscriptionErrors.RecurlyTransactionError({})
)
this.SubscriptionController.createSubscription(this.req, null, error => {
expect(error).to.exist
expect(error).to.be.instanceof(HttpErrors.UnprocessableEntityError)
expect(
OError.hasCauseInstanceOf(
error,
SubscriptionErrors.RecurlyTransactionError
)
).to.be.true
})
return done()
})
})
describe('updateSubscription via post', function() {
beforeEach(function(done) {
this.res = {
@@ -118,7 +118,7 @@ describe('SubscriptionHandler', function() {
cvv: '123',
number: '12345'
}
this.recurly_token_id = '45555666'
this.recurlyTokenIds = { billing: '45555666' }
return (this.SubscriptionHandler.validateNoSubscriptionInRecurly = sinon
.stub()
.yields(null, true))
@@ -129,18 +129,14 @@ describe('SubscriptionHandler', function() {
return this.SubscriptionHandler.createSubscription(
this.user,
this.subscriptionDetails,
this.recurly_token_id,
this.recurlyTokenIds,
this.callback
)
})
it('should create the subscription with the wrapper', function() {
return this.RecurlyWrapper.createSubscription
.calledWith(
this.user,
this.subscriptionDetails,
this.recurly_token_id
)
.calledWith(this.user, this.subscriptionDetails, this.recurlyTokenIds)
.should.equal(true)
})
@@ -163,7 +159,7 @@ describe('SubscriptionHandler', function() {
return this.SubscriptionHandler.createSubscription(
this.user,
this.subscriptionDetails,
this.recurly_token_id,
this.recurlyTokenIds,
this.callback
)
})