Skip to content

Commit

Permalink
feat(payments-plugin): Allow custom params to be passed to Stripe API
Browse files Browse the repository at this point in the history
Closes #2412
  • Loading branch information
michaelbromley committed Sep 26, 2023
1 parent 6c1d7bb commit 1b29097
Show file tree
Hide file tree
Showing 6 changed files with 253 additions and 9 deletions.
44 changes: 37 additions & 7 deletions packages/payments-plugin/e2e/graphql/generated-shop-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3280,7 +3280,7 @@ export type TestOrderFragmentFragment = {
}>;
}>;
shippingLines: Array<{ shippingMethod: { id: string; code: string; description: string } }>;
customer?: { id: string; user?: { id: string; identifier: string } | null } | null;
customer?: { id: string; emailAddress: string; user?: { id: string; identifier: string } | null } | null;
history: { items: Array<{ id: string; type: HistoryEntryType; data: any }> };
};

Expand Down Expand Up @@ -3340,7 +3340,11 @@ export type AddPaymentToOrderMutation = {
}>;
}>;
shippingLines: Array<{ shippingMethod: { id: string; code: string; description: string } }>;
customer?: { id: string; user?: { id: string; identifier: string } | null } | null;
customer?: {
id: string;
emailAddress: string;
user?: { id: string; identifier: string } | null;
} | null;
history: { items: Array<{ id: string; type: HistoryEntryType; data: any }> };
}
| { errorCode: ErrorCode; message: string }
Expand Down Expand Up @@ -3456,7 +3460,11 @@ export type SetShippingMethodMutation = {
}>;
}>;
shippingLines: Array<{ shippingMethod: { id: string; code: string; description: string } }>;
customer?: { id: string; user?: { id: string; identifier: string } | null } | null;
customer?: {
id: string;
emailAddress: string;
user?: { id: string; identifier: string } | null;
} | null;
history: { items: Array<{ id: string; type: HistoryEntryType; data: any }> };
}
| { errorCode: ErrorCode; message: string };
Expand Down Expand Up @@ -3521,7 +3529,11 @@ export type AddItemToOrderMutation = {
}>;
}>;
shippingLines: Array<{ shippingMethod: { id: string; code: string; description: string } }>;
customer?: { id: string; user?: { id: string; identifier: string } | null } | null;
customer?: {
id: string;
emailAddress: string;
user?: { id: string; identifier: string } | null;
} | null;
history: { items: Array<{ id: string; type: HistoryEntryType; data: any }> };
};
}
Expand Down Expand Up @@ -3574,7 +3586,11 @@ export type AddItemToOrderMutation = {
}>;
}>;
shippingLines: Array<{ shippingMethod: { id: string; code: string; description: string } }>;
customer?: { id: string; user?: { id: string; identifier: string } | null } | null;
customer?: {
id: string;
emailAddress: string;
user?: { id: string; identifier: string } | null;
} | null;
history: { items: Array<{ id: string; type: HistoryEntryType; data: any }> };
}
| { errorCode: ErrorCode; message: string }
Expand Down Expand Up @@ -3634,7 +3650,11 @@ export type GetOrderByCodeQuery = {
}>;
}>;
shippingLines: Array<{ shippingMethod: { id: string; code: string; description: string } }>;
customer?: { id: string; user?: { id: string; identifier: string } | null } | null;
customer?: {
id: string;
emailAddress: string;
user?: { id: string; identifier: string } | null;
} | null;
history: { items: Array<{ id: string; type: HistoryEntryType; data: any }> };
} | null;
};
Expand Down Expand Up @@ -3690,7 +3710,11 @@ export type GetActiveOrderQuery = {
}>;
}>;
shippingLines: Array<{ shippingMethod: { id: string; code: string; description: string } }>;
customer?: { id: string; user?: { id: string; identifier: string } | null } | null;
customer?: {
id: string;
emailAddress: string;
user?: { id: string; identifier: string } | null;
} | null;
history: { items: Array<{ id: string; type: HistoryEntryType; data: any }> };
} | null;
};
Expand Down Expand Up @@ -3820,6 +3844,7 @@ export const TestOrderFragmentFragmentDoc = {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
{ kind: 'Field', name: { kind: 'Name', value: 'emailAddress' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'user' },
Expand Down Expand Up @@ -4103,6 +4128,7 @@ export const AddPaymentToOrderDocument = {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
{ kind: 'Field', name: { kind: 'Name', value: 'emailAddress' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'user' },
Expand Down Expand Up @@ -4541,6 +4567,7 @@ export const SetShippingMethodDocument = {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
{ kind: 'Field', name: { kind: 'Name', value: 'emailAddress' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'user' },
Expand Down Expand Up @@ -4808,6 +4835,7 @@ export const AddItemToOrderDocument = {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
{ kind: 'Field', name: { kind: 'Name', value: 'emailAddress' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'user' },
Expand Down Expand Up @@ -5013,6 +5041,7 @@ export const GetOrderByCodeDocument = {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
{ kind: 'Field', name: { kind: 'Name', value: 'emailAddress' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'user' },
Expand Down Expand Up @@ -5201,6 +5230,7 @@ export const GetActiveOrderDocument = {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
{ kind: 'Field', name: { kind: 'Name', value: 'emailAddress' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'user' },
Expand Down
1 change: 1 addition & 0 deletions packages/payments-plugin/e2e/graphql/shop-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export const TEST_ORDER_FRAGMENT = gql`
}
customer {
id
emailAddress
user {
id
identifier
Expand Down
83 changes: 83 additions & 0 deletions packages/payments-plugin/e2e/stripe-payment.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,89 @@ describe('Stripe payments', () => {
StripePlugin.options.metadata = undefined;
});

// https://github.com/vendure-ecommerce/vendure/issues/2412
it('should attach additional params to payment intent using paymentIntentCreateParams', async () => {
StripePlugin.options.paymentIntentCreateParams = async (injector, ctx, currentOrder) => {
const hydrator = injector.get(EntityHydrator);
await hydrator.hydrate(ctx, currentOrder, { relations: ['customer'] });
return {
description: `Order #${currentOrder.code} for ${currentOrder.customer!.emailAddress}`,
};
};
let createPaymentIntentPayload: any;
const { activeOrder } = await shopClient.query<GetActiveOrderQuery>(GET_ACTIVE_ORDER);
nock('https://api.stripe.com/')
.post('/v1/payment_intents', body => {
createPaymentIntentPayload = body;
return true;
})
.reply(200, {
client_secret: 'test-client-secret',
});
const { createStripePaymentIntent } = await shopClient.query(CREATE_STRIPE_PAYMENT_INTENT);
expect(createPaymentIntentPayload).toEqual({
amount: activeOrder?.totalWithTax.toString(),
currency: activeOrder?.currencyCode?.toLowerCase(),
customer: 'new-customer-id',
description: `Order #${activeOrder!.code} for ${activeOrder!.customer!.emailAddress}`,
'automatic_payment_methods[enabled]': 'true',
'metadata[channelToken]': E2E_DEFAULT_CHANNEL_TOKEN,
'metadata[orderId]': '1',
'metadata[orderCode]': activeOrder?.code,
});
expect(createStripePaymentIntent).toEqual('test-client-secret');
StripePlugin.options.paymentIntentCreateParams = undefined;
});

// https://github.com/vendure-ecommerce/vendure/issues/2412
it('should attach additional params to customer using customerCreateParams', async () => {
StripePlugin.options.customerCreateParams = async (injector, ctx, currentOrder) => {
const hydrator = injector.get(EntityHydrator);
await hydrator.hydrate(ctx, currentOrder, { relations: ['customer'] });
return {
description: `Description for ${currentOrder.customer!.emailAddress}`,
phone: '12345',
};
};

await shopClient.asUserWithCredentials(customers[1].emailAddress, 'test');
const { addItemToOrder } = await shopClient.query<
AddItemToOrderMutation,
AddItemToOrderMutationVariables
>(ADD_ITEM_TO_ORDER, {
productVariantId: 'T_1',
quantity: 2,
});
order = addItemToOrder as TestOrderFragmentFragment;

let createCustomerPayload: { name: string; email: string } | undefined;
const emptyList = { data: [] };
nock('https://api.stripe.com/')
.get(/\/v1\/customers.*/)
.reply(200, emptyList);
nock('https://api.stripe.com/')
.post('/v1/customers', body => {
createCustomerPayload = body;
return true;
})
.reply(201, {
id: 'new-customer-id',
});
nock('https://api.stripe.com/').post('/v1/payment_intents').reply(200, {
client_secret: 'test-client-secret',
});

const { activeOrder } = await shopClient.query<GetActiveOrderQuery>(GET_ACTIVE_ORDER);

await shopClient.query(CREATE_STRIPE_PAYMENT_INTENT);
expect(createCustomerPayload).toEqual({
email: 'trevor_donnelly96@hotmail.com',
name: 'Trevor Donnelly',
description: `Description for ${activeOrder!.customer!.emailAddress}`,
phone: '12345',
});
});

// https://github.com/vendure-ecommerce/vendure/issues/1630
describe('currencies with no fractional units', () => {
let japanProductId: string;
Expand Down
1 change: 1 addition & 0 deletions packages/payments-plugin/src/stripe/stripe.plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { StripePluginOptions } from './types';
* }),
* ]
* ````
* For all the plugin options, see the {@link StripePluginOptions} type.
* 2. Create a new PaymentMethod in the Admin UI, and select "Stripe payments" as the handler.
* 3. Set the webhook secret and API key in the PaymentMethod form.
*
Expand Down
22 changes: 21 additions & 1 deletion packages/payments-plugin/src/stripe/stripe.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ export class StripeService {
}
const amountInMinorUnits = getAmountInStripeMinorUnits(order);

const additionalParams = await this.options.paymentIntentCreateParams?.(
new Injector(this.moduleRef),
ctx,
order,
);
const metadata = sanitizeMetadata({
...(typeof this.options.metadata === 'function'
? await this.options.metadata(new Injector(this.moduleRef), ctx, order)
Expand All @@ -49,6 +54,11 @@ export class StripeService {
orderCode: order.code,
});

const allMetadata = {
...metadata,
...sanitizeMetadata(additionalParams?.metadata ?? {}),
};

const { client_secret } = await stripe.paymentIntents.create(
{
amount: amountInMinorUnits,
Expand All @@ -57,7 +67,8 @@ export class StripeService {
automatic_payment_methods: {
enabled: true,
},
metadata,
...(additionalParams ?? {}),
metadata: allMetadata,
},
{ idempotencyKey: `${order.code}_${amountInMinorUnits}` },
);
Expand Down Expand Up @@ -164,9 +175,18 @@ export class StripeService {
if (stripeCustomers.data.length > 0) {
stripeCustomerId = stripeCustomers.data[0].id;
} else {
const additionalParams = await this.options.customerCreateParams?.(
new Injector(this.moduleRef),
ctx,
order,
);
const newStripeCustomer = await stripe.customers.create({
email: customer.emailAddress,
name: `${customer.firstName} ${customer.lastName}`,
...(additionalParams ?? {}),
...(additionalParams?.metadata
? { metadata: sanitizeMetadata(additionalParams.metadata) }
: {}),
});

stripeCustomerId = newStripeCustomer.id;
Expand Down
Loading

0 comments on commit 1b29097

Please sign in to comment.