Skip to content

Stripe and payment processing improvements #493

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

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

FranjoMindek
Copy link
Contributor

@FranjoMindek FranjoMindek commented Aug 11, 2025

Fixes #487

The idea of this PR is to improve the current Stripe payment processing system.
However some of the changes touch a wider concepts so it also changes files outside of payments/stripe.

Main behavioral change is that we now generate Stripe.Invoice for one-time payments.
We didn't do that before (in older Stripe API versions it wasn't possible), so we had to handle one-time payments separately from subscriptions. Now this is unified under invoice.paid event.

Another side-effect of above is that users who only use one-time payments also have invoices.
So it felt wrong that accessing the Stripe billing portal was only possible to users with active subscriptions.
The billing portal allows you to:

  1. See past invoices
  2. Update payment methods
  3. Change subscription status

So it should be accessible to everyone.
Changes to AccountPage.tsx are related to that change.

I've also made it so we have create customer specific billing portals.
This can be seen in stripe/paymentProcessor.ts's fetchCustomerPortalUrl.
The change here is that users are automatically logged in into their Stripe customer account when they open the link:

Screen.Recording.2025-08-11.at.13.38.02.mov

One bigger change is that I removed all parsing inside of stripe/webhookPayload.ts. This is because Stripe already verifies the event data itself. This is enforced by its SDK. No need to parse twice. We also lost type information by using custom parsing interfaces.

The rest is not really behavioral changes.
Just improvements to old code.

There is also some noise because we updated diffs for some old files nobody updated the diff for.

Changes:

fetchStripeCustomer

  • Bad name / bad behavior:
    • The function can also create a new customer which isn't implied by name
  • Bad logic?:
    • email is not a unique field for Stripe.Customer
    • Currently we only allow 1 Stripe.Customer per 1 User.email but that is enforced by our own application logic
    • We already store Stripe.Customer.id under User.paymentProcessorUserId
    • We could easily search Stripe.Customer by its id, which is unique, rather by its email
  • Changes:
    • I've renamed it to ensureStripeCustomer

stripePaymentProcessor.createCheckoutSession

  • Bad logic:
    • We:
      • fetch/create a Stripe.Customer
      • create a Stripe.Checkout.Session
      • connect User to Stripe.Customer
    • If we are creating a Stripe.Customer (rather than fetching), and we error on creating a Stripe.Checkout.Session, we won’t connect User to the newly created Stripe.Customer
    • However due to our application logic, if we retry, we will re-fetch the same Stripe.Customer since we fetch it by email
  • Changes:
    • Switched the order to connect Stripe.Customer to User before we create a Stripe.Checkout.Session

paymentDetails.ts

  • Bad name:
    • This module is solely responsible for updating User entity, let's mention the user in the name
  • Changes:
    • paymentDetails.ts -> userPaymentDetails.ts

stripePaymentProcessor.fetchCustomerPortalUrl

  • Bad usage?
    • We use a billing portal URL which isn't a secret. We set is as STRIPE_CUSTOMER_PORTAL_URL and then we fetch it though server on the client.
      • The users still have to login to their Stripe customer account to use it even though they are logged in the application.
      • We can easily use the link directly on the client
    • I think we meant to use stripeClient.billingPortal.sessions.create, which creates a billing portal URL for that specific customer
      • The user is already logged in to their Stripe customer account inside of that link
  • Changes:
    • We create a customer specific billing portal through stripeClient now, rather than using predefined URL
    • TODO: if we go this route
      • update the docs to reflect the new billing portal
      • remove STRIPE_CUSTOMER_PORTAL_URL

stripe/webhookPayload.ts

  • Unnecessary parsing:
    • We parse the Stripe payload with our custom zod schemas.
    • We don't have to do that since we already validate the data via stripeClient.webhooks.constructEvent.
    • Plus we lose proper type information through our zod schemas.
  • Changes:
    I deleted the Stripe webhookPayload.ts.

Payment details auditing problems

  • We always overwrite user payment details, instead we should save each one of them as a separate record

webhook.ts

  • Old way:
    • Stripe now supports creating invoices for one-time payments
  • Changes:
    • I've changed checkout session creation logic to create invoices for one-time payments too
    • That means we now handle all of the initial payment effects inside of the invoice.paid event
    • TODO: Someone with access to Stripe will need to update our webhook settings, we no longer need the checkout.session.completed events

CheckoutPage.tsx

  • Bad name:
    • We use it to say success or failure, not actual checkout stuff
  • Changes:
    • CheckoutPage.tsx -> CheckoutResultPage.tsx

AccountPage.tsx

  • Problem:
    • Only users with subscription can access the billing portal, instead anyone who has User.paymentProcessorId should be
  • Changes:
    • Billing portal button renders for anyone who can generate a valid billing portal URL, which is decided by that provider's specific requirements

Other than mentioned changes I've also clean up the types and general logic to be clearer.

@FranjoMindek FranjoMindek self-assigned this Aug 11, 2025
@FranjoMindek FranjoMindek mentioned this pull request Aug 11, 2025
3 tasks
import type { SubscriptionStatus } from '../plans';
import { PaymentPlanId } from '../plans';

export async function updateUserStripePaymentDetails(
Copy link
Contributor Author

@FranjoMindek FranjoMindek Aug 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just old updateUserStripePaymentDetails from stripe/paymentDetails, but I've split it up into two separate functions which correspond to two different actions it does.

@@ -1,7 +1,7 @@
import Stripe from 'stripe';
import { requireNodeEnvVar } from '../../server/utils';

export const stripe = new Stripe(requireNodeEnvVar('STRIPE_API_KEY'), {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having it named stripe makes it easy to confuse with the Stripe namespace often imported everywhere.
And it is a client.

@FranjoMindek FranjoMindek changed the title Strapi and payment processing improvements Stripe and payment processing improvements Aug 11, 2025
@FranjoMindek FranjoMindek requested a review from vincanger August 12, 2025 12:25
Copy link
Collaborator

@vincanger vincanger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool. Looks great. Just a couple comments + we need to update docs.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What env vars were changed here? I want to make sure we're still using the old Product IDs and vars from Stripe.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any idea why this changed? I assume you didn't actually deploy anything.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

Genyus added a commit to Genyus/open-saas that referenced this pull request Aug 23, 2025
- Align with changes coming in wasp-lang#493
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
2 participants