diff --git a/website/server/controllers/top-level/payments/iap.js b/website/server/controllers/top-level/payments/iap.js index 9cf0f99967..85c93584f6 100644 --- a/website/server/controllers/top-level/payments/iap.js +++ b/website/server/controllers/top-level/payments/iap.js @@ -52,6 +52,8 @@ api.iapSubscriptionAndroid = { req.body.transaction.receipt, req.body.transaction.signature, req.headers, + req.body.nextPaymentProcessing, + req.body.deferredSku, ); res.respond(200); diff --git a/website/server/libs/payments/google.js b/website/server/libs/payments/google.js index adc4f76993..0e40dadb8b 100644 --- a/website/server/libs/payments/google.js +++ b/website/server/libs/payments/google.js @@ -119,15 +119,7 @@ api.getSubscriptionPaymentDetails = async function getDetails (userId, subscript }; }; -api.subscribe = async function subscribe ( - sku, - user, - receipt, - signature, - headers, - nextPaymentProcessing = undefined, -) { - if (!sku) throw new BadRequest(shared.i18n.t('missingSubscriptionCode')); +function getSubCodeFromSku (sku) { let subCode; switch (sku) { // eslint-disable-line default-case case 'com.habitrpg.android.habitica.subscription.1month': @@ -143,6 +135,20 @@ api.subscribe = async function subscribe ( subCode = 'basic_12mo'; break; } + return subCode; +} + +api.subscribe = async function subscribe ( + sku, + user, + receipt, + signature, + headers, + nextPaymentProcessing = undefined, + deferredSku = undefined, +) { + if (!sku) throw new BadRequest(shared.i18n.t('missingSubscriptionCode')); + const subCode = getSubCodeFromSku(sku); const sub = subCode ? shared.content.subscriptionBlocks[subCode] : false; if (!sub) throw new NotAuthorized(this.constants.RESPONSE_INVALID_ITEM); @@ -186,8 +192,13 @@ api.subscribe = async function subscribe ( additionalData: testObj, }; if (existingSub) { - data.updatedFrom = existingSub; - data.updatedFrom.logic = 'payFull'; + if (deferredSku) { + const deferredSubCode = getSubCodeFromSku(deferredSku); + data.updatedTo = shared.content.subscriptionBlocks[deferredSubCode]; + } else { + data.updatedFrom = existingSub; + data.updatedFrom.logic = 'payFull'; + } } await payments.createSubscription(data); }; diff --git a/website/server/libs/payments/subscriptions.js b/website/server/libs/payments/subscriptions.js index a7a4ceb488..170f941cd3 100644 --- a/website/server/libs/payments/subscriptions.js +++ b/website/server/libs/payments/subscriptions.js @@ -214,6 +214,10 @@ async function prepareSubscriptionValues (data) { if (data.subscriptionId) { plan.subscriptionId = data.subscriptionId; } + if (data.updatingTo && data.updatingTo.key) { + // Subscription change is deferred until next payment processing + plan.deferredPlanId = data.updatingTo.key; + } } return { diff --git a/website/server/models/subscriptionPlan.js b/website/server/models/subscriptionPlan.js index 4943705e89..d316ed4ba9 100644 --- a/website/server/models/subscriptionPlan.js +++ b/website/server/models/subscriptionPlan.js @@ -35,6 +35,7 @@ export const schema = new mongoose.Schema({ trinkets: { $type: Number, default: 0 }, lastHourglassReceived: Date, }, + deferredPlanId: String, }, { strict: true, minimize: false, // So empty objects are returned