From c632511ba0b6f0291df5acd56cffa1aab3c09bad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20K=C3=BCndig?= Date: Fri, 22 Dec 2023 09:46:24 +0700 Subject: [PATCH] refactor(general): only create firestore user on successful stripe charge (#686) --- .../{Contributions.ts => Contributions.tsx} | 47 ++++++--- admin/src/collections/Users.tsx | 99 ++++--------------- .../PostfinancePaymentsFileImporter.ts | 6 +- shared/src/stripe/StripeEventHandler.ts | 41 +++++--- .../src/stripe/StripeWebhookHandler.test.ts | 15 ++- shared/src/types/contribution.ts | 15 +-- shared/src/types/currency.ts | 1 + shared/src/types/user.ts | 14 +-- .../stats/ContributionStatsCalculator.test.ts | 27 ++--- .../stats/ContributionStatsCalculator.ts | 2 +- .../stripe/checkout-session/create/route.ts | 3 +- 11 files changed, 119 insertions(+), 151 deletions(-) rename admin/src/collections/{Contributions.ts => Contributions.tsx} (75%) diff --git a/admin/src/collections/Contributions.ts b/admin/src/collections/Contributions.tsx similarity index 75% rename from admin/src/collections/Contributions.ts rename to admin/src/collections/Contributions.tsx index 559f486bd..f23af3cad 100644 --- a/admin/src/collections/Contributions.ts +++ b/admin/src/collections/Contributions.tsx @@ -17,6 +17,7 @@ export function buildContributionsCollection( icon: 'Paid', path: CONTRIBUTION_FIRESTORE_PATH, textSearchEnabled: false, + inlineEditing: false, initialSort: ['created', 'desc'], properties: buildProperties({ source: { @@ -53,32 +54,54 @@ export function buildContributionsCollection( }, validation: { required: true }, }, + status: { + dataType: 'string', + name: 'Status', + validation: { required: true }, + enumValues: [ + { id: StatusKey.SUCCEEDED, label: 'Succeeded' }, + { id: StatusKey.PENDING, label: 'Pending' }, + { id: StatusKey.FAILED, label: 'Failed' }, + { id: StatusKey.UNKNOWN, label: 'Unknown' }, + ], + defaultValue: StatusKey.SUCCEEDED, + }, amount_chf: { dataType: 'number', - name: 'Amount Chf (without fees applied)', + name: 'Amount CHF (same as amount if currency is CHF)', + validation: { required: true }, }, fees_chf: { dataType: 'number', name: 'Fees Chf', + validation: { required: true }, }, reference_id: { dataType: 'string', name: 'External Reference', + Preview: (property) => { + return ( + <> + {property.entity?.values.source === ContributionSourceKey.STRIPE ? ( + + {property.value} + + ) : ( + property.value + )} + + ); + }, }, monthly_interval: { dataType: 'number', name: 'Monthly recurrence interval', - }, - status: { - dataType: 'string', - name: 'Status', - enumValues: [ - { id: StatusKey.SUCCEEDED, label: 'Succeeded' }, - { id: StatusKey.PENDING, label: 'Pending' }, - { id: StatusKey.FAILED, label: 'Failed' }, - { id: StatusKey.UNKNOWN, label: 'Unknown' }, - ], - defaultValue: StatusKey.SUCCEEDED, + validation: { required: true }, + enumValues: { 0: 'One time', 1: 'Monthly', 3: 'Quarterly', 12: 'Annually' }, }, }), ...collectionProps, diff --git a/admin/src/collections/Users.tsx b/admin/src/collections/Users.tsx index 0aa52ea85..973e597c3 100644 --- a/admin/src/collections/Users.tsx +++ b/admin/src/collections/Users.tsx @@ -1,59 +1,10 @@ import { USER_FIRESTORE_PATH, User, UserReferralSource } from '@socialincome/shared/src/types/user'; -import { AdditionalFieldDelegate, buildProperties } from 'firecms'; +import { buildProperties } from 'firecms'; import { CreateDonationCertificatesAction } from '../actions/CreateDonationCertificatesAction'; import { buildContributionsCollection } from './Contributions'; import { donationCertificateCollection } from './DonationCertificate'; import { buildAuditedCollection } from './shared'; -const FirstNameCol: AdditionalFieldDelegate = { - id: 'first_name_col', - name: 'First Name', - Builder: ({ entity }) => <>{entity.values?.personal?.name}, - dependencies: ['personal'], -}; - -const LastNameCol: AdditionalFieldDelegate = { - id: 'last_name_col', - name: 'Last Name', - Builder: ({ entity }) => <>{entity.values?.personal?.lastname}, - dependencies: ['personal'], -}; - -const GenderCol: AdditionalFieldDelegate = { - id: 'gender_col', - name: 'Gender', - Builder: ({ entity }) => <>{entity.values?.personal?.gender}, - dependencies: ['personal'], -}; - -const PhoneCol: AdditionalFieldDelegate = { - id: 'phone_col', - name: 'Phone', - Builder: ({ entity }) => <>{entity.values?.personal?.phone}, - dependencies: ['personal'], -}; - -const CountryCol: AdditionalFieldDelegate = { - id: 'country_col', - name: 'Country', - Builder: ({ entity }) => <>{entity.values?.address?.country}, - dependencies: ['address'], -}; - -const CityCol: AdditionalFieldDelegate = { - id: 'city_col', - name: 'City', - Builder: ({ entity }) => <>{entity.values?.address?.city}, - dependencies: ['address'], -}; - -const ReferralCol: AdditionalFieldDelegate = { - id: 'referral_col', - name: 'Referral', - Builder: ({ entity }) => <>{entity.values?.personal?.referral}, - dependencies: ['personal'], -}; - export const usersCollection = buildAuditedCollection({ path: USER_FIRESTORE_PATH, group: 'Contributors', @@ -62,17 +13,12 @@ export const usersCollection = buildAuditedCollection({ singularName: 'Contributor', description: 'Lists all contributors', textSearchEnabled: true, - permissions: () => ({ - edit: true, - create: true, - delete: false, - }), - additionalFields: [FirstNameCol, LastNameCol, GenderCol, PhoneCol, CountryCol, CityCol, ReferralCol], + permissions: () => ({ edit: true, create: true, delete: false }), subcollections: [buildContributionsCollection(), donationCertificateCollection], Actions: CreateDonationCertificatesAction, properties: buildProperties({ - test_user: { - name: 'Test User', + institution: { + name: 'Institutional', dataType: 'boolean', }, email: { @@ -80,11 +26,6 @@ export const usersCollection = buildAuditedCollection({ validation: { required: true }, dataType: 'string', }, - auth_user_id: { - name: 'Auth User Id', - dataType: 'string', - readOnly: true, - }, personal: { name: 'Personal Info', dataType: 'map', @@ -92,10 +33,12 @@ export const usersCollection = buildAuditedCollection({ name: { name: 'Name', dataType: 'string', + validation: { required: true }, }, lastname: { name: 'Last Name', dataType: 'string', + validation: { required: true }, }, gender: { name: 'Gender', @@ -136,6 +79,7 @@ export const usersCollection = buildAuditedCollection({ country: { name: 'Country', dataType: 'string', + validation: { required: true }, }, city: { name: 'City', @@ -155,24 +99,10 @@ export const usersCollection = buildAuditedCollection({ }, }, }, - institution: { - name: 'Institutional', - dataType: 'boolean', - }, language: { name: 'Language', dataType: 'string', }, - location: { - name: 'Location', - description: 'Living location defined by List of ISO 3166 country codes', - dataType: 'string', - validation: { - required: true, - length: 2, - uppercase: true, - }, - }, currency: { name: 'Currency', dataType: 'string', @@ -183,15 +113,20 @@ export const usersCollection = buildAuditedCollection({ }, validation: { required: true }, }, - status: { - name: 'Status', - dataType: 'number', - disabled: true, + auth_user_id: { + name: 'Auth User Id', + dataType: 'string', + readOnly: true, }, stripe_customer_id: { - name: 'stripe customer id', + name: 'Stripe Customer', dataType: 'string', readOnly: true, + Preview: (property) => ( + + {property.value} + + ), }, payment_reference_id: { name: 'Swiss QR-bill payment reference id', diff --git a/functions/src/storage/postfinance-payments-files/PostfinancePaymentsFileImporter.ts b/functions/src/storage/postfinance-payments-files/PostfinancePaymentsFileImporter.ts index 6e32c9084..f2395bf46 100644 --- a/functions/src/storage/postfinance-payments-files/PostfinancePaymentsFileImporter.ts +++ b/functions/src/storage/postfinance-payments-files/PostfinancePaymentsFileImporter.ts @@ -39,7 +39,7 @@ export class PostfinancePaymentsFileImporter { for (let node of nodes) { const contribution: BankWireContribution = { - referenceId: parseFloat(select('string(//ns:Refs/ns:AcctSvcrRef)', node) as string), + reference_id: parseFloat(select('string(//ns:Refs/ns:AcctSvcrRef)', node) as string), currency: (select('string(//ns:Amt/@Ccy)', node) as string).toUpperCase(), amount: parseFloat(select('string(//ns:Amt)', node) as string), amount_chf: parseFloat(select('string(//ns:Amt)', node) as string), @@ -47,11 +47,11 @@ export class PostfinancePaymentsFileImporter { status: StatusKey.SUCCEEDED, created: toFirebaseAdminTimestamp(DateTime.now()), source: ContributionSourceKey.WIRE_TRANSFER, - rawContent: node.toString(), + raw_content: node.toString(), }; const user = await this.firestoreAdmin.findFirst(USER_FIRESTORE_PATH, (q) => - q.where('paymentReferenceId', '==', contribution.referenceId), + q.where('paymentReferenceId', '==', contribution.reference_id), ); if (user) { diff --git a/shared/src/stripe/StripeEventHandler.ts b/shared/src/stripe/StripeEventHandler.ts index 4324ed799..7fa263407 100644 --- a/shared/src/stripe/StripeEventHandler.ts +++ b/shared/src/stripe/StripeEventHandler.ts @@ -10,7 +10,8 @@ import { StripeContribution, } from '../types/contribution'; import { CountryCode } from '../types/country'; -import { USER_FIRESTORE_PATH, User, UserStatusKey, splitName } from '../types/user'; +import { Currency, bestGuessCurrency } from '../types/currency'; +import { USER_FIRESTORE_PATH, User, splitName } from '../types/user'; export class StripeEventHandler { readonly stripe: Stripe; @@ -31,14 +32,21 @@ export class StripeEventHandler { const fullCharge = await this.stripe.charges.retrieve(chargeId, { expand: ['balance_transaction', 'invoice'], }); - await this.storeCharge(fullCharge); + // We only store non-successful charges if the user already exists. + // This prevents us from having users in the database that never made a successful contribution. + if ( + fullCharge.status === 'succeeded' || + (await this.findFirestoreUser(await this.retrieveStripeCustomer(fullCharge.customer as string))) + ) { + await this.storeCharge(fullCharge); + } }; updateUser = async (checkoutSessionId: string, userData: Partial) => { const checkoutSession = await this.stripe.checkout.sessions.retrieve(checkoutSessionId); const customer = await this.stripe.customers.retrieve(checkoutSession.customer as string); if (customer.deleted) throw Error(`Dealing with a deleted Stripe customer (id=${customer.id})`); - const userRef = await this.getOrCreateUser(customer); + const userRef = await this.getOrCreateFirestoreUser(customer); const user = await userRef.get(); await this.firestoreAdmin.doc(USER_FIRESTORE_PATH, user.id).update(userData); }; @@ -53,8 +61,8 @@ export class StripeEventHandler { /** * Try to find an existing user using create a new on. */ - getOrCreateUser = async (customer: Stripe.Customer): Promise> => { - const userDoc = await this.findUser(customer); + getOrCreateFirestoreUser = async (customer: Stripe.Customer): Promise> => { + const userDoc = await this.findFirestoreUser(customer); if (!userDoc) { console.info(`User not found for stripe customer: ${customer.id}`); const userToCreate = this.constructUser(customer); @@ -68,9 +76,9 @@ export class StripeEventHandler { }; /** - * First tries to match using the stripe_customer_id otherwise falls back to email. + * First, tries to match using the stripe_customer_id otherwise falls back to email. */ - findUser = async (customer: Stripe.Customer) => { + findFirestoreUser = async (customer: Stripe.Customer) => { return ( (await this.firestoreAdmin.findFirst('users', (col) => col.where('stripe_customer_id', '==', customer.id), @@ -78,6 +86,12 @@ export class StripeEventHandler { ); }; + retrieveStripeCustomer = async (customerId: string) => { + const customer = await this.stripe.customers.retrieve(customerId); + if (customer.deleted) throw Error(`Dealing with a deleted Stripe customer (id=${customer.id})`); + return customer; + }; + /** * Transforms the stripe charge into our own Contribution representation */ @@ -89,7 +103,7 @@ export class StripeEventHandler { source: ContributionSourceKey.STRIPE, created: toFirebaseAdminTimestamp(DateTime.fromSeconds(charge.created)), amount: charge.amount / 100, - currency: charge.currency, + currency: charge.currency.toUpperCase() as Currency, amount_chf: balanceTransaction?.amount ? balanceTransaction.amount / 100 : 0, fees_chf: balanceTransaction?.fee ? balanceTransaction.fee / 100 : 0, monthly_interval: monthlyInterval, @@ -129,22 +143,19 @@ export class StripeEventHandler { country: customer.address?.country as CountryCode, }, email: customer.email, - status: UserStatusKey.INITIALIZED, stripe_customer_id: customer.id, payment_reference_id: DateTime.now().toMillis(), + currency: bestGuessCurrency(customer.address?.country as CountryCode), test_user: false, - location: customer.address?.country?.toLowerCase(), - currency: customer.currency, }; }; /** - * Converts the stripe charge to a contribution and stores it in the contributions subcollection of the corresponding user. + * Converts the stripe charge to a contribution and stores it in the 'contributions' subcollection of the corresponding user. */ storeCharge = async (charge: Stripe.Charge): Promise> => { - const customer = await this.stripe.customers.retrieve(charge.customer as string); - if (customer.deleted) throw Error(`Dealing with a deleted Stripe customer (id=${customer.id})`); - const userRef = await this.getOrCreateUser(customer); + const customer = await this.retrieveStripeCustomer(charge.customer as string); + const userRef = await this.getOrCreateFirestoreUser(customer); const contribution = this.constructContribution(charge); const contributionRef = ( userRef.collection(CONTRIBUTION_FIRESTORE_PATH) as CollectionReference diff --git a/shared/src/stripe/StripeWebhookHandler.test.ts b/shared/src/stripe/StripeWebhookHandler.test.ts index 0e7bb1f97..966ffe6ca 100644 --- a/shared/src/stripe/StripeWebhookHandler.test.ts +++ b/shared/src/stripe/StripeWebhookHandler.test.ts @@ -5,7 +5,7 @@ import { FirestoreAdmin } from '../firebase/admin/FirestoreAdmin'; import { getOrInitializeFirebaseAdmin } from '../firebase/admin/app'; import { toFirebaseAdminTimestamp } from '../firebase/admin/utils'; import { ContributionSourceKey, StatusKey, StripeContribution } from '../types/contribution'; -import { User, UserStatusKey } from '../types/user'; +import { User } from '../types/user'; import { StripeEventHandler } from './StripeEventHandler'; describe('stripeWebhook', () => { @@ -22,14 +22,14 @@ describe('stripeWebhook', () => { }); test('storeCharge for inexisting user', async () => { - const initialUser = await stripeWebhook.findUser(testCustomer); + const initialUser = await stripeWebhook.findFirestoreUser(testCustomer); expect(initialUser).toBeUndefined(); const ref = await stripeWebhook.storeCharge(testCharge); const contribution = await ref!.get(); expect(contribution.data()).toEqual(expectedContribution); - const createdUser = (await stripeWebhook.findUser(testCustomer))!.data(); + const createdUser = (await stripeWebhook.findFirestoreUser(testCustomer))!.data(); expect(Math.round(createdUser.payment_reference_id / 100000)).toEqual( // rounded to 100 seconds Math.round(expectedUser.payment_reference_id / 100000), @@ -37,10 +37,8 @@ describe('stripeWebhook', () => { expect(createdUser.personal).toEqual(expectedUser.personal); expect(createdUser.email).toEqual(expectedUser.email); expect(createdUser.stripe_customer_id).toEqual(expectedUser.stripe_customer_id); - expect(createdUser.status).toEqual(expectedUser.status); expect(createdUser.currency).toEqual(expectedUser.currency); expect(createdUser.test_user).toEqual(expectedUser.test_user); - expect(createdUser.location).toEqual(expectedUser.location); }); test('storeCharge for existing user through stripe id', async () => { @@ -400,7 +398,7 @@ describe('stripeWebhook', () => { source: ContributionSourceKey.STRIPE, created: toFirebaseAdminTimestamp(new Date('2021-03-05T18:36:21.000Z')), amount: 900, - currency: 'usd', + currency: 'USD', amount_chf: 818.68, fees_chf: 24.04, monthly_interval: 3, @@ -413,12 +411,13 @@ describe('stripeWebhook', () => { name: 'Test', lastname: 'User', }, + address: { + country: 'CH', + }, email: 'test@socialincome.org', stripe_customer_id: 'cus_123', payment_reference_id: DateTime.now().toMillis(), test_user: false, - status: UserStatusKey.INITIALIZED, - location: 'us', currency: 'USD', }; }); diff --git a/shared/src/types/contribution.ts b/shared/src/types/contribution.ts index 5f4d9eb0e..d816fdf0b 100644 --- a/shared/src/types/contribution.ts +++ b/shared/src/types/contribution.ts @@ -1,3 +1,4 @@ +import { Currency } from './currency'; import { Timestamp } from './timestamp'; export const CONTRIBUTION_FIRESTORE_PATH = 'contributions'; @@ -18,24 +19,26 @@ export enum StatusKey { UNKNOWN = 'unknown', } -export type Contribution = { +export type Contribution = StripeContribution | BankWireContribution; + +type BaseContribution = { source: ContributionSourceKey; status: StatusKey; created: Timestamp; amount: number; amount_chf: number; fees_chf: number; - currency: string; + currency: Currency; }; -export type StripeContribution = Contribution & { +export type StripeContribution = BaseContribution & { source: ContributionSourceKey.STRIPE; monthly_interval: number; reference_id: string; // stripe charge id }; -export type BankWireContribution = Contribution & { +export type BankWireContribution = BaseContribution & { source: ContributionSourceKey.WIRE_TRANSFER; - rawContent: string; - referenceId: number; // reference number from the bank wire + raw_content: string; + reference_id: number; // reference number from the bank wire }; diff --git a/shared/src/types/currency.ts b/shared/src/types/currency.ts index 2bcf72cde..68a8d7d82 100644 --- a/shared/src/types/currency.ts +++ b/shared/src/types/currency.ts @@ -42,6 +42,7 @@ const countryToCurrency = new Map([ ['NO', 'EUR'], ['PL', 'EUR'], ['PT', 'EUR'], + ['US', 'USD'], ]); export const bestGuessCurrency = (country: CountryCode | undefined): Currency => { diff --git a/shared/src/types/user.ts b/shared/src/types/user.ts index 00ebf23c6..9fcd267a8 100644 --- a/shared/src/types/user.ts +++ b/shared/src/types/user.ts @@ -1,15 +1,11 @@ import { EntityReference } from 'firecms'; import { capitalizeStringIfUppercase } from '../utils/strings'; import { CountryCode } from './country'; +import { Currency } from './currency'; import { LanguageCode } from './language'; export const USER_FIRESTORE_PATH = 'users'; -export enum UserStatusKey { - INITIALIZED = 0, // automatically created through the system - PROFILE_CREATED = 1, // user submitted registration form -} - export const GENDER_OPTIONS = ['male', 'female', 'other', 'private'] as const; export type Gender = (typeof GENDER_OPTIONS)[number]; @@ -23,11 +19,11 @@ export enum UserReferralSource { } export type UserAddress = { + country: CountryCode; street?: string; number?: string; city?: string; zip?: number; - country?: CountryCode; }; export type User = { @@ -40,16 +36,14 @@ export type User = { phone?: string; referral?: UserReferralSource; }; - address?: UserAddress; + address: UserAddress; email: string; - status?: UserStatusKey; payment_reference_id: number; // used to identify user in wire transfer stripe_customer_id?: string; test_user?: boolean; // TODO: discuss if still needed institution?: boolean; language?: LanguageCode; - location?: string; // TODO: discuss if still needed - currency?: string | null; // TODO: proper typing + currency?: Currency; contributor_organisations?: EntityReference[]; }; diff --git a/shared/src/utils/stats/ContributionStatsCalculator.test.ts b/shared/src/utils/stats/ContributionStatsCalculator.test.ts index d729af1c7..c3b1f3a32 100644 --- a/shared/src/utils/stats/ContributionStatsCalculator.test.ts +++ b/shared/src/utils/stats/ContributionStatsCalculator.test.ts @@ -5,7 +5,7 @@ import { FirestoreAdmin } from '../../firebase/admin/FirestoreAdmin'; import { getOrInitializeFirebaseAdmin } from '../../firebase/admin/app'; import { toFirebaseAdminTimestamp } from '../../firebase/admin/utils'; import { CONTRIBUTION_FIRESTORE_PATH, ContributionSourceKey, StatusKey } from '../../types/contribution'; -import { USER_FIRESTORE_PATH, User, UserStatusKey } from '../../types/user'; +import { USER_FIRESTORE_PATH, User } from '../../types/user'; import { ContributionStatsCalculator } from './ContributionStatsCalculator'; const projectId = 'contribution-stats-calculator-test'; @@ -79,12 +79,13 @@ const user1: User = { name: 'User1', lastname: 'User1', }, + address: { + country: 'US', + }, email: '123@socialincome.org', stripe_customer_id: 'cus_123', payment_reference_id: DateTime.now().toMillis(), test_user: false, - status: UserStatusKey.INITIALIZED, - location: 'US', currency: 'USD', }; const contributionsUser1 = ['2023-01-05', '2023-02-05', '2023-03-05', '2023-04-05'].map((date) => { @@ -92,7 +93,7 @@ const contributionsUser1 = ['2023-01-05', '2023-02-05', '2023-03-05', '2023-04-0 source: ContributionSourceKey.STRIPE, created: toFirebaseAdminTimestamp(new Date(date)), amount: 100, - currency: 'usd', + currency: 'USD', amount_chf: 100, fees_chf: 2, monthly_interval: 3, @@ -106,21 +107,22 @@ const user2: User = { name: 'User2', lastname: 'User2', }, + address: { + country: 'CH', + }, email: '456@socialincome.org', stripe_customer_id: 'cus_456', payment_reference_id: DateTime.now().toMillis(), test_user: false, - status: UserStatusKey.INITIALIZED, institution: true, - location: 'ch', - currency: 'chf', + currency: 'CHF', }; const contributionsUser2 = ['2023-01-08', '2023-04-09'].map((date) => { return { source: ContributionSourceKey.BENEVITY, created: toFirebaseAdminTimestamp(new Date(date)), amount: 1000, - currency: 'chf', + currency: 'CHF', amount_chf: 1000, fees_chf: 20, monthly_interval: 3, @@ -135,12 +137,13 @@ const testUser: User = { name: 'Test User', lastname: 'User2', }, + address: { + country: 'US', + }, email: 'test@socialincome.org', - stripe_customer_id: 'cus_123', + stripe_customer_id: 'cus_124', payment_reference_id: DateTime.now().toMillis(), test_user: true, - status: UserStatusKey.INITIALIZED, - location: 'US', currency: 'USD', }; const contributionsTestUser = ['2023-01-05'].map((date) => { @@ -148,7 +151,7 @@ const contributionsTestUser = ['2023-01-05'].map((date) => { source: ContributionSourceKey.STRIPE, created: toFirebaseAdminTimestamp(new Date(date)), amount: 100, - currency: 'usd', + currency: 'USD', amount_chf: 818.68, fees_chf: 24.04, monthly_interval: 3, diff --git a/shared/src/utils/stats/ContributionStatsCalculator.ts b/shared/src/utils/stats/ContributionStatsCalculator.ts index 1084d03c8..af6bf3e29 100644 --- a/shared/src/utils/stats/ContributionStatsCalculator.ts +++ b/shared/src/utils/stats/ContributionStatsCalculator.ts @@ -84,7 +84,7 @@ export class ContributionStatsCalculator { return { userId: userDoc.id, isInstitution: Boolean(user.institution), - country: user.location?.toUpperCase() ?? 'CH', + country: user.address.country ?? 'CH', amount: contribution.amount_chf * exchangeRate, paymentFees: contribution.fees_chf * exchangeRate, source: contribution.source, diff --git a/website/src/app/api/stripe/checkout-session/create/route.ts b/website/src/app/api/stripe/checkout-session/create/route.ts index 881f35462..8ba04d073 100644 --- a/website/src/app/api/stripe/checkout-session/create/route.ts +++ b/website/src/app/api/stripe/checkout-session/create/route.ts @@ -29,13 +29,12 @@ export async function POST(request: CreateCheckoutSessionRequest) { const price = await stripe.prices.create({ active: true, unit_amount: amount, - currency: currency, + currency: currency.toLowerCase(), product: recurring ? process.env.STRIPE_PRODUCT_RECURRING : process.env.STRIPE_PRODUCT_ONETIME, recurring: recurring ? { interval: 'month', interval_count: intervalCount } : undefined, }); const session = await stripe.checkout.sessions.create({ mode: recurring ? 'subscription' : 'payment', - payment_method_types: ['card'], customer: customerId, customer_creation: customerId || recurring ? undefined : 'always', line_items: [