-
-
Notifications
You must be signed in to change notification settings - Fork 928
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
Organize payments vertically #225
Changes from all commits
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 |
---|---|---|
|
@@ -61,16 +61,16 @@ If you are using a version of the OpenSaaS template with Wasp `v0.11.x` or below | |
├── src/ # Your code goes here. | ||
│ ├── client/ # Your client code (React) goes here. | ||
│ ├── server/ # Your server code (NodeJS) goes here. | ||
│ ├── shared/ # Your shared (runtime independent) code goes here. | ||
│ ├── auth/ # All auth-related pages/components and logic. | ||
│ ├── file-upload/ # Logic for uploading files to S3. | ||
│ └── .waspignore | ||
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. We dropped .waspignore? 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. waspignore (Was Pig Nore) is in the main |
||
│ └── payment/ # Logic for handling Stripe payments and webhooks. | ||
├── .env.server # Dev environment variables for your server code. | ||
├── .env.client # Dev environment variables for your client code. | ||
├── .prettierrc # Prettier configuration. | ||
├── tailwind.config.js # TailwindCSS configuration. | ||
├── package.json | ||
├── package-lock.json | ||
|
||
└── .wasproot | ||
``` | ||
|
||
|
@@ -86,12 +86,12 @@ This template at its core is a Wasp project, where [Wasp](https://wasp-lang.dev) | |
|
||
In this template, we've already defined a number of things in the `main.wasp` config file, including: | ||
|
||
- Auth | ||
- Routes and Pages | ||
- Prisma Database Models | ||
- Operations (data read and write functions) | ||
- Background Jobs | ||
- Email Sending | ||
- [Auth](https://wasp-lang.dev/docs/auth/overview) | ||
- [Routes and Pages](https://wasp-lang.dev/docs/tutorial/pages) | ||
- [Prisma Database Models](https://wasp-lang.dev/docs/data-model/entities) | ||
- [Operations (data read and write functions)](https://wasp-lang.dev/docs/data-model/operations/overview) | ||
- [Background Jobs](https://wasp-lang.dev/docs/advanced/jobs) | ||
- [Email Sending](https://wasp-lang.dev/docs/advanced/email) | ||
|
||
By defining these things in the config file, Wasp continuously handles the boilerplate necessary with putting all these features together. You just need to focus on the business logic of your app. | ||
|
||
|
@@ -127,13 +127,11 @@ All you have to do is define your server-side functions in the `main.wasp` file, | |
|
||
```sh | ||
└── server | ||
├── payments # Payments utility functions. | ||
├── scripts # Scripts to run via Wasp, e.g. database seeding. | ||
├── webhooks # The webhook handler for Stripe. | ||
├── workers # Functions that run in the background as Wasp Jobs, e.g. daily stats calculation. | ||
├── actions.ts # Your server-side write/mutation functions. | ||
├── queries.ts # Your server-side read functions. | ||
└── types.ts | ||
└── utils.ts | ||
``` | ||
|
||
## Main Features | ||
|
@@ -199,17 +197,17 @@ Let's take a quick look at how payments are handled in this template. | |
4. Stripe sends a webhook event to the server with the payment info | ||
5. The app server's **webhook handler** handles the event and updates the user's subscription status | ||
|
||
The logic for creating the Stripe Checkout session is defined in the `src/server/actions.ts` file. [Actions](https://wasp-lang.dev/docs/data-model/operations/actions) are your server-side functions that are used to write or update data to the database. Once they're defined in the `main.wasp` file, you can easily call them on the client-side: | ||
The logic for creating the Stripe Checkout session is defined in the `src/payment/operation.ts` file. [Actions](https://wasp-lang.dev/docs/data-model/operations/actions) are a type of Wasp Operation, specifically your server-side functions that are used to **write** or **update** data to the database. Once they're defined in the `main.wasp` file, you can easily call them on the client-side: | ||
|
||
a) define the action in the `main.wasp` file | ||
```js title="main.wasp" | ||
action generateStripeCheckoutSession { | ||
fn: import { generateStripeCheckoutSession } from "@src/server/actions.js", | ||
fn: import { generateStripeCheckoutSession } from "@src/payment/operations", | ||
entities: [User] | ||
} | ||
``` | ||
|
||
b) implement the action in the `src/server/actions.ts` file | ||
b) implement the action in the `src/payment/operations` file | ||
```js title="src/server/actions.ts" | ||
export const generateStripeCheckoutSession = async (paymentPlanId, context) => { | ||
//... | ||
|
@@ -225,11 +223,11 @@ const handleBuyClick = async (paymentPlanId) => { | |
}; | ||
``` | ||
|
||
The webhook handler is defined in the `src/server/webhooks/stripe.ts` file. Unlike Actions and Queries in Wasp which are only to be used internally, we define the webhook handler in the `main.wasp` file as an API endpoint in order to expose it externally to Stripe | ||
The webhook handler is defined in the `src/payment/stripe/webhook.ts` file. Unlike Actions and Queries in Wasp which are only to be used internally, we define the webhook handler in the `main.wasp` file as an API endpoint in order to expose it externally to Stripe | ||
|
||
```js title="main.wasp" | ||
api stripeWebhook { | ||
fn: import { stripeWebhook } from "@src/server/webhooks/stripe.js", | ||
fn: import { stripeWebhook } from "@src/payment/stripe/webhook", | ||
httpRoute: (POST, "/stripe-webhook") | ||
entities: [User], | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { type GenerateStripeCheckoutSession } from 'wasp/server/operations'; | ||
import { HttpError } from 'wasp/server'; | ||
import { PaymentPlanId, paymentPlans, type PaymentPlanEffect } from '../payment/plans'; | ||
import { fetchStripeCustomer, createStripeCheckoutSession, type StripeMode } from './stripe/checkoutUtils'; | ||
|
||
export type StripeCheckoutSession = { | ||
sessionUrl: string | null; | ||
sessionId: string; | ||
}; | ||
|
||
export const generateStripeCheckoutSession: GenerateStripeCheckoutSession< | ||
PaymentPlanId, | ||
StripeCheckoutSession | ||
> = async (paymentPlanId, context) => { | ||
if (!context.user) { | ||
throw new HttpError(401); | ||
} | ||
const userEmail = context.user.email; | ||
if (!userEmail) { | ||
throw new HttpError( | ||
403, | ||
'User needs an email to make a payment. If using the usernameAndPassword Auth method, switch to an Auth method that provides an email.' | ||
); | ||
} | ||
|
||
const paymentPlan = paymentPlans[paymentPlanId]; | ||
const customer = await fetchStripeCustomer(userEmail); | ||
const session = await createStripeCheckoutSession({ | ||
priceId: paymentPlan.getStripePriceId(), | ||
customerId: customer.id, | ||
mode: paymentPlanEffectToStripeMode(paymentPlan.effect), | ||
}); | ||
|
||
await context.entities.User.update({ | ||
where: { | ||
id: context.user.id, | ||
}, | ||
data: { | ||
checkoutSessionId: session.id, | ||
stripeId: customer.id, | ||
}, | ||
}); | ||
|
||
return { | ||
sessionUrl: session.url, | ||
sessionId: session.id, | ||
}; | ||
}; | ||
|
||
function paymentPlanEffectToStripeMode(planEffect: PaymentPlanEffect): StripeMode { | ||
const effectToMode: Record<PaymentPlanEffect['kind'], StripeMode> = { | ||
subscription: 'subscription', | ||
credits: 'payment', | ||
}; | ||
return effectToMode[planEffect.kind]; | ||
} |
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.
There is nothing left in shared?
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.
nope. nada. The leftovers got moved to
common
I believe