Explorar el Código

feat(payments-plugin): Allow additional options on Stripe payment intent creation (#3194)

Shingo Aoyama hace 1 año
padre
commit
3f66216401

+ 40 - 0
packages/payments-plugin/e2e/stripe-payment.e2e-spec.ts

@@ -246,6 +246,46 @@ describe('Stripe payments', () => {
         StripePlugin.options.paymentIntentCreateParams = undefined;
     });
 
+    // https://github.com/vendure-ecommerce/vendure/issues/3183
+    it('should attach additional options to payment intent using requestOptions', async () => {
+        StripePlugin.options.requestOptions = async (injector, ctx, currentOrder) => {
+            return {
+                stripeAccount: 'acct_connected',
+            };
+        };
+        let connectedAccountHeader: any;
+        let createPaymentIntentPayload: any;
+        const { activeOrder } = await shopClient.query<GetActiveOrderQuery>(GET_ACTIVE_ORDER);
+        nock('https://api.stripe.com/', {
+            reqheaders: {
+                'Stripe-Account': headerValue => {
+                    connectedAccountHeader = headerValue;
+                    return true;
+                },
+            },
+        })
+            .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',
+            'automatic_payment_methods[enabled]': 'true',
+            'metadata[channelToken]': E2E_DEFAULT_CHANNEL_TOKEN,
+            'metadata[orderId]': '1',
+            'metadata[orderCode]': activeOrder?.code,
+        });
+        expect(connectedAccountHeader).toEqual('acct_connected');
+        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) => {

+ 9 - 1
packages/payments-plugin/src/stripe/stripe.service.ts

@@ -44,6 +44,11 @@ export class StripeService {
             ctx,
             order,
         );
+        const additionalOptions = await this.options.requestOptions?.(
+            new Injector(this.moduleRef),
+            ctx,
+            order,
+        );
         const metadata = sanitizeMetadata({
             ...(typeof this.options.metadata === 'function'
                 ? await this.options.metadata(new Injector(this.moduleRef), ctx, order)
@@ -69,7 +74,10 @@ export class StripeService {
                 ...(additionalParams ?? {}),
                 metadata: allMetadata,
             },
-            { idempotencyKey: `${order.code}_${amountInMinorUnits}` },
+            {
+                idempotencyKey: `${order.code}_${amountInMinorUnits}`,
+                ...(additionalOptions ?? {}),
+            },
         );
 
         if (!client_secret) {

+ 37 - 0
packages/payments-plugin/src/stripe/types.ts

@@ -15,6 +15,8 @@ type AdditionalPaymentIntentCreateParams = Partial<
     Omit<Stripe.PaymentIntentCreateParams, 'amount' | 'currency' | 'customer'>
 >;
 
+type AdditionalRequestOptions = Partial<Omit<Stripe.RequestOptions, 'idempotencyKey'>>;
+
 type AdditionalCustomerCreateParams = Partial<Omit<Stripe.CustomerCreateParams, 'email'>>;
 
 /**
@@ -107,6 +109,41 @@ export interface StripePluginOptions {
         order: Order,
     ) => AdditionalPaymentIntentCreateParams | Promise<AdditionalPaymentIntentCreateParams>;
 
+    /**
+     * @description
+     * Provide additional options to the Stripe payment intent creation. By default,
+     * the plugin will already pass the `idempotencyKey` parameter.
+     *
+     * For example, if you want to provide a `stripeAccount` for the payment intent, you can do so like this:
+     *
+     * @example
+     * ```ts
+     * import { VendureConfig } from '\@vendure/core';
+     * import { StripePlugin } from '\@vendure/payments-plugin/package/stripe';
+     *
+     * export const config: VendureConfig = {
+     *   // ...
+     *   plugins: [
+     *     StripePlugin.init({
+     *       requestOptions: (injector, ctx, order) => {
+     *         return {
+     *           stripeAccount: ctx.channel.seller?.customFields.connectedAccountId
+     *         },
+     *       }
+     *     }),
+     *   ],
+     * };
+     * ```
+     *
+     * @since 3.1.0
+     *
+     */
+    requestOptions?: (
+        injector: Injector,
+        ctx: RequestContext,
+        order: Order,
+    ) => AdditionalRequestOptions | Promise<AdditionalRequestOptions>;
+
     /**
      * @description
      * Provide additional parameters to the Stripe customer creation. By default,