fix requested changes

This commit is contained in:
vincanger 2025-02-19 13:43:42 +01:00
parent d3e67b4eba
commit a986348a7e
2 changed files with 41 additions and 33 deletions

View File

@ -35,10 +35,14 @@ interface CreateStripeCheckoutSessionParams {
mode: StripeMode;
}
export async function createStripeCheckoutSession({ priceId, customerId, mode }: CreateStripeCheckoutSessionParams) {
export async function createStripeCheckoutSession({
priceId,
customerId,
mode,
}: CreateStripeCheckoutSessionParams) {
try {
const metadata = returnMetadataByMode({ mode, priceId });
const paymentIntentData = getPaymentIntentData({ mode, priceId });
return await stripe.checkout.sessions.create({
line_items: [
{
@ -58,7 +62,7 @@ export async function createStripeCheckoutSession({ priceId, customerId, mode }:
// We do this so that we can capture priceId in the payment_intent.succeeded webhook
// and easily confirm the user's payment based on the price id. For subscriptions, we can get the price id
// in the customer.subscription.updated webhook via the line_items field.
...metadata
payment_intent_data: paymentIntentData,
});
} catch (error) {
console.error(error);
@ -66,21 +70,16 @@ export async function createStripeCheckoutSession({ priceId, customerId, mode }:
}
}
interface ReturnMetadataByModeParams {
mode: StripeMode;
priceId: string;
}
interface ReturnMetadataByModeResult {
payment_intent_data: Stripe.Checkout.SessionCreateParams.PaymentIntentData;
}
function returnMetadataByMode({ mode, priceId }: ReturnMetadataByModeParams): ReturnMetadataByModeResult | undefined {
function getPaymentIntentData({ mode, priceId }: { mode: StripeMode; priceId: string }):
| {
metadata: { priceId: string };
}
| undefined {
switch (mode) {
case 'subscription':
return undefined;
case 'payment':
return { payment_intent_data: { metadata: { priceId } } };
return { metadata: { priceId } };
default:
assertUnreachable(mode);
}

View File

@ -4,7 +4,7 @@ import { type PrismaClient } from '@prisma/client';
import express from 'express';
import { Stripe } from 'stripe';
import { stripe } from './stripeClient';
import { paymentPlans, PaymentPlanId, SubscriptionStatus } from '../plans';
import { paymentPlans, PaymentPlanId, SubscriptionStatus, PaymentPlanEffect, PaymentPlan } from '../plans';
import { updateUserStripePaymentDetails } from './paymentDetails';
import { emailSender } from 'wasp/server/email';
import { assertUnreachable } from '../../shared/utils';
@ -35,7 +35,6 @@ export const stripeWebhook: PaymentsWebhook = async (request, response, context)
break;
case 'payment_intent.succeeded':
const paymentIntent = event.data.object as Stripe.PaymentIntent;
if (paymentIntent.invoice) return; // We handle invoices in the invoice.paid webhook.
await handlePaymentIntentSucceeded(paymentIntent, prismaUserDelegate);
break;
case 'customer.subscription.updated':
@ -77,7 +76,11 @@ export async function handleCheckoutSessionCompleted(
});
const lineItemPriceId = extractPriceId(line_items);
const planId = getPlanIdByPriceId(lineItemPriceId);
const { subscriptionPlan } = getPlanEffectDetailsById(planId);
const plan = paymentPlans[planId];
if (plan.effect.kind === 'credits') {
return;
}
const { subscriptionPlan } = getPlanEffectPaymentDetails({ planId, planEffect: plan.effect });
return updateUserStripePaymentDetails(
{ userStripeId, subscriptionPlan },
@ -97,6 +100,12 @@ export async function handlePaymentIntentSucceeded(
paymentIntent: Stripe.PaymentIntent,
prismaUserDelegate: PrismaClient['user']
) {
// We handle invoices in the invoice.paid webhook. Invoices exist for subscription payments,
// but not for one-time payment/credits products which use the Stripe `payment` mode on checkout sessions.
if (paymentIntent.invoice) {
return;
}
const userStripeId = validateUserStripeIdOrThrow(paymentIntent.customer);
const datePaid = new Date(paymentIntent.created * 1000);
@ -109,10 +118,15 @@ export async function handlePaymentIntentSucceeded(
}
const planId = getPlanIdByPriceId(metadata.priceId);
const { subscriptionPlan, numOfCreditsPurchased } = getPlanEffectDetailsById(planId);
const plan = paymentPlans[planId];
if (plan.effect.kind === 'subscription') {
return;
}
const { numOfCreditsPurchased } = getPlanEffectPaymentDetails({ planId, planEffect: plan.effect });
return updateUserStripePaymentDetails(
{ userStripeId, subscriptionPlan, numOfCreditsPurchased, datePaid },
{ userStripeId, numOfCreditsPurchased, datePaid },
prismaUserDelegate
);
}
@ -195,21 +209,16 @@ function getPlanIdByPriceId(priceId: string): PaymentPlanId {
return planId;
}
function getPlanEffectDetailsById(planId: PaymentPlanId) {
const plan = paymentPlans[planId];
let subscriptionPlan: PaymentPlanId | undefined;
let numOfCreditsPurchased: number | undefined;
switch (plan.effect.kind) {
function getPlanEffectPaymentDetails({ planId, planEffect }: { planId: PaymentPlanId, planEffect: PaymentPlanEffect}): {
subscriptionPlan: PaymentPlanId | undefined;
numOfCreditsPurchased: number | undefined;
} {
switch (planEffect.kind) {
case 'subscription':
subscriptionPlan = planId;
break;
return { subscriptionPlan: planId, numOfCreditsPurchased: undefined };
case 'credits':
numOfCreditsPurchased = plan.effect.amount;
break;
return { subscriptionPlan: undefined, numOfCreditsPurchased: planEffect.amount };
default:
assertUnreachable(plan.effect);
assertUnreachable(planEffect);
}
return { subscriptionPlan, numOfCreditsPurchased };
}