mirror of
https://github.com/wasp-lang/open-saas.git
synced 2025-11-19 16:56:34 +01:00
abstract stripe payment stuff
This commit is contained in:
@@ -1,30 +1,28 @@
|
|||||||
--- template/app/src/payment/stripe/paymentProcessor.ts
|
--- template/app/src/payment/stripe/paymentProcessor.ts
|
||||||
+++ opensaas-sh/app/src/payment/stripe/paymentProcessor.ts
|
+++ opensaas-sh/app/src/payment/stripe/paymentProcessor.ts
|
||||||
@@ -13,8 +13,8 @@
|
@@ -8,8 +8,8 @@
|
||||||
} from "./checkoutUtils";
|
} from "../paymentProcessor";
|
||||||
import { stripeClient } from "./stripeClient";
|
import type { PaymentPlanEffect } from "../plans";
|
||||||
import {
|
import {
|
||||||
- fetchUserPaymentProcessorUserId,
|
- fetchUserPaymentProcessorUserId,
|
||||||
- updateUserPaymentProcessorUserId,
|
- updateUserPaymentProcessorUserId,
|
||||||
+ fetchUserStripeId,
|
+ fetchUserStripeId,
|
||||||
+ updateUserStripeId
|
+ updateUserStripeId
|
||||||
} from "./user";
|
} from "../user";
|
||||||
import { stripeMiddlewareConfigFn, stripeWebhook } from "./webhook";
|
import {
|
||||||
|
createStripeCheckoutSession,
|
||||||
@@ -28,10 +28,10 @@
|
@@ -28,8 +28,8 @@
|
||||||
}: CreateCheckoutSessionArgs) => {
|
}: CreateCheckoutSessionArgs) => {
|
||||||
const customer = await ensureStripeCustomer(userEmail);
|
const customer = await ensureStripeCustomer(userEmail);
|
||||||
|
|
||||||
- await updateUserPaymentProcessorUserId(
|
- await updateUserPaymentProcessorUserId(
|
||||||
|
- { userId, paymentProcessorUserId: customer.id },
|
||||||
+ await updateUserStripeId(
|
+ await updateUserStripeId(
|
||||||
{
|
+ { userId, stripeId: customer.id },
|
||||||
userId,
|
|
||||||
- paymentProcessorUserId: customer.id,
|
|
||||||
+ stripeId: customer.id,
|
|
||||||
},
|
|
||||||
prismaUserDelegate,
|
prismaUserDelegate,
|
||||||
);
|
);
|
||||||
@@ -59,18 +59,18 @@
|
|
||||||
|
@@ -56,18 +56,18 @@
|
||||||
prismaUserDelegate,
|
prismaUserDelegate,
|
||||||
userId,
|
userId,
|
||||||
}: FetchCustomerPortalUrlArgs) => {
|
}: FetchCustomerPortalUrlArgs) => {
|
||||||
|
|||||||
38
opensaas-sh/app_diff/src/payment/stripe/webhook.ts.diff
Normal file
38
opensaas-sh/app_diff/src/payment/stripe/webhook.ts.diff
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
--- template/app/src/payment/stripe/webhook.ts
|
||||||
|
+++ opensaas-sh/app/src/payment/stripe/webhook.ts
|
||||||
|
@@ -111,7 +111,7 @@
|
||||||
|
case PaymentPlanId.Credits10:
|
||||||
|
await updateUserCredits(
|
||||||
|
{
|
||||||
|
- paymentProcessorUserId: customerId,
|
||||||
|
+ stripeId: customerId,
|
||||||
|
datePaid: invoicePaidAtDate,
|
||||||
|
numOfCreditsPurchased: paymentPlans[paymentPlanId].effect.amount,
|
||||||
|
},
|
||||||
|
@@ -122,7 +122,7 @@
|
||||||
|
case PaymentPlanId.Hobby:
|
||||||
|
await updateUserSubscription(
|
||||||
|
{
|
||||||
|
- paymentProcessorUserId: customerId,
|
||||||
|
+ stripeId: customerId,
|
||||||
|
datePaid: invoicePaidAtDate,
|
||||||
|
paymentPlanId,
|
||||||
|
subscriptionStatus: SubscriptionStatus.Active,
|
||||||
|
@@ -169,7 +169,7 @@
|
||||||
|
);
|
||||||
|
|
||||||
|
const user = await updateUserSubscription(
|
||||||
|
- { paymentProcessorUserId: customerId, paymentPlanId, subscriptionStatus },
|
||||||
|
+ { stripeId: customerId, paymentPlanId, subscriptionStatus },
|
||||||
|
prismaUserDelegate,
|
||||||
|
);
|
||||||
|
|
||||||
|
@@ -237,7 +237,7 @@
|
||||||
|
|
||||||
|
await updateUserSubscription(
|
||||||
|
{
|
||||||
|
- paymentProcessorUserId: customerId,
|
||||||
|
+ stripeId: customerId,
|
||||||
|
subscriptionStatus: SubscriptionStatus.Deleted,
|
||||||
|
},
|
||||||
|
prismaUserDelegate,
|
||||||
98
opensaas-sh/app_diff/src/payment/user.ts.diff
Normal file
98
opensaas-sh/app_diff/src/payment/user.ts.diff
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
--- template/app/src/payment/user.ts
|
||||||
|
+++ opensaas-sh/app/src/payment/user.ts
|
||||||
|
@@ -2,7 +2,7 @@
|
||||||
|
import { PrismaClient } from "wasp/server";
|
||||||
|
import { PaymentPlanId, SubscriptionStatus } from "./plans";
|
||||||
|
|
||||||
|
-export async function fetchUserPaymentProcessorUserId(
|
||||||
|
+export async function fetchUserStripeId(
|
||||||
|
userId: User["id"],
|
||||||
|
prismaUserDelegate: PrismaClient["user"],
|
||||||
|
): Promise<string | null> {
|
||||||
|
@@ -11,20 +11,20 @@
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
- paymentProcessorUserId: true,
|
||||||
|
+ stripeId: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
- return user.paymentProcessorUserId;
|
||||||
|
+ return user.stripeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
-interface UpdateUserPaymentProcessorUserIdArgs {
|
||||||
|
+interface UpdateUserStripeIdArgs {
|
||||||
|
userId: User["id"];
|
||||||
|
- paymentProcessorUserId: NonNullable<User["paymentProcessorUserId"]>;
|
||||||
|
+ stripeId: NonNullable<User["stripeId"]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
-export function updateUserPaymentProcessorUserId(
|
||||||
|
- { userId, paymentProcessorUserId }: UpdateUserPaymentProcessorUserIdArgs,
|
||||||
|
+export function updateUserStripeId(
|
||||||
|
+ { userId, stripeId }: UpdateUserStripeIdArgs,
|
||||||
|
prismaUserDelegate: PrismaClient["user"],
|
||||||
|
): Promise<User> {
|
||||||
|
return prismaUserDelegate.update({
|
||||||
|
@@ -32,13 +32,13 @@
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
- paymentProcessorUserId,
|
||||||
|
+ stripeId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateUserSubscriptionArgs {
|
||||||
|
- paymentProcessorUserId: NonNullable<User["paymentProcessorUserId"]>;
|
||||||
|
+ stripeId: NonNullable<User["stripeId"]>;
|
||||||
|
subscriptionStatus: SubscriptionStatus;
|
||||||
|
paymentPlanId?: PaymentPlanId;
|
||||||
|
datePaid?: Date;
|
||||||
|
@@ -46,7 +46,7 @@
|
||||||
|
|
||||||
|
export function updateUserSubscription(
|
||||||
|
{
|
||||||
|
- paymentProcessorUserId,
|
||||||
|
+ stripeId,
|
||||||
|
paymentPlanId,
|
||||||
|
subscriptionStatus,
|
||||||
|
datePaid,
|
||||||
|
@@ -55,7 +55,7 @@
|
||||||
|
) {
|
||||||
|
return userDelegate.update({
|
||||||
|
where: {
|
||||||
|
- paymentProcessorUserId,
|
||||||
|
+ stripeId,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
subscriptionPlan: paymentPlanId,
|
||||||
|
@@ -66,14 +66,14 @@
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateUserCreditsArgs {
|
||||||
|
- paymentProcessorUserId: NonNullable<User["paymentProcessorUserId"]>;
|
||||||
|
+ stripeId: NonNullable<User["stripeId"]>;
|
||||||
|
numOfCreditsPurchased: number;
|
||||||
|
datePaid: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateUserCredits(
|
||||||
|
{
|
||||||
|
- paymentProcessorUserId,
|
||||||
|
+ stripeId,
|
||||||
|
numOfCreditsPurchased,
|
||||||
|
datePaid,
|
||||||
|
}: UpdateUserCreditsArgs,
|
||||||
|
@@ -81,7 +81,7 @@
|
||||||
|
) {
|
||||||
|
return userDelegate.update({
|
||||||
|
where: {
|
||||||
|
- paymentProcessorUserId,
|
||||||
|
+ stripeId,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
credits: { increment: numOfCreditsPurchased },
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { User } from "wasp/entities";
|
||||||
import { requireNodeEnvVar } from "../server/utils";
|
import { requireNodeEnvVar } from "../server/utils";
|
||||||
|
|
||||||
export enum SubscriptionStatus {
|
export enum SubscriptionStatus {
|
||||||
@@ -67,3 +68,15 @@ export function getSubscriptionPaymentPlanIds(): PaymentPlanId[] {
|
|||||||
(planId) => paymentPlans[planId].effect.kind === "subscription",
|
(planId) => paymentPlans[planId].effect.kind === "subscription",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getPaymentPlanIdByPaymentProcessorPlanId(
|
||||||
|
paymentProcessorUserId: NonNullable<User["paymentProcessorUserId"]>,
|
||||||
|
): PaymentPlanId {
|
||||||
|
for (const [planId, plan] of Object.entries(paymentPlans)) {
|
||||||
|
if (plan.getPaymentProcessorPlanId() === paymentProcessorUserId) {
|
||||||
|
return planId as PaymentPlanId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Unknown payment processor ID: ${paymentProcessorUserId}`);
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,15 +7,15 @@ import type {
|
|||||||
PaymentProcessor,
|
PaymentProcessor,
|
||||||
} from "../paymentProcessor";
|
} from "../paymentProcessor";
|
||||||
import type { PaymentPlanEffect } from "../plans";
|
import type { PaymentPlanEffect } from "../plans";
|
||||||
|
import {
|
||||||
|
fetchUserPaymentProcessorUserId,
|
||||||
|
updateUserPaymentProcessorUserId,
|
||||||
|
} from "../user";
|
||||||
import {
|
import {
|
||||||
createStripeCheckoutSession,
|
createStripeCheckoutSession,
|
||||||
ensureStripeCustomer,
|
ensureStripeCustomer,
|
||||||
} from "./checkoutUtils";
|
} from "./checkoutUtils";
|
||||||
import { stripeClient } from "./stripeClient";
|
import { stripeClient } from "./stripeClient";
|
||||||
import {
|
|
||||||
fetchUserPaymentProcessorUserId,
|
|
||||||
updateUserPaymentProcessorUserId,
|
|
||||||
} from "./user";
|
|
||||||
import { stripeMiddlewareConfigFn, stripeWebhook } from "./webhook";
|
import { stripeMiddlewareConfigFn, stripeWebhook } from "./webhook";
|
||||||
|
|
||||||
export const stripePaymentProcessor: PaymentProcessor = {
|
export const stripePaymentProcessor: PaymentProcessor = {
|
||||||
@@ -29,20 +29,17 @@ export const stripePaymentProcessor: PaymentProcessor = {
|
|||||||
const customer = await ensureStripeCustomer(userEmail);
|
const customer = await ensureStripeCustomer(userEmail);
|
||||||
|
|
||||||
await updateUserPaymentProcessorUserId(
|
await updateUserPaymentProcessorUserId(
|
||||||
{
|
{ userId, paymentProcessorUserId: customer.id },
|
||||||
userId,
|
|
||||||
paymentProcessorUserId: customer.id,
|
|
||||||
},
|
|
||||||
prismaUserDelegate,
|
prismaUserDelegate,
|
||||||
);
|
);
|
||||||
|
|
||||||
const stripeSession = await createStripeCheckoutSession({
|
const checkoutSession = await createStripeCheckoutSession({
|
||||||
customerId: customer.id,
|
customerId: customer.id,
|
||||||
priceId: paymentPlan.getPaymentProcessorPlanId(),
|
priceId: paymentPlan.getPaymentProcessorPlanId(),
|
||||||
mode: paymentPlanEffectToStripeCheckoutSessionMode(paymentPlan.effect),
|
mode: paymentPlanEffectToStripeCheckoutSessionMode(paymentPlan.effect),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!stripeSession.url) {
|
if (!checkoutSession.url) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Stripe checkout session URL is missing. Checkout session might not be active.",
|
"Stripe checkout session URL is missing. Checkout session might not be active.",
|
||||||
);
|
);
|
||||||
@@ -50,8 +47,8 @@ export const stripePaymentProcessor: PaymentProcessor = {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
session: {
|
session: {
|
||||||
url: stripeSession.url,
|
url: checkoutSession.url,
|
||||||
id: stripeSession.id,
|
id: checkoutSession.id,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,12 +7,14 @@ import { emailSender } from "wasp/server/email";
|
|||||||
import { requireNodeEnvVar } from "../../server/utils";
|
import { requireNodeEnvVar } from "../../server/utils";
|
||||||
import { assertUnreachable } from "../../shared/utils";
|
import { assertUnreachable } from "../../shared/utils";
|
||||||
import { UnhandledWebhookEventError } from "../errors";
|
import { UnhandledWebhookEventError } from "../errors";
|
||||||
import { PaymentPlanId, paymentPlans, SubscriptionStatus } from "../plans";
|
|
||||||
import { stripeClient } from "./stripeClient";
|
|
||||||
import {
|
import {
|
||||||
updateUserOneTimePaymentDetails,
|
getPaymentPlanIdByPaymentProcessorPlanId,
|
||||||
updateUserSubscriptionDetails,
|
PaymentPlanId,
|
||||||
} from "./user";
|
paymentPlans,
|
||||||
|
SubscriptionStatus,
|
||||||
|
} from "../plans";
|
||||||
|
import { updateUserCredits, updateUserSubscription } from "../user";
|
||||||
|
import { stripeClient } from "./stripeClient";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stripe requires a raw request to construct events successfully.
|
* Stripe requires a raw request to construct events successfully.
|
||||||
@@ -35,38 +37,30 @@ export const stripeWebhook: PaymentsWebhook = async (
|
|||||||
) => {
|
) => {
|
||||||
const prismaUserDelegate = context.entities.User;
|
const prismaUserDelegate = context.entities.User;
|
||||||
try {
|
try {
|
||||||
const stripeEvent = constructStripeEvent(request);
|
const event = constructStripeEvent(request);
|
||||||
|
|
||||||
// If you'd like to handle more events, you can add more cases below.
|
// If you'd like to handle more events, you can add more cases below.
|
||||||
// When deploying your app, you configure your webhook in the Stripe dashboard
|
// When deploying your app, you configure your webhook in the Stripe dashboard
|
||||||
// to only send the events that you're handling above.
|
// to only send the events that you're handling above.
|
||||||
// See: https://docs.opensaas.sh/guides/deploying/#setting-up-your-stripe-webhook
|
// See: https://docs.opensaas.sh/guides/deploying/#setting-up-your-stripe-webhook
|
||||||
switch (stripeEvent.type) {
|
switch (event.type) {
|
||||||
case "invoice.paid":
|
case "invoice.paid":
|
||||||
await handleInvoicePaid(stripeEvent, prismaUserDelegate);
|
await handleInvoicePaid(event, prismaUserDelegate);
|
||||||
break;
|
break;
|
||||||
case "customer.subscription.updated":
|
case "customer.subscription.updated":
|
||||||
await handleCustomerSubscriptionUpdated(
|
await handleCustomerSubscriptionUpdated(event, prismaUserDelegate);
|
||||||
stripeEvent,
|
|
||||||
prismaUserDelegate,
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case "customer.subscription.deleted":
|
case "customer.subscription.deleted":
|
||||||
await handleCustomerSubscriptionDeleted(
|
await handleCustomerSubscriptionDeleted(event, prismaUserDelegate);
|
||||||
stripeEvent,
|
|
||||||
prismaUserDelegate,
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new UnhandledWebhookEventError(stripeEvent.type);
|
throw new UnhandledWebhookEventError(event.type);
|
||||||
}
|
}
|
||||||
return response.status(204).send();
|
return response.status(204).send();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof UnhandledWebhookEventError) {
|
if (error instanceof UnhandledWebhookEventError) {
|
||||||
// In development, it is likely that we will receive events that we are not handling.
|
// In development, it is likely that we will receive events that we are not handling.
|
||||||
// E.g. via the `stripe trigger` command.
|
// E.g. via the `stripe trigger` command.
|
||||||
// While these can be ignored safely in development, it's good to be aware of them.
|
|
||||||
// For production we shouldn't have any extra webhook events.
|
|
||||||
if (process.env.NODE_ENV === "development") {
|
if (process.env.NODE_ENV === "development") {
|
||||||
console.info("Unhandled Stripe webhook event in development: ", error);
|
console.info("Unhandled Stripe webhook event in development: ", error);
|
||||||
} else if (process.env.NODE_ENV === "production") {
|
} else if (process.env.NODE_ENV === "production") {
|
||||||
@@ -82,7 +76,7 @@ export const stripeWebhook: PaymentsWebhook = async (
|
|||||||
return response.status(400).json({ error: error.message });
|
return response.status(400).json({ error: error.message });
|
||||||
} else {
|
} else {
|
||||||
return response
|
return response
|
||||||
.status(400)
|
.status(500)
|
||||||
.json({ error: "Error processing Stripe webhook event" });
|
.json({ error: "Error processing Stripe webhook event" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,13 +103,15 @@ async function handleInvoicePaid(
|
|||||||
const invoice = event.data.object;
|
const invoice = event.data.object;
|
||||||
const customerId = getCustomerId(invoice.customer);
|
const customerId = getCustomerId(invoice.customer);
|
||||||
const invoicePaidAtDate = getInvoicePaidAtDate(invoice);
|
const invoicePaidAtDate = getInvoicePaidAtDate(invoice);
|
||||||
const paymentPlanId = getPaymentPlanIdByPriceId(getInvoicePriceId(invoice));
|
const paymentPlanId = getPaymentPlanIdByPaymentProcessorPlanId(
|
||||||
|
getInvoicePriceId(invoice),
|
||||||
|
);
|
||||||
|
|
||||||
switch (paymentPlanId) {
|
switch (paymentPlanId) {
|
||||||
case PaymentPlanId.Credits10:
|
case PaymentPlanId.Credits10:
|
||||||
await updateUserOneTimePaymentDetails(
|
await updateUserCredits(
|
||||||
{
|
{
|
||||||
customerId,
|
paymentProcessorUserId: customerId,
|
||||||
datePaid: invoicePaidAtDate,
|
datePaid: invoicePaidAtDate,
|
||||||
numOfCreditsPurchased: paymentPlans[paymentPlanId].effect.amount,
|
numOfCreditsPurchased: paymentPlans[paymentPlanId].effect.amount,
|
||||||
},
|
},
|
||||||
@@ -124,9 +120,9 @@ async function handleInvoicePaid(
|
|||||||
break;
|
break;
|
||||||
case PaymentPlanId.Pro:
|
case PaymentPlanId.Pro:
|
||||||
case PaymentPlanId.Hobby:
|
case PaymentPlanId.Hobby:
|
||||||
await updateUserSubscriptionDetails(
|
await updateUserSubscription(
|
||||||
{
|
{
|
||||||
customerId,
|
paymentProcessorUserId: customerId,
|
||||||
datePaid: invoicePaidAtDate,
|
datePaid: invoicePaidAtDate,
|
||||||
paymentPlanId,
|
paymentPlanId,
|
||||||
subscriptionStatus: SubscriptionStatus.Active,
|
subscriptionStatus: SubscriptionStatus.Active,
|
||||||
@@ -168,12 +164,12 @@ async function handleCustomerSubscriptionUpdated(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const customerId = getCustomerId(subscription.customer);
|
const customerId = getCustomerId(subscription.customer);
|
||||||
const paymentPlanId = getPaymentPlanIdByPriceId(
|
const paymentPlanId = getPaymentPlanIdByPaymentProcessorPlanId(
|
||||||
getSubscriptionPriceId(subscription),
|
getSubscriptionPriceId(subscription),
|
||||||
);
|
);
|
||||||
|
|
||||||
const user = await updateUserSubscriptionDetails(
|
const user = await updateUserSubscription(
|
||||||
{ customerId, paymentPlanId, subscriptionStatus },
|
{ paymentProcessorUserId: customerId, paymentPlanId, subscriptionStatus },
|
||||||
prismaUserDelegate,
|
prismaUserDelegate,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -190,14 +186,31 @@ async function handleCustomerSubscriptionUpdated(
|
|||||||
function getOpenSaasSubscriptionStatus(
|
function getOpenSaasSubscriptionStatus(
|
||||||
subscription: Stripe.Subscription,
|
subscription: Stripe.Subscription,
|
||||||
): SubscriptionStatus | undefined {
|
): SubscriptionStatus | undefined {
|
||||||
if (subscription.status === SubscriptionStatus.Active) {
|
const stripeToOpenSaasSubscriptionStatusMap: Record<
|
||||||
if (subscription.cancel_at_period_end) {
|
Stripe.Subscription.Status,
|
||||||
return SubscriptionStatus.CancelAtPeriodEnd;
|
SubscriptionStatus | undefined
|
||||||
}
|
> = {
|
||||||
return SubscriptionStatus.Active;
|
trialing: SubscriptionStatus.Active,
|
||||||
} else if (subscription.status === SubscriptionStatus.PastDue) {
|
active: SubscriptionStatus.Active,
|
||||||
return SubscriptionStatus.PastDue;
|
past_due: SubscriptionStatus.PastDue,
|
||||||
|
canceled: SubscriptionStatus.Deleted,
|
||||||
|
unpaid: SubscriptionStatus.Deleted,
|
||||||
|
incomplete_expired: SubscriptionStatus.Deleted,
|
||||||
|
paused: undefined,
|
||||||
|
incomplete: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const subscriptionStauts =
|
||||||
|
stripeToOpenSaasSubscriptionStatusMap[subscription.status];
|
||||||
|
|
||||||
|
if (
|
||||||
|
subscriptionStauts === SubscriptionStatus.Active &&
|
||||||
|
subscription.cancel_at_period_end
|
||||||
|
) {
|
||||||
|
return SubscriptionStatus.CancelAtPeriodEnd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return subscriptionStauts;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSubscriptionPriceId(
|
function getSubscriptionPriceId(
|
||||||
@@ -222,8 +235,11 @@ async function handleCustomerSubscriptionDeleted(
|
|||||||
const subscription = event.data.object;
|
const subscription = event.data.object;
|
||||||
const customerId = getCustomerId(subscription.customer);
|
const customerId = getCustomerId(subscription.customer);
|
||||||
|
|
||||||
await updateUserSubscriptionDetails(
|
await updateUserSubscription(
|
||||||
{ customerId, subscriptionStatus: SubscriptionStatus.Deleted },
|
{
|
||||||
|
paymentProcessorUserId: customerId,
|
||||||
|
subscriptionStatus: SubscriptionStatus.Deleted,
|
||||||
|
},
|
||||||
prismaUserDelegate,
|
prismaUserDelegate,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -249,15 +265,3 @@ function getInvoicePaidAtDate(invoice: Stripe.Invoice): Date {
|
|||||||
// so we multiply by 1000 to convert to milliseconds.
|
// so we multiply by 1000 to convert to milliseconds.
|
||||||
return new Date(invoice.status_transitions.paid_at * 1000);
|
return new Date(invoice.status_transitions.paid_at * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPaymentPlanIdByPriceId(priceId: string): PaymentPlanId {
|
|
||||||
const paymentPlanId = Object.values(PaymentPlanId).find(
|
|
||||||
(paymentPlanId) =>
|
|
||||||
paymentPlans[paymentPlanId].getPaymentProcessorPlanId() === priceId,
|
|
||||||
);
|
|
||||||
if (!paymentPlanId) {
|
|
||||||
throw new Error(`No payment plan with Stripe price id ${priceId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return paymentPlanId;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { PrismaClient } from "@prisma/client";
|
|
||||||
import Stripe from "stripe";
|
|
||||||
import { User } from "wasp/entities";
|
import { User } from "wasp/entities";
|
||||||
import type { SubscriptionStatus } from "../plans";
|
import { PrismaClient } from "wasp/server";
|
||||||
import { PaymentPlanId } from "../plans";
|
import { PaymentPlanId, SubscriptionStatus } from "./plans";
|
||||||
|
|
||||||
export async function fetchUserPaymentProcessorUserId(
|
export async function fetchUserPaymentProcessorUserId(
|
||||||
userId: User["id"],
|
userId: User["id"],
|
||||||
@@ -39,50 +37,25 @@ export function updateUserPaymentProcessorUserId(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateUserOneTimePaymentDetailsArgs {
|
interface UpdateUserSubscriptionArgs {
|
||||||
customerId: Stripe.Customer["id"];
|
paymentProcessorUserId: NonNullable<User["paymentProcessorUserId"]>;
|
||||||
datePaid: Date;
|
|
||||||
numOfCreditsPurchased: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateUserOneTimePaymentDetails(
|
|
||||||
{
|
|
||||||
customerId,
|
|
||||||
datePaid,
|
|
||||||
numOfCreditsPurchased,
|
|
||||||
}: UpdateUserOneTimePaymentDetailsArgs,
|
|
||||||
userDelegate: PrismaClient["user"],
|
|
||||||
): Promise<User> {
|
|
||||||
return userDelegate.update({
|
|
||||||
where: {
|
|
||||||
paymentProcessorUserId: customerId,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
datePaid,
|
|
||||||
credits: { increment: numOfCreditsPurchased },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UpdateUserSubscriptionDetailsArgs {
|
|
||||||
customerId: Stripe.Customer["id"];
|
|
||||||
subscriptionStatus: SubscriptionStatus;
|
subscriptionStatus: SubscriptionStatus;
|
||||||
datePaid?: Date;
|
|
||||||
paymentPlanId?: PaymentPlanId;
|
paymentPlanId?: PaymentPlanId;
|
||||||
|
datePaid?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateUserSubscriptionDetails(
|
export function updateUserSubscription(
|
||||||
{
|
{
|
||||||
customerId,
|
paymentProcessorUserId,
|
||||||
paymentPlanId,
|
paymentPlanId,
|
||||||
subscriptionStatus,
|
subscriptionStatus,
|
||||||
datePaid,
|
datePaid,
|
||||||
}: UpdateUserSubscriptionDetailsArgs,
|
}: UpdateUserSubscriptionArgs,
|
||||||
userDelegate: PrismaClient["user"],
|
userDelegate: PrismaClient["user"],
|
||||||
): Promise<User> {
|
) {
|
||||||
return userDelegate.update({
|
return userDelegate.update({
|
||||||
where: {
|
where: {
|
||||||
paymentProcessorUserId: customerId,
|
paymentProcessorUserId,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
subscriptionPlan: paymentPlanId,
|
subscriptionPlan: paymentPlanId,
|
||||||
@@ -91,3 +64,28 @@ export function updateUserSubscriptionDetails(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface UpdateUserCreditsArgs {
|
||||||
|
paymentProcessorUserId: NonNullable<User["paymentProcessorUserId"]>;
|
||||||
|
numOfCreditsPurchased: number;
|
||||||
|
datePaid: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateUserCredits(
|
||||||
|
{
|
||||||
|
paymentProcessorUserId,
|
||||||
|
numOfCreditsPurchased,
|
||||||
|
datePaid,
|
||||||
|
}: UpdateUserCreditsArgs,
|
||||||
|
userDelegate: PrismaClient["user"],
|
||||||
|
) {
|
||||||
|
return userDelegate.update({
|
||||||
|
where: {
|
||||||
|
paymentProcessorUserId,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
credits: { increment: numOfCreditsPurchased },
|
||||||
|
datePaid,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user