Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

jbranchaud/sks 87 derive payment provider from url query params #1469

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion apps/testing-javascript/src/pages/thanks/purchase.tsx
Expand Up @@ -27,10 +27,13 @@ import {paymentOptions} from '../api/skill/[...skillRecordings]'
export const getServerSideProps: GetServerSideProps = async (context) => {
const {query} = context

const provider =
(query.provider instanceof Array ? query.provider[0] : query.provider) ||
'stripe'
const session_id =
query.session_id instanceof Array ? query.session_id[0] : query.session_id

const paymentProvider = paymentOptions.providers.stripe
const paymentProvider = paymentOptions.getProvider(provider)

if (!session_id || !paymentProvider) {
return {
Expand Down
5 changes: 4 additions & 1 deletion apps/testing-javascript/src/pages/welcome/index.tsx
Expand Up @@ -18,12 +18,15 @@ import {paymentOptions} from '../api/skill/[...skillRecordings]'

export const getServerSideProps: GetServerSideProps = async ({req, query}) => {
const {purchaseId: purchaseQueryParam, upgrade} = query
const provider =
(query.provider instanceof Array ? query.provider[0] : query.provider) ||
'stripe'
const session_id =
query.session_id instanceof Array ? query.session_id[0] : query.session_id
const token = await getToken({req})
const {getPurchaseDetails} = getSdk()

const paymentProvider = paymentOptions.providers.stripe
const paymentProvider = paymentOptions.getProvider(provider)

let purchaseId = purchaseQueryParam

Expand Down
36 changes: 28 additions & 8 deletions packages/commerce-server/src/providers/default-payment-options.ts
Expand Up @@ -47,20 +47,27 @@ export type StripeProviderFunction = (
) => StripeProvider

type Paypal = 'paypal-client'
type PaypalProvider = {name: 'paypal'; paymentClient: Paypal}
type PaypalProvider = {
name: 'paypal'
paymentClient: Paypal
} & PaymentProviderFunctionality
type PaypalProviderFunction = (options: {
paypalSecretKey: string
}) => PaypalProvider

type PaymentProviderOptions = StripeProvider | PaypalProvider

type SupportedProviders = {
stripe?: StripeProvider
paypal?: PaypalProvider
}
export type PaymentOptions = {
providers: {
stripe?: StripeProvider
paypal?: PaypalProvider
}
getProvider: (providerName: string) => PaymentProvider | undefined
providers: SupportedProviders
}

type ProviderNames = Readonly<Array<keyof PaymentOptions['providers']>>

// 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 All @@ -69,10 +76,23 @@ export const defaultPaymentOptions = (options: {
stripeProvider?: StripeProvider
paypalProvider?: PaypalProvider
}): PaymentOptions => {
const supportedProviderNames: ProviderNames = ['stripe', 'paypal'] as const

const providers: SupportedProviders = {
stripe: options.stripeProvider,
paypal: options.paypalProvider,
}

return {
providers: {
stripe: options.stripeProvider,
paypal: options.paypalProvider,
getProvider: (providerName: string) => {
for (const supportedProviderName of supportedProviderNames) {
if (providerName === supportedProviderName) {
return providers[supportedProviderName]
}
}

return undefined
},
providers,
}
}
Expand Up @@ -299,7 +299,7 @@ export const processStripeWebhook = async (
if (nextAuthOptions) {
await sendServerEmail({
email,
callbackUrl: `${process.env.NEXT_PUBLIC_URL}/welcome?purchaseId=${purchase.id}`,
callbackUrl: `${process.env.NEXT_PUBLIC_URL}/welcome?purchaseId=${purchase.id}&provider=stripe`,
nextAuthOptions,
type: 'purchase',
})
Expand Down
44 changes: 40 additions & 4 deletions packages/skill-api/src/core/services/stripe-checkout.ts
Expand Up @@ -7,7 +7,7 @@ import {
getSdk,
prisma,
} from '@skillrecordings/database'
import {first} from 'lodash'
import {first, isEmpty} from 'lodash'
import {add} from 'date-fns'
import {
getCalculatedPrice,
Expand All @@ -24,6 +24,24 @@ import {

const {stripe: defaultStripe} = defaultStripeContext

const buildSearchParams = (params: object) => {
// implementing this instead of using `URLSearchParams` because that API
// does URL encoding of values in the URL like the curly braces in
// `session_id={CHECKOUT_SESSION_ID}` which needs to get passed to stripe
// as is.
if (isEmpty(params)) {
return ''
} else {
const paramsAsString = Object.entries(params)
.map(([key, value]) => {
return `${key}=${value}`
})
.join('&')

return paramsAsString
}
}

/**
* Given a specific user we want to lookup their Stripe
* customer ID and if one doesn't exist we will
Expand Down Expand Up @@ -407,9 +425,27 @@ export async function stripeCheckout({
throw new CheckoutError('No product was found', productId as string)
}

const successUrl = isUpgrade
? `${process.env.NEXT_PUBLIC_URL}/welcome?session_id={CHECKOUT_SESSION_ID}&upgrade=true`
: `${process.env.NEXT_PUBLIC_URL}/thanks/purchase?session_id={CHECKOUT_SESSION_ID}`
let successUrl: string = (() => {
const baseQueryParams = {
session_id: '{CHECKOUT_SESSION_ID}',
provider: 'stripe',
}

if (isUpgrade) {
const queryParamString = buildSearchParams({
...baseQueryParams,
upgrade: 'true',
})
const url = `${process.env.NEXT_PUBLIC_URL}/welcome?${queryParamString}`

return url
} else {
const queryParamString = buildSearchParams(baseQueryParams)

const url = `${process.env.NEXT_PUBLIC_URL}/thanks/purchase?${queryParamString}`
return url
}
})()

const metadata = {
...(Boolean(availableUpgrade && upgradeFromPurchase) && {
Expand Down