From 96b2b5136f94513f812b4576b770208987e43de4 Mon Sep 17 00:00:00 2001 From: jbranchaud Date: Thu, 21 Mar 2024 16:03:39 -0500 Subject: [PATCH] feat: extract stripe provider --- .../src/providers/default-payment-options.ts | 65 +------------------ .../src/providers/stripe-provider.ts | 64 ++++++++++++++++++ 2 files changed, 67 insertions(+), 62 deletions(-) create mode 100644 packages/commerce-server/src/providers/stripe-provider.ts diff --git a/packages/commerce-server/src/providers/default-payment-options.ts b/packages/commerce-server/src/providers/default-payment-options.ts index b1b87de62..d597cfe33 100644 --- a/packages/commerce-server/src/providers/default-payment-options.ts +++ b/packages/commerce-server/src/providers/default-payment-options.ts @@ -1,5 +1,3 @@ -import {getStripeSdk} from '@skillrecordings/stripe-sdk' -import {first} from 'lodash' import Stripe from 'stripe' import {z} from 'zod' @@ -8,14 +6,14 @@ type StripeConfig = { apiVersion: '2020-08-27' } -const PurchaseMetadata = z.object({ +export const PurchaseMetadata = z.object({ country: z.string().optional(), appliedPPPStripeCouponId: z.string().optional(), // TODO: make this provider agnostic upgradedFromPurchaseId: z.string().optional(), usedCouponId: z.string().optional(), }) -const PurchaseInfoSchema = z.object({ +export const PurchaseInfoSchema = z.object({ customerIdentifier: z.string(), email: z.string().nullable(), name: z.string().nullable(), @@ -44,7 +42,7 @@ type StripeProvider = { name: 'stripe' paymentClient: Stripe } & PaymentProviderFunctionality -type StripeProviderFunction = ( +export type StripeProviderFunction = ( options: StripeConfig | {defaultStripeClient: Stripe}, ) => StripeProvider @@ -63,63 +61,6 @@ export type PaymentOptions = { } } -// TODO: this should have a shared PaymentProvider type that all providers conform to -// TODO: this can eventually move to it's own Stripe module in `providers/` -export const StripeProvider: StripeProviderFunction = (config) => { - const stripeClient = - 'defaultStripeClient' in config - ? config.defaultStripeClient - : new Stripe(config.stripeSecretKey, { - apiVersion: config.apiVersion, - }) - - const getStripePurchaseInfo = async (checkoutSessionId: string) => { - const {getCheckoutSession} = getStripeSdk({ctx: {stripe: stripeClient}}) - - const checkoutSession = await getCheckoutSession(checkoutSessionId) - - const {customer, line_items, payment_intent, metadata} = checkoutSession - const {email, name, id: stripeCustomerId} = customer as Stripe.Customer - const lineItem = first(line_items?.data) as Stripe.LineItem - const stripePrice = lineItem.price - const quantity = lineItem.quantity || 1 - const stripeProduct = stripePrice?.product as Stripe.Product - const {charges} = payment_intent as Stripe.PaymentIntent - const stripeCharge = first(charges.data) - const stripeChargeId = stripeCharge?.id as string - const stripeChargeAmount = stripeCharge?.amount || 0 - - // extract MerchantCoupon identifier if used for purchase - const discount = first(lineItem.discounts) - const stripeCouponId = discount?.discount.coupon.id - - const parsedMetadata = metadata - ? PurchaseMetadata.parse(metadata) - : undefined - - const info: PurchaseInfo = { - customerIdentifier: stripeCustomerId, - email, - name, - productIdentifier: stripeProduct.id, - product: stripeProduct, - chargeIdentifier: stripeChargeId, - couponIdentifier: stripeCouponId, - quantity, - chargeAmount: stripeChargeAmount, - metadata: parsedMetadata, - } - - return PurchaseInfoSchema.parse(info) - } - - return { - name: 'stripe', - paymentClient: stripeClient, - getPurchaseInfo: getStripePurchaseInfo, - } -} - // Two concepts for the providers: // 1. We have the Payment Provider Functions (factories?) that take a few config values // 2. We have the Payment Provider Options which are the resulting object of the above function diff --git a/packages/commerce-server/src/providers/stripe-provider.ts b/packages/commerce-server/src/providers/stripe-provider.ts new file mode 100644 index 000000000..93085571c --- /dev/null +++ b/packages/commerce-server/src/providers/stripe-provider.ts @@ -0,0 +1,64 @@ +import {getStripeSdk} from '@skillrecordings/stripe-sdk' +import {first} from 'lodash' +import Stripe from 'stripe' +import { + StripeProviderFunction, + PurchaseMetadata, + PurchaseInfo, + PurchaseInfoSchema, +} from './default-payment-options' + +export const StripeProvider: StripeProviderFunction = (config) => { + const stripeClient = + 'defaultStripeClient' in config + ? config.defaultStripeClient + : new Stripe(config.stripeSecretKey, { + apiVersion: config.apiVersion, + }) + + const getStripePurchaseInfo = async (checkoutSessionId: string) => { + const {getCheckoutSession} = getStripeSdk({ctx: {stripe: stripeClient}}) + + const checkoutSession = await getCheckoutSession(checkoutSessionId) + + const {customer, line_items, payment_intent, metadata} = checkoutSession + const {email, name, id: stripeCustomerId} = customer as Stripe.Customer + const lineItem = first(line_items?.data) as Stripe.LineItem + const stripePrice = lineItem.price + const quantity = lineItem.quantity || 1 + const stripeProduct = stripePrice?.product as Stripe.Product + const {charges} = payment_intent as Stripe.PaymentIntent + const stripeCharge = first(charges.data) + const stripeChargeId = stripeCharge?.id as string + const stripeChargeAmount = stripeCharge?.amount || 0 + + // extract MerchantCoupon identifier if used for purchase + const discount = first(lineItem.discounts) + const stripeCouponId = discount?.discount.coupon.id + + const parsedMetadata = metadata + ? PurchaseMetadata.parse(metadata) + : undefined + + const info: PurchaseInfo = { + customerIdentifier: stripeCustomerId, + email, + name, + productIdentifier: stripeProduct.id, + product: stripeProduct, + chargeIdentifier: stripeChargeId, + couponIdentifier: stripeCouponId, + quantity, + chargeAmount: stripeChargeAmount, + metadata: parsedMetadata, + } + + return PurchaseInfoSchema.parse(info) + } + + return { + name: 'stripe', + paymentClient: stripeClient, + getPurchaseInfo: getStripePurchaseInfo, + } +}