Merge pull request #2020 from overleaf/ta-recurly-sca
SCA Integration GitOrigin-RevId: d7935584f87ec7c7339d050430efc87420a39de1
This commit is contained in:
committed by
sharelatex
parent
b90d07fc6f
commit
03460ba229
@@ -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
|
||||
)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user