-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Improve Stripe payment provider logic #505
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
base: main
Are you sure you want to change the base?
Changes from all commits
809e982
3df17a2
d0a2e15
1560833
fec25ab
05b0fad
91e2f0d
de70c30
0cbe24c
5dce0da
4d6a64f
1541759
dddc128
d7b5f8f
b764193
d6a608a
346da0f
4d9fb58
8371bd4
e5caba8
2e5ea07
f09865f
b4f80d3
2c82386
34209ae
f288f9b
0bd0b73
375cf1a
4673ec6
fb0ff94
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,20 @@ | ||
--- template/app/src/payment/stripe/paymentDetails.ts | ||
+++ opensaas-sh/app/src/payment/stripe/paymentDetails.ts | ||
@@ -20,10 +20,10 @@ | ||
) => { | ||
@@ -19,7 +19,7 @@ | ||
) { | ||
return userDelegate.update({ | ||
where: { | ||
- paymentProcessorUserId: userStripeId, | ||
+ stripeId: userStripeId, | ||
- paymentProcessorUserId: customerId, | ||
+ stripeId: customerId, | ||
}, | ||
data: { | ||
- paymentProcessorUserId: userStripeId, | ||
+ stripeId: userStripeId, | ||
subscriptionPlan, | ||
subscriptionStatus, | ||
datePaid, | ||
@@ -46,7 +46,7 @@ | ||
) { | ||
return userDelegate.update({ | ||
where: { | ||
- paymentProcessorUserId: customerId, | ||
+ stripeId: customerId, | ||
}, | ||
data: { | ||
subscriptionPlan: paymentPlanId, |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,11 @@ | ||
--- template/app/src/payment/stripe/paymentProcessor.ts | ||
+++ opensaas-sh/app/src/payment/stripe/paymentProcessor.ts | ||
@@ -32,7 +32,7 @@ | ||
@@ -29,7 +29,7 @@ | ||
id: userId, | ||
}, | ||
data: { | ||
- paymentProcessorUserId: customer.id, | ||
+ stripeId: customer.id, | ||
}, | ||
}); | ||
if (!stripeSession.url) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,64 +1,67 @@ | ||
import type { StripeMode } from "./paymentProcessor"; | ||
|
||
import Stripe from "stripe"; | ||
import { stripe } from "./stripeClient"; | ||
import { stripeClient } from "./stripeClient"; | ||
|
||
// WASP_WEB_CLIENT_URL will be set up by Wasp when deploying to production: https://wasp.sh/docs/deploying | ||
const DOMAIN = process.env.WASP_WEB_CLIENT_URL || "http://localhost:3000"; | ||
/** | ||
* Returns a Stripe customer for the given User email, creating a customer if none exist. | ||
* Implements email uniqueness logic since Stripe doesn't enforce unique emails. | ||
*/ | ||
export async function ensureStripeCustomer( | ||
userEmail: string, | ||
): Promise<Stripe.Customer> { | ||
const stripeCustomers = await stripeClient.customers.list({ | ||
email: userEmail, | ||
}); | ||
|
||
export async function fetchStripeCustomer(customerEmail: string) { | ||
let customer: Stripe.Customer; | ||
try { | ||
const stripeCustomers = await stripe.customers.list({ | ||
email: customerEmail, | ||
if (stripeCustomers.data.length === 0) { | ||
console.log("Creating a new Stripe customer"); | ||
return stripeClient.customers.create({ | ||
email: userEmail, | ||
}); | ||
if (!stripeCustomers.data.length) { | ||
console.log("creating customer"); | ||
customer = await stripe.customers.create({ | ||
email: customerEmail, | ||
}); | ||
} else { | ||
console.log("using existing customer"); | ||
customer = stripeCustomers.data[0]; | ||
} | ||
return customer; | ||
} catch (error) { | ||
console.error(error); | ||
throw error; | ||
} else { | ||
console.log("Using an existing Stripe customer"); | ||
return stripeCustomers.data[0]; | ||
} | ||
} | ||
|
||
interface CreateStripeCheckoutSessionParams { | ||
priceId: string; | ||
customerId: string; | ||
mode: StripeMode; | ||
priceId: Stripe.Price["id"]; | ||
customerId: Stripe.Customer["id"]; | ||
mode: Stripe.Checkout.Session.Mode; | ||
} | ||
|
||
export async function createStripeCheckoutSession({ | ||
priceId, | ||
customerId, | ||
mode, | ||
}: CreateStripeCheckoutSessionParams) { | ||
try { | ||
return await stripe.checkout.sessions.create({ | ||
line_items: [ | ||
{ | ||
price: priceId, | ||
quantity: 1, | ||
}, | ||
], | ||
mode: mode, | ||
success_url: `${DOMAIN}/checkout?status=success`, | ||
cancel_url: `${DOMAIN}/checkout?status=canceled`, | ||
automatic_tax: { enabled: true }, | ||
allow_promotion_codes: true, | ||
customer_update: { | ||
address: "auto", | ||
}: CreateStripeCheckoutSessionParams): Promise<Stripe.Checkout.Session> { | ||
// WASP_WEB_CLIENT_URL will be set up by Wasp when deploying to production: https://wasp.sh/docs/deploying | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reminder to create a issue about solving the env vars situation on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this one already exists here #483 |
||
const CLIENT_BASE_URL = | ||
process.env.WASP_WEB_CLIENT_URL || "http://localhost:3000"; | ||
|
||
return await stripeClient.checkout.sessions.create({ | ||
customer: customerId, | ||
line_items: [ | ||
{ | ||
price: priceId, | ||
quantity: 1, | ||
}, | ||
customer: customerId, | ||
}); | ||
} catch (error) { | ||
console.error(error); | ||
throw error; | ||
} | ||
], | ||
mode, | ||
success_url: `${CLIENT_BASE_URL}/checkout?status=success`, | ||
cancel_url: `${CLIENT_BASE_URL}/checkout?status=canceled`, | ||
automatic_tax: { enabled: true }, | ||
allow_promotion_codes: true, | ||
customer_update: { | ||
address: "auto", | ||
}, | ||
// Stripe automatically creates invoices for subscriptions. | ||
// For one-time payments, we must enable them manually. | ||
// However, enabling invoices for subscriptions will throw an error. | ||
invoice_creation: | ||
mode === "payment" | ||
? { | ||
enabled: true, | ||
} | ||
: undefined, | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,36 +1,57 @@ | ||
import { PrismaClient } from "@prisma/client"; | ||
import Stripe from "stripe"; | ||
import type { SubscriptionStatus } from "../plans"; | ||
import { PaymentPlanId } from "../plans"; | ||
|
||
export const updateUserStripePaymentDetails = async ( | ||
interface UpdateUserStripeOneTimePaymentDetails { | ||
customerId: Stripe.Customer["id"]; | ||
datePaid: Date; | ||
numOfCreditsPurchased: number; | ||
} | ||
|
||
export function updateUserStripeOneTimePaymentDetails( | ||
{ | ||
userStripeId, | ||
subscriptionPlan, | ||
subscriptionStatus, | ||
customerId, | ||
datePaid, | ||
numOfCreditsPurchased, | ||
}: { | ||
userStripeId: string; | ||
subscriptionPlan?: PaymentPlanId; | ||
subscriptionStatus?: SubscriptionStatus; | ||
numOfCreditsPurchased?: number; | ||
datePaid?: Date; | ||
}, | ||
}: UpdateUserStripeOneTimePaymentDetails, | ||
userDelegate: PrismaClient["user"], | ||
) { | ||
return userDelegate.update({ | ||
where: { | ||
paymentProcessorUserId: customerId, | ||
}, | ||
data: { | ||
datePaid, | ||
credits: { increment: numOfCreditsPurchased }, | ||
}, | ||
}); | ||
} | ||
|
||
interface UpdateUserStripeSubscriptionDetails { | ||
customerId: Stripe.Customer["id"]; | ||
datePaid?: Date; | ||
subscriptionStatus: SubscriptionStatus; | ||
paymentPlanId?: PaymentPlanId; | ||
} | ||
|
||
export function updateUserStripeSubscriptionDetails( | ||
{ | ||
customerId, | ||
paymentPlanId, | ||
subscriptionStatus, | ||
datePaid, | ||
}: UpdateUserStripeSubscriptionDetails, | ||
userDelegate: PrismaClient["user"], | ||
) => { | ||
) { | ||
return userDelegate.update({ | ||
where: { | ||
paymentProcessorUserId: userStripeId, | ||
paymentProcessorUserId: customerId, | ||
}, | ||
data: { | ||
paymentProcessorUserId: userStripeId, | ||
subscriptionPlan, | ||
subscriptionPlan: paymentPlanId, | ||
subscriptionStatus, | ||
datePaid, | ||
credits: | ||
numOfCreditsPurchased !== undefined | ||
? { increment: numOfCreditsPurchased } | ||
: undefined, | ||
}, | ||
}); | ||
}; | ||
} |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Allows us to do e.g.
paymentPlans.credits10.effect.amount
which would throw before.