Skip to content

Conversation

FranjoMindek
Copy link
Contributor

@FranjoMindek FranjoMindek commented Sep 17, 2025

Description

Fixes #487

The PR idea was to research stripe payment provider to see If we could optimize our current stripe 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:

  • We now generate Stripe.Invoice for one-time payments (this is a opt-in setting).
    • This allows us to handle both one-time payments and subscriptions under the invoice.paid event. So we can stop handling checkout.session.completed event, which we handled to update ONLY one-time payments related data.
    • As a nice side-effect users can how see their one-time payment invoices in their stripe customer portal
  • I've refactored stripe customer postal to be generated by API on per-custmer basis rather than using public customer portal link
    • This is how we do it for lemon 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)
  • I've deleted webhookPayload.ts which included payload parsing schema. This is because we already verify the payload via stripe's signature (constructEvent func), and the type system allows us to navigate the data.

Other changes:

  • Naming changes
  • I tried to remove all uselss try/catch blocks
    • This is mostly console.log(error); throw error; situations


## What's inside?

The template itself is built on top of some very powerful tools and frameworks, including:
Copy link
Contributor Author

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>;
Copy link
Contributor Author

@FranjoMindek FranjoMindek Sep 17, 2025

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.

Copy link

@Copilot Copilot AI left a 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.

* 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> {
Copy link
Contributor Author

@FranjoMindek FranjoMindek Sep 21, 2025

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'];
Copy link
Contributor Author

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
@FranjoMindek FranjoMindek requested a review from Copilot October 1, 2025 09:33
Copy link

@Copilot Copilot AI left a 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.

@FranjoMindek FranjoMindek marked this pull request as ready for review October 1, 2025 09:49
"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",
Copy link
Contributor Author

@FranjoMindek FranjoMindek Oct 13, 2025

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
Copy link
Contributor Author

@FranjoMindek FranjoMindek Oct 13, 2025

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.

@FranjoMindek FranjoMindek requested a review from infomiho October 13, 2025 08:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Review and improve the Stripe logic

1 participant