diff --git a/test/api/unit/libs/payments/apple.test.js b/test/api/unit/libs/payments/apple.test.js index c3a49b2b08..905b3296c0 100644 --- a/test/api/unit/libs/payments/apple.test.js +++ b/test/api/unit/libs/payments/apple.test.js @@ -415,26 +415,72 @@ describe('Apple Payments', () => { } }); - it('errors when a user is using the same subscription', async () => { - payments.createSubscription.restore(); - iap.getPurchaseData.restore(); - iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData') - .returns([{ - expirationDate: moment.utc().add({ day: 1 }).toDate(), - productId: sku, - transactionId: token, - originalTransactionId: token, - }]); + describe('does not apply multiple times', async () => { + it('errors when a user is using the same subscription', async () => { + payments.createSubscription.restore(); + iap.getPurchaseData.restore(); + iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData') + .returns([{ + expirationDate: moment.utc().add({ day: 1 }).toDate(), + productId: sku, + transactionId: token, + originalTransactionId: token, + }]); + + await applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing); + + await expect(applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing)) + .to.eventually.be.rejected.and.to.eql({ + httpCode: 401, + name: 'NotAuthorized', + message: applePayments.constants.RESPONSE_ALREADY_USED, + }); + }); - await applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing); + it('errors when a user is using a rebill of the same subscription', async () => { + payments.createSubscription.restore(); + iap.getPurchaseData.restore(); + iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData') + .returns([{ + expirationDate: moment.utc().add({ day: 1 }).toDate(), + productId: sku, + transactionId: token + 'renew', + originalTransactionId: token, + }]); - await expect(applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing)) - .to.eventually.be.rejected.and.to.eql({ - httpCode: 401, - name: 'NotAuthorized', - message: applePayments.constants.RESPONSE_ALREADY_USED, - }); + await applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing); + + await expect(applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing)) + .to.eventually.be.rejected.and.to.eql({ + httpCode: 401, + name: 'NotAuthorized', + message: applePayments.constants.RESPONSE_ALREADY_USED, + }); + }); + + it('errors when a different user is using the subscription', async () => { + payments.createSubscription.restore(); + iap.getPurchaseData.restore(); + iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData') + .returns([{ + expirationDate: moment.utc().add({ day: 1 }).toDate(), + productId: sku, + transactionId: token, + originalTransactionId: token, + }]); + + await applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing); + + const secondUser = new User(); + await expect(applePayments.subscribe(sku, secondUser, receipt, headers, nextPaymentProcessing)) + .to.eventually.be.rejected.and.to.eql({ + httpCode: 401, + name: 'NotAuthorized', + message: applePayments.constants.RESPONSE_ALREADY_USED, + }); + }); }); + }); describe('cancelSubscribe ', () => { diff --git a/website/server/libs/payments/apple.js b/website/server/libs/payments/apple.js index 358870afe1..3108651153 100644 --- a/website/server/libs/payments/apple.js +++ b/website/server/libs/payments/apple.js @@ -124,12 +124,16 @@ api.subscribe = async function subscribe (sku, user, receipt, headers, nextPayme throw new NotAuthorized(this.constants.RESPONSE_ALREADY_USED); } existingSub = shared.content.subscriptionBlocks[user.purchased.plan.planId]; + if (existingSub === sub) { + throw new NotAuthorized(this.constants.RESPONSE_ALREADY_USED); + } } const existingUser = await User.findOne({ 'purchased.plan.customerId': originalTransactionId, }).exec(); if (existingUser - && (originalTransactionId === newTransactionId || existingUser._id !== user._id)) { + && (originalTransactionId === newTransactionId + || existingUser._id !== user._id)) { throw new NotAuthorized(this.constants.RESPONSE_ALREADY_USED); }