From e7fc7feddd57468236e0bee2a88fc8b24bdbecad Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Fri, 4 Nov 2022 13:30:50 +0100 Subject: [PATCH] Better handling for cancellation when user had multiple subs --- test/api/unit/libs/payments/apple.test.js | 66 +++++++++++------------ website/server/libs/payments/apple.js | 15 ++++-- 2 files changed, 44 insertions(+), 37 deletions(-) diff --git a/test/api/unit/libs/payments/apple.test.js b/test/api/unit/libs/payments/apple.test.js index f8d8900bfe..fce71fc634 100644 --- a/test/api/unit/libs/payments/apple.test.js +++ b/test/api/unit/libs/payments/apple.test.js @@ -412,41 +412,41 @@ describe('Apple Payments', () => { it('uses the most recent subscription data', async () => { iap.getPurchaseData.restore(); - iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData') - .returns([{ - expirationDate: moment.utc().add({ day: 4 }).toDate(), - purchaseDate: moment.utc().subtract({ day: 5 }).toDate(), - productId: 'com.habitrpg.ios.habitica.subscription.3month', - transactionId: token + 'oldest', - originalTransactionId: token + 'evenOlder', - }, { - expirationDate: moment.utc().add({ day: 2 }).toDate(), - purchaseDate: moment.utc().subtract({ day: 1 }).toDate(), - productId: 'com.habitrpg.ios.habitica.subscription.12month', - transactionId: token + 'newest', - originalTransactionId: token + 'newest', - }, { - expirationDate: moment.utc().add({ day: 1 }).toDate(), - purchaseDate: moment.utc().subtract({ day: 2 }).toDate(), - productId: 'com.habitrpg.ios.habitica.subscription.6month', - transactionId: token, - originalTransactionId: token, - }]); - sub = common.content.subscriptionBlocks['basic_12mo']; + iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData') + .returns([{ + expirationDate: moment.utc().add({ day: 4 }).toDate(), + purchaseDate: moment.utc().subtract({ day: 5 }).toDate(), + productId: 'com.habitrpg.ios.habitica.subscription.3month', + transactionId: `${token}oldest`, + originalTransactionId: `${token}evenOlder`, + }, { + expirationDate: moment.utc().add({ day: 2 }).toDate(), + purchaseDate: moment.utc().subtract({ day: 1 }).toDate(), + productId: 'com.habitrpg.ios.habitica.subscription.12month', + transactionId: `${token}newest`, + originalTransactionId: `${token}newest`, + }, { + expirationDate: moment.utc().add({ day: 1 }).toDate(), + purchaseDate: moment.utc().subtract({ day: 2 }).toDate(), + productId: 'com.habitrpg.ios.habitica.subscription.6month', + transactionId: token, + originalTransactionId: token, + }]); + sub = common.content.subscriptionBlocks.basic_12mo; - await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing); + await applePayments.subscribe(user, receipt, headers, nextPaymentProcessing); - expect(paymentsCreateSubscritionStub).to.be.calledOnce; - expect(paymentsCreateSubscritionStub).to.be.calledWith({ - user, - customerId: token + 'newest', - paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE, - sub, - headers, - additionalData: receipt, - nextPaymentProcessing, - }); - }) + expect(paymentsCreateSubscritionStub).to.be.calledOnce; + expect(paymentsCreateSubscritionStub).to.be.calledWith({ + user, + customerId: `${token}newest`, + paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE, + sub, + headers, + additionalData: receipt, + nextPaymentProcessing, + }); + }); describe('does not apply multiple times', async () => { it('errors when a user is using the same subscription', async () => { diff --git a/website/server/libs/payments/apple.js b/website/server/libs/payments/apple.js index 9997652db2..e56affc4cf 100644 --- a/website/server/libs/payments/apple.js +++ b/website/server/libs/payments/apple.js @@ -128,8 +128,8 @@ api.subscribe = async function subscribe (user, receipt, headers, nextPaymentPro if ((!newestDate || datePurchased > newestDate) && dateTerminated > new Date()) { originalTransactionId = purchaseData.originalTransactionId; newTransactionId = purchaseData.transactionId; - newestDate = datePurchased - sku = purchaseData.productId + newestDate = datePurchased; + sku = purchaseData.productId; } } @@ -286,9 +286,16 @@ api.cancelSubscribe = async function cancelSubscribe (user, headers) { const purchases = iap.getPurchaseData(appleRes); if (purchases.length === 0) throw new NotAuthorized(this.constants.RESPONSE_INVALID_RECEIPT); - const subscriptionData = purchases[0]; + let newestDate; + + for (const purchaseData of purchases) { + const datePurchased = new Date(Number(purchaseData.purchaseDate)); + if (!newestDate || datePurchased > newestDate) { + dateTerminated = new Date(Number(purchaseData.expirationDate)); + newestDate = datePurchased; + } + } - dateTerminated = new Date(Number(subscriptionData.expirationDate)); if (dateTerminated > new Date()) throw new NotAuthorized(this.constants.RESPONSE_STILL_VALID); } catch (err) { // If we have an invalid receipt, cancel anyway