-
-
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?
Conversation
|
||
## What's inside? | ||
|
||
The template itself is built on top of some very powerful tools and frameworks, including: |
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.
This hasn't been run in a while so more changes pop up.
effect: { kind: 'credits', amount: 10 }, | ||
}, | ||
}; | ||
} as const satisfies Record<PaymentPlanId, PaymentPlan>; |
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.
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.
Pull Request Overview
This PR simplifies and hardens the Stripe integration by moving from custom Zod parsing to Stripe’s typed events, shifting one-time payments to be invoice-driven, and generating customer-portal sessions per user instead of relying on a static URL.
- Switch webhook processing to use Stripe typed events; remove custom webhookPayload parsing.
- Enable invoices for one-time payments and handle credits via invoice.paid.
- Introduce stripeClient with explicit API version; generate customer-portal session URLs server-side.
Reviewed Changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 7 comments.
Show a summary per file
File | Description |
---|---|
template/app/src/shared/utils.ts | Minor cleanup: unused param naming for assertUnreachable. |
template/app/src/payment/stripe/webhookPayload.ts | Removed custom Zod-based webhook parsing in favor of Stripe types. |
template/app/src/payment/stripe/webhook.ts | Rewrote webhook handling to use Stripe typed events; added invoice-driven flows and helper extractors. |
template/app/src/payment/stripe/stripeClient.ts | Introduced stripeClient with centralized API version constant and docs. |
template/app/src/payment/stripe/paymentProcessor.ts | Generate billing portal sessions per user; ensure Stripe customer creation; minor API refinements. |
template/app/src/payment/stripe/paymentDetails.ts | Split update into one-time vs subscription paths; updated shapes and fields. |
template/app/src/payment/stripe/checkoutUtils.ts | Ensure/create Stripe customer; enable invoice creation for one-time checkouts; align with stripeClient. |
template/app/src/payment/plans.ts | Strengthened typing for paymentPlans using satisfies and as const. |
template/app/src/analytics/stats.ts | Switched to stripeClient usage for listing balance transactions. |
template/app/.env.server.example | Removed obsolete STRIPE_CUSTOMER_PORTAL_URL. |
opensaas-sh/blog/src/content/docs/guides/payments-integration.mdx | Updated docs to reflect customer-portal and customer creation changes. |
opensaas-sh/blog/public/llms.txt | Added a new docs link. |
opensaas-sh/blog/public/llms-full.txt | Multiple doc updates; links and guidance refreshed. |
.github/workflows/e2e-tests.yml | Removed obsolete STRIPE_CUSTOMER_PORTAL_URL from CI env. |
opensaas-sh/app_diff/* | App-diff adjustments mirroring template changes (not core template code). |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
opensaas-sh/blog/src/content/docs/guides/payments-integration.mdx
Outdated
Show resolved
Hide resolved
* 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> { |
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.
Old name fetchStripeCustomer
lied, because it also created a customer if none existed.
priceId: string; | ||
customerId: string; | ||
mode: StripeMode; | ||
priceId: Stripe.Price['id']; |
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.
I try to extract types from stripe
as much as I can.
If I miss it anywhere it is a mistake.
# Conflicts: # template/app/src/payment/stripe/checkoutUtils.ts
# Conflicts: # opensaas-sh/app_diff/src/analytics/stats.ts.diff # opensaas-sh/app_diff/src/payment/stripe/paymentDetails.ts.diff # opensaas-sh/app_diff/src/payment/stripe/paymentProcessor.ts.diff # template/app/src/analytics/stats.ts # template/app/src/payment/stripe/checkoutUtils.ts # template/app/src/payment/stripe/paymentDetails.ts # template/app/src/payment/stripe/paymentProcessor.ts # template/app/src/payment/stripe/stripeClient.ts # template/app/src/payment/stripe/webhook.ts # template/app/src/payment/stripe/webhookPayload.ts # template/app/src/shared/utils.ts
…e-payment-provider # Conflicts: # opensaas-sh/blog/src/content/docs/blog/2024-12-10-turboreel-os-ai-video-generator-built-with-open-saas.mdx
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.
Pull Request Overview
Copilot reviewed 16 out of 17 changed files in this pull request and generated 1 comment.
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
"e2e:playwright": "DEBUG=pw:webserver npx playwright test", | ||
"local:e2e:cleanup-stripe": "PID=$(ps -ef | grep 'stripe listen' | grep -v grep | awk '{print $2}') || true && kill -9 $PID || true", | ||
"local:e2e:playwright:ui": "npx playwright test --ui", | ||
"local:e2e:playwright:ui": "SKIP_EMAIL_VERIFICATION_IN_DEV=true npx playwright test --ui", |
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.
Explore: how come we don't set this by default inside of wasp-app-runner
if we depend on it?
Why did I add this? Did I do something wrong?
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 comment
The reason will be displayed to describe this comment to others. Learn more.
Reminder to create a issue about solving the env vars situation on open-saas
.
Use Wass's defineEnvValidationSchema
.
Centralize them in a single place.
Description
Fixes #487
The PR idea was to research
stripe
payment provider to see If we could optimize our currentstripe
integration.For implementation, my main idea was to simplify webhook handling logic and reduce the number of events we have to handle. Secondary idea was to make it easier for users to work with our
stripe
integration.The simplification is done through:
Stripe.Invoice
for one-time payments (this is a opt-in setting).invoice.paid
event. So we can stop handlingcheckout.session.completed
event, which we handled to update ONLY one-time payments related data.stripe
customer portalstripe
customer postal to be generated by API on per-custmer basis rather than using public customer portal linklemon squeezy
, and it makes sense for already logged-in users (you are automatically logged in into your payment provider customer profile, instead of having to login via magic link)webhookPayload.ts
which included payload parsing schema. This is because we already verify the payload viastripe
's signature (constructEvent
func), and the type system allows us to navigate the data.Other changes:
try/catch
blocksconsole.log(error); throw error;
situations