From 740a304bf07ee890eff828d65bd072e740eebbd9 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Mon, 24 May 2021 14:02:43 +0530 Subject: [PATCH] Handled product save failure on Stripe connect refs https://github.com/TryGhost/Team/issues/704 We try and create new default prices soon after Stripe Connect is completed, but it might take couple of seconds for backend to have Stripe config ready and in the meanwhile saving a product with new prices will fail with 409 Conflict error as it will be unable to create prices on Stripe. This change allows re-attempting saving a product with new prices soon after connect in case of a STRIPE_NOT_CONFIGURED error so that the default prices can be created properly. --- .../gh-launch-wizard/connect-stripe.js | 28 +++++++++++++++++-- .../components/gh-members-payments-setting.js | 27 ++++++++++++++++-- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/ghost/admin/app/components/gh-launch-wizard/connect-stripe.js b/ghost/admin/app/components/gh-launch-wizard/connect-stripe.js index 0151786024..984d90d9b8 100644 --- a/ghost/admin/app/components/gh-launch-wizard/connect-stripe.js +++ b/ghost/admin/app/components/gh-launch-wizard/connect-stripe.js @@ -5,6 +5,9 @@ import {task} from 'ember-concurrency-decorators'; import {timeout} from 'ember-concurrency'; import {tracked} from '@glimmer/tracking'; +const RETRY_PRODUCT_SAVE_POLL_LENGTH = 1000; +const RETRY_PRODUCT_SAVE_MAX_POLL = 15 * RETRY_PRODUCT_SAVE_POLL_LENGTH; + export default class GhLaunchWizardConnectStripeComponent extends Component { @service ajax; @service config; @@ -88,6 +91,28 @@ export default class GhLaunchWizardConnectStripeComponent extends Component { this.settings.set('portalPlans', portalPlans); } + @task({drop: true}) + *saveProduct() { + let pollTimeout = 0; + while (pollTimeout < RETRY_PRODUCT_SAVE_MAX_POLL) { + yield timeout(RETRY_PRODUCT_SAVE_POLL_LENGTH); + + try { + const updatedProduct = yield this.product.save(); + return updatedProduct; + } catch (error) { + if (error.payload?.errors && error.payload.errors[0].code === 'STRIPE_NOT_CONFIGURED') { + pollTimeout += RETRY_PRODUCT_SAVE_POLL_LENGTH; + // no-op: will try saving again as stripe is not ready + continue; + } else { + throw error; + } + } + } + return this.product; + } + @task({drop: true}) *openDisconnectStripeConnectModalTask() { this.hasActiveStripeSubscriptions = false; @@ -170,8 +195,7 @@ export default class GhLaunchWizardConnectStripeComponent extends Component { } ); this.product.set('stripePrices', stripePrices); - yield timeout(1000); - const updatedProduct = yield this.product.save(); + const updatedProduct = yield this.saveProduct.perform(); const monthlyPrice = this.getActivePrice(updatedProduct.stripePrices, 'month', 500, 'usd'); const yearlyPrice = this.getActivePrice(updatedProduct.stripePrices, 'year', 5000, 'usd'); this.updatePortalPlans(monthlyPrice.id, yearlyPrice.id); diff --git a/ghost/admin/app/components/gh-members-payments-setting.js b/ghost/admin/app/components/gh-members-payments-setting.js index e7ce1c8820..eb51dfe30a 100644 --- a/ghost/admin/app/components/gh-members-payments-setting.js +++ b/ghost/admin/app/components/gh-members-payments-setting.js @@ -5,6 +5,9 @@ import {reads} from '@ember/object/computed'; import {inject as service} from '@ember/service'; import {task, timeout} from 'ember-concurrency'; +const RETRY_PRODUCT_SAVE_POLL_LENGTH = 1000; +const RETRY_PRODUCT_SAVE_MAX_POLL = 15 * RETRY_PRODUCT_SAVE_POLL_LENGTH; + export default Component.extend({ config: service(), ghostPaths: service(), @@ -258,6 +261,27 @@ export default Component.extend({ this.settings.set('portalPlans', portalPlans); }, + saveProduct: task(function* () { + let pollTimeout = 0; + while (pollTimeout < RETRY_PRODUCT_SAVE_MAX_POLL) { + yield timeout(RETRY_PRODUCT_SAVE_POLL_LENGTH); + + try { + const updatedProduct = yield this.product.save(); + return updatedProduct; + } catch (error) { + if (error.payload?.errors && error.payload.errors[0].code === 'STRIPE_NOT_CONFIGURED') { + pollTimeout += RETRY_PRODUCT_SAVE_POLL_LENGTH; + // no-op: will try saving again as stripe is not ready + continue; + } else { + throw error; + } + } + } + return this.product; + }), + saveStripeSettings: task(function* () { this.set('stripeConnectError', null); this.set('stripeConnectSuccess', null); @@ -292,8 +316,7 @@ export default Component.extend({ } ); this.product.set('stripePrices', stripePrices); - yield timeout(3000); - const updatedProduct = yield this.product.save(); + const updatedProduct = yield this.saveProduct.perform(); const monthlyPrice = this.getActivePrice(updatedProduct.stripePrices, 'month', 500, 'usd'); const yearlyPrice = this.getActivePrice(updatedProduct.stripePrices, 'year', 5000, 'usd'); this.updatePortalPlans(monthlyPrice.id, yearlyPrice.id);