Skip to content

Commit

Permalink
feat: extract stripe provider
Browse files Browse the repository at this point in the history
  • Loading branch information
jbranchaud authored and kodiakhq[bot] committed Mar 22, 2024
1 parent 35af7be commit 96b2b51
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 62 deletions.
65 changes: 3 additions & 62 deletions 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'

Expand All @@ -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(),
Expand Down Expand Up @@ -44,7 +42,7 @@ type StripeProvider = {
name: 'stripe'
paymentClient: Stripe
} & PaymentProviderFunctionality
type StripeProviderFunction = (
export type StripeProviderFunction = (
options: StripeConfig | {defaultStripeClient: Stripe},
) => StripeProvider

Expand All @@ -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<Stripe.Charge>(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
Expand Down
64 changes: 64 additions & 0 deletions 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<Stripe.Charge>(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,
}
}

0 comments on commit 96b2b51

Please sign in to comment.