🐛 Stopped creating redundant Stripe Customers for Members
fixes https://github.com/TryGhost/Ghost/issues/16057 Briefly, Ghost created two Customer objects via the Stripe API when an existing subscriber would upgrade to a paid subscription, one in an API call to create the Customer and then a second as a side effect of an API call to create a Checkout session for the user. The fix is passing the reference to the Customer object to the API call to create the Checkout session; Stripe will no longer redundantly create a Customer object in this case. This largely impacts the owner's experience of the Stripe Dashboard; it will correct their new Customer count (going forward) and make searches for users by name or email address return one responsive object which has the actual subscription in it versus returning two and forcing them to look in each to e.g. refund a transaction or similar.
This commit is contained in:
parent
78384dd9eb
commit
559ca9d866
@ -364,7 +364,9 @@ module.exports = class StripeAPI {
|
||||
*/
|
||||
async createCheckoutSession(priceId, customer, options) {
|
||||
const metadata = options.metadata || undefined;
|
||||
const customerId = customer ? customer.id : undefined;
|
||||
const customerEmail = customer ? customer.email : options.customerEmail;
|
||||
|
||||
await this._rateLimitBucket.throttle();
|
||||
let discounts;
|
||||
if (options.coupon) {
|
||||
@ -386,11 +388,11 @@ module.exports = class StripeAPI {
|
||||
delete subscriptionData.trial_from_plan;
|
||||
subscriptionData.trial_period_days = options.trialDays;
|
||||
}
|
||||
const session = await this._stripe.checkout.sessions.create({
|
||||
|
||||
let stripeSessionOptions = {
|
||||
payment_method_types: ['card'],
|
||||
success_url: options.successUrl || this._config.checkoutSessionSuccessUrl,
|
||||
cancel_url: options.cancelUrl || this._config.checkoutSessionCancelUrl,
|
||||
customer_email: customerEmail,
|
||||
// @ts-ignore - we need to update to latest stripe library to correctly use newer features
|
||||
allow_promotion_codes: discounts ? undefined : this._config.enablePromoCodes,
|
||||
metadata,
|
||||
@ -405,7 +407,17 @@ module.exports = class StripeAPI {
|
||||
// however, this would lose the "trial from plan" feature which has also
|
||||
// been deprecated by Stripe
|
||||
subscription_data: subscriptionData
|
||||
});
|
||||
};
|
||||
|
||||
/* We are only allowed to specify one of these; email will be pulled from
|
||||
customer object on Stripe side if that object already exists. */
|
||||
if (customerId) {
|
||||
stripeSessionOptions.customer = customerId;
|
||||
} else {
|
||||
stripeSessionOptions.customer_email = customerEmail;
|
||||
}
|
||||
|
||||
const session = await this._stripe.checkout.sessions.create(stripeSessionOptions);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
@ -80,4 +80,59 @@ describe('StripeAPI', function () {
|
||||
should.equal(mockStripe.checkout.sessions.create.firstCall.firstArg.subscription_data.trial_from_plan, true);
|
||||
should.not.exist(mockStripe.checkout.sessions.create.firstCall.firstArg.subscription_data.trial_period_days);
|
||||
});
|
||||
|
||||
it('createCheckoutSession passes customer ID successfully to Stripe', async function (){
|
||||
const mockCustomer = {
|
||||
id: 'cust_mock_123456',
|
||||
customer_email: 'foo@example.com',
|
||||
name: 'Example Customer'
|
||||
};
|
||||
|
||||
await api.createCheckoutSession('priceId', mockCustomer, {
|
||||
trialDays: null
|
||||
});
|
||||
|
||||
should.exist(mockStripe.checkout.sessions.create.firstCall.firstArg.customer);
|
||||
should.equal(mockStripe.checkout.sessions.create.firstCall.firstArg.customer, 'cust_mock_123456');
|
||||
});
|
||||
|
||||
it('createCheckoutSession passes email if no customer object provided', async function (){
|
||||
await api.createCheckoutSession('priceId', undefined, {
|
||||
customerEmail: 'foo@example.com',
|
||||
trialDays: null
|
||||
});
|
||||
|
||||
should.exist(mockStripe.checkout.sessions.create.firstCall.firstArg.customer_email);
|
||||
should.equal(mockStripe.checkout.sessions.create.firstCall.firstArg.customer_email, 'foo@example.com');
|
||||
});
|
||||
|
||||
it('createCheckoutSession passes email if customer object provided w/o ID', async function (){
|
||||
const mockCustomer = {
|
||||
email: 'foo@example.com',
|
||||
name: 'Example Customer'
|
||||
};
|
||||
|
||||
await api.createCheckoutSession('priceId', mockCustomer, {
|
||||
trialDays: null
|
||||
});
|
||||
|
||||
should.exist(mockStripe.checkout.sessions.create.firstCall.firstArg.customer_email);
|
||||
should.equal(mockStripe.checkout.sessions.create.firstCall.firstArg.customer_email, 'foo@example.com');
|
||||
});
|
||||
|
||||
it('createCheckoutSession passes only one of customer ID and email', async function (){
|
||||
const mockCustomer = {
|
||||
id: 'cust_mock_123456',
|
||||
email: 'foo@example.com',
|
||||
name: 'Example Customer'
|
||||
};
|
||||
|
||||
await api.createCheckoutSession('priceId', mockCustomer, {
|
||||
trialDays: null
|
||||
});
|
||||
|
||||
should.not.exist(mockStripe.checkout.sessions.create.firstCall.firstArg.customer_email);
|
||||
should.exist(mockStripe.checkout.sessions.create.firstCall.firstArg.customer);
|
||||
should.equal(mockStripe.checkout.sessions.create.firstCall.firstArg.customer, 'cust_mock_123456');
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user