-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Polar integration #461
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?
Polar integration #461
Changes from 99 commits
728bcd0
61f5e73
d3f3ed6
51d05ca
f07fa91
2b42cc6
a0276a3
0ebd0a3
e84670d
54292a7
156823c
2841121
825eb66
6ad783b
a97b79f
eb68932
fca11f9
c71f29a
732b9ed
2410e01
f65cc67
df213c9
5f71cac
882df67
7d0eb4e
3639c0d
2f5748c
e5a63de
fda6a57
add2038
254aae4
780d24c
568c38b
721fd6f
31ad46c
f5892aa
32c8934
2b3195c
2a64914
9658034
9b3a26e
033bdfe
dbcfa08
e81b2db
20e95f8
f31f176
3003595
6cce3fe
ce97f73
27b8ea7
6471324
1b72606
b0eaf88
3defeb9
2741ea2
543fa92
ee65871
31a3b0b
42cbf32
adde113
29ec825
ee820b3
4b1ce80
cbf0e62
064abc1
5a46b2f
ac66c28
745516e
9a09fed
33ab2a3
37ce885
a9f51e3
17042bf
9ce5cfc
8c9e8e5
c110026
4b343ff
d5d97af
99bda63
22e1c6d
d8b9732
a1671c8
c2886ae
9287ec3
4c69038
11a7093
8c2254a
456ef49
cae2372
3593df3
18f3479
d2f2dd0
dbd67b3
6d85122
5384775
f4b965c
45a9194
fa246d4
5527860
e6c11fe
70d3220
893288d
a4fff20
d183139
bbbf9a2
9287cd9
671dcf0
8114346
9bd7278
2c0fbd0
cf10272
b8ca44a
9ea1170
b1fcd54
80ffbe1
a285d07
ce7c90a
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 | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -199,6 +199,20 @@ With the webhook url ready, go to your [Lemon Squeezy Webhooks Dashboard](https: | |||||||||
| - subscription_cancelled | ||||||||||
| - click `save` | ||||||||||
|
|
||||||||||
| ### Setting up your Production Polar Webhook | ||||||||||
|
|
||||||||||
| To set up your Polar webhook, you'll need the URL of your newly deployed server + `/payments-webhook`, e.g. `https://open-saas-wasp-sh-server.fly.dev/payments-webhook`. | ||||||||||
|
|
||||||||||
| With the webhook URL ready, go to `Settings` > `Webhooks` in your [Polar Dashboard](https://polar.sh/dashboard): | ||||||||||
| - click the `Add Endpoint` button. | ||||||||||
| - paste the webhook forwarding URL in the `URL` field. | ||||||||||
| - set the `Format` to `"Raw"` | ||||||||||
| - select at least the following events to be sent: | ||||||||||
| - order.paid | ||||||||||
| - subscription.updated | ||||||||||
|
Comment on lines
+211
to
+212
Member
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.
Suggested change
|
||||||||||
| - click `Save` | ||||||||||
| - copy the generated webhook secret (a long, random string starting with `polar_whs_`). | ||||||||||
| - add this signing secret to your server's production environment variables under `POLAR_WEBHOOK_SECRET=` | ||||||||||
|
|
||||||||||
| ## Deploying your Blog | ||||||||||
|
|
||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -16,43 +16,67 @@ import variantId from '@assets/lemon-squeezy/variant-id.png'; | |||||||||
| import subscriptionVariantIds from '@assets/lemon-squeezy/subscription-variant-ids.png'; | ||||||||||
| import ngrok from '@assets/lemon-squeezy/ngrok.png'; | ||||||||||
| import storeId from '@assets/lemon-squeezy/store-id.png'; | ||||||||||
| import addPolarProduct from '@assets/polar/add-product.png'; | ||||||||||
| import addPolarToken from '@assets/polar/add-token.png'; | ||||||||||
| import polarUserTable from '@assets/polar/user-table.png'; | ||||||||||
| import polarWebhookLogs from '@assets/polar/webhook-log.png'; | ||||||||||
|
|
||||||||||
| This guide will show you how to set up Payments for testing and local development with the following payment processors: | ||||||||||
| - Stripe | ||||||||||
| - Lemon Squeezy | ||||||||||
| - Polar | ||||||||||
|
|
||||||||||
| :::note[Which should I choose?] | ||||||||||
| Stripe is the industry standard, is more configurable, and has cheaper fees. | ||||||||||
| Lemon Squeezy acts a [Merchant of Record](https://www.lemonsqueezy.com/reporting/merchant-of-record). This means they take care of paying taxes in multiple countries for you, but charge higher fees per transaction. | ||||||||||
| Lemon Squeezy acts a [Merchant of Record](https://www.lemonsqueezy.com/reporting/merchant-of-record). This means they take care of paying taxes in multiple countries for you, but charge higher fees per transaction. | ||||||||||
| Polar is an open-source [Merchant of Record](https://docs.polar.sh/merchant-of-record/introduction#merchant-of-record) designed specifically for developers. | ||||||||||
| ::: | ||||||||||
|
|
||||||||||
| ## Important First Steps | ||||||||||
|
|
||||||||||
| First, go to `/src/payment/paymentProcessor.ts` and choose which payment processor you'd like to use, e.g. Stripe or Lemon Squeezy: | ||||||||||
| First, go to `/src/payment/paymentProcessor.ts` and choose which payment processor you'd like to use, e.g. Stripe, Lemon Squeezy, or Polar: | ||||||||||
|
|
||||||||||
| ```ts title="src/payment/paymentProcessor.ts" ins={5, 7} | ||||||||||
| ```ts title="src/payment/paymentProcessor.ts" ins={6, 8, 10} | ||||||||||
| import { stripePaymentProcessor } from './stripe/paymentProcessor'; | ||||||||||
| import { lemonSqueezyPaymentProcessor } from './lemonSqueezy/paymentProcessor'; | ||||||||||
| import { polarPaymentProcessor } from './polar/paymentProcessor'; | ||||||||||
| //... | ||||||||||
|
|
||||||||||
| export const paymentProcessor: PaymentProcessor = stripePaymentProcessor; | ||||||||||
| // or... | ||||||||||
| export const paymentProcessor: PaymentProcessor = lemonSqueezyPaymentProcessor; | ||||||||||
| // or... | ||||||||||
| export const paymentProcessor: PaymentProcessor = polarPaymentProcessor; | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| At this point, you can delete: | ||||||||||
| - the unused payment processor code within the `/src/payment/<unused-provider>` directory, | ||||||||||
| - any unused environment variables from `.env.server` (they will be prefixed with the name of the provider your are not using): | ||||||||||
| - e.g. `STRIPE_API_KEY`, `STRIPE_CUSTOMER_PORTAL_URL`, `LEMONSQUEEZY_API_KEY`, `LEMONSQUEEZY_WEBHOOK_SECRET` | ||||||||||
| - e.g. `STRIPE_API_KEY`, `LEMONSQUEEZY_API_KEY`, `POLAR_ACCESS_TOKEN` | ||||||||||
| - Make sure to also uninstall the unused dependencies: | ||||||||||
| - `npm uninstall @lemonsqueezy/lemonsqueezy.js` | ||||||||||
| - or | ||||||||||
| - `npm uninstall stripe` | ||||||||||
| - If using Stripe | ||||||||||
| ```sh | ||||||||||
| npm uninstall @lemonsqueezy/lemonsqueezy.js @polar-sh/sdk | ||||||||||
| ``` | ||||||||||
| - If using Lemon Squeezy | ||||||||||
| ```sh | ||||||||||
| npm uninstall stripe @polar-sh/sdk | ||||||||||
| ``` | ||||||||||
| - If using Polar | ||||||||||
| ```sh | ||||||||||
| npm uninstall @lemonsqueezy/lemonsqueezy.js stripe | ||||||||||
| ``` | ||||||||||
| - Remove any unused fields from the `User` model in the `schema.prisma` file if they exist: | ||||||||||
| - e.g. `lemonSqueezyCustomerPortalUrl` | ||||||||||
|
|
||||||||||
| Now your code is ready to go with your preferred payment processor and it's time to configure your payment processor's API keys, products, and other settings. | ||||||||||
|
|
||||||||||
| Follow the steps for your selected processor: | ||||||||||
|
|
||||||||||
| - [Stripe](#stripe) | ||||||||||
| - [Lemon Squeezy](#lemon-squeezy) | ||||||||||
| - [Polar](#polar) | ||||||||||
|
|
||||||||||
| ## Stripe | ||||||||||
|
|
||||||||||
| First, you'll need to create a Stripe account. You can do that [here](https://dashboard.stripe.com/register). | ||||||||||
|
|
@@ -306,6 +330,128 @@ Now go to your [Lemon Squeezy Webhooks Dashboard](https://app.lemonsqueezy.com/s | |||||||||
|
|
||||||||||
| You're now ready to start consuming Lemon Squeezy webhook events in local development. | ||||||||||
|
|
||||||||||
| ## Polar | ||||||||||
|
Member
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. Another idea to think about: splitting this page into 3 pages (one for each payment providers since it's getting long) - I don't feel too strongly about this - I'll let you decide if that makes sense right row or maybe with the next provider. |
||||||||||
|
|
||||||||||
| First, make sure you've defined your payment processor in `src/payment/paymentProcessor.ts`, as described in the [important first steps](#important-first-steps). | ||||||||||
|
|
||||||||||
| Next, you'll need to create a Polar account. You can do that [here](https://polar.sh). | ||||||||||
|
|
||||||||||
| :::tip[Star our Repo on GitHub! 🌟] | ||||||||||
| We've packed in a ton of features and love into this SaaS starter, and offer it all to you for free! | ||||||||||
|
|
||||||||||
| If you're finding this template and its guides useful, consider giving us [a star on GitHub](https://github.com/wasp-lang/wasp) | ||||||||||
| ::: | ||||||||||
|
Comment on lines
+336
to
+340
Member
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. Out of scope for this PR - but this could be a separate component that we can reuse. |
||||||||||
|
|
||||||||||
| ### Enable Sandbox Mode | ||||||||||
|
|
||||||||||
| For local development and testing, you'll want to use Polar's sandbox mode. The Polar sandbox is fully isolated, so there are separate URLs for the [sandbox dashboard](https://sandbox.polar.sh/dashboard) and [live dashboard](https://polar.sh/dashboard), each with independent products, sales, access tokens, etc. | ||||||||||
|
|
||||||||||
| Add the following to your `.env.server` file: | ||||||||||
|
|
||||||||||
| ```ts title=".env.server" | ||||||||||
| POLAR_SANDBOX_MODE=true | ||||||||||
| ``` | ||||||||||
|
Comment on lines
+346
to
+350
Member
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. Nitpick: We have this in the example env server - do we use that context to say "Make sure that the |
||||||||||
|
|
||||||||||
| :::note[Going Live] | ||||||||||
| When you're ready to go live, change this to `false`. | ||||||||||
| ::: | ||||||||||
|
|
||||||||||
| ### Get your Polar API Access Token | ||||||||||
|
Member
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 need to emphasize here a lot more that people need two accounts:
And then create the development token in Sandbox env. I got confused by this, so I think our users will get confused as well. |
||||||||||
|
|
||||||||||
| Once you've created your account, you'll need to get your API access token. You can do that by navigating to the `Developers` section under your dashboard settings and creating a new personal access token. | ||||||||||
|
Member
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.
Suggested change
Since there is a list after the sentence.
Member
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.
Suggested change
I wasn't looking for
|
||||||||||
|
|
||||||||||
| - Click on the `New Token` button to create a new token | ||||||||||
| - Give your token a name (e.g., "Open SaaS Development") | ||||||||||
|
Member
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.
Member
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.
Member
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. Also, I just realized I created the token in the production env and not sandbox! This means it wasn't really clear I need to go to |
||||||||||
| - Copy the generated token and paste it in your `.env.server` file, e.g. `POLAR_ACCESS_TOKEN=polar_oat_...` | ||||||||||
|
|
||||||||||
| ### Create Test Products | ||||||||||
|
|
||||||||||
| To create test products in Polar, go to the `Products` section in your Polar dashboard. | ||||||||||
|
Member
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.
Suggested change
|
||||||||||
|
|
||||||||||
| - Click on the `New Product` button to create a new product | ||||||||||
| - Fill in the product details: | ||||||||||
| - **Name**: e.g., "Hobby Plan", "Pro Plan", or "10 Credits" | ||||||||||
| - **Description**: Brief description of what the product includes | ||||||||||
| - **Pricing**: Select "Monthly" or "Yearly" for recurring plans or "One-time purchase" for credits, then configure the desired pricing type | ||||||||||
| - Configure any remaining optional fields, e.g. **Media**, **Checkout Fields**, **Metadata**, etc | ||||||||||
| - Click `Create Product` | ||||||||||
|
|
||||||||||
| After creating each product, you'll need to copy the Product ID from the product page and add it to your `.env.server` file: | ||||||||||
|
|
||||||||||
| ```ini title=".env.server" | ||||||||||
| PAYMENTS_HOBBY_SUBSCRIPTION_PLAN_ID=<your-hobby-product-id> | ||||||||||
| PAYMENTS_PRO_SUBSCRIPTION_PLAN_ID=<your-pro-product-id> | ||||||||||
| PAYMENTS_CREDITS_10_PLAN_ID=<your-credits-product-id> | ||||||||||
| ``` | ||||||||||
|
Comment on lines
+376
to
+382
Member
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. I feel like the previous section said "just create some products" and now we are specific three product we need to fill it. I'd maybe clarify it somewhere "this is what Open Saas comes with out of the box, you can of course change the plans like you want, make sure to modify the code ..." If we are not doing it for other payment providers as well, I think we should, so it might be better handled with a separate issue. If we are doing it for others, let's just do it here as well. |
||||||||||
|
|
||||||||||
| :::note[Product ID Location] | ||||||||||
| The Product ID can be acquired on the product page in your Polar dashboard. Tap the `⠇` icon in the top-right corner, then select `Copy Product ID`. | ||||||||||
| ::: | ||||||||||
|
|
||||||||||
| ### Create and Use the Polar Webhook in Local Development | ||||||||||
|
|
||||||||||
| Polar notifies your Wasp app through a webhook (for example, when a payment succeeds). During development, you need to expose your locally running Wasp server (started with `wasp start`) to the internet. Because the Wasp server runs on port 3001 by default, run `ngrok` on the same port. `ngrok` will generate a public URL that you can give to Polar. | ||||||||||
|
Member
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.
Suggested change
Felt weirdly formulated "Becuase Wasp runs on 3001, that's why you need ngrok" but we wanted to say something else "you can use ngrok to expose port 3001". |
||||||||||
|
|
||||||||||
| To do this, first make sure you have installed [`ngrok`](https://ngrok.com/docs/getting-started/). | ||||||||||
|
|
||||||||||
| Once `ngrok` is installed and your Wasp app is running, run: | ||||||||||
|
|
||||||||||
| ```sh | ||||||||||
| ngrok http 3001 | ||||||||||
| ``` | ||||||||||
|
Comment on lines
+394
to
+398
Member
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. I'm not sure if we have the same requirements for other providers? Do we need a "common" section with these kind of tips? If this is only for Polr, let's keep it like this. Also, one completely free alternative to Ngrok is https://theboroer.github.io/localtunnel-www/
Member
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. One extra tip for Ngrok, if users go to https://dashboard.ngrok.com/domains they can get their fixed domain which they can use like: And it will always be |
||||||||||
|
|
||||||||||
| <Image src={ngrok} alt="ngrok" loading="lazy" /> | ||||||||||
|
|
||||||||||
| `ngrok` will display a forwarding address. Copy this address and append `/payments-webhook` to it. This path is already configured in `main.wasp` under the `api paymentsWebhook` definition. It should look something like this: | ||||||||||
|
|
||||||||||
| ```sh title="Callback URL" | ||||||||||
| https://89e5-2003-c7-153c-72a5-f837.ngrok-free.app/payments-webhook | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| Next, configure the webhook in your Polar dashboard: | ||||||||||
|
|
||||||||||
| - Go to `Webhooks` page under `Settings` | ||||||||||
|
Member
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.
Suggested change
Nitpick |
||||||||||
| - Click `Add Endpoint` to create a new endpoint | ||||||||||
| - In the URL field, paste your `ngrok` forwarding address with `/payments-webhook` appended (for example: `https://abc123.ngrok-free.app/payments-webhook`). | ||||||||||
| - Set the `Format` to `"Raw"` | ||||||||||
| - Select the following events to listen for: | ||||||||||
| - `order.paid` | ||||||||||
| - `subscription.updated` | ||||||||||
| - Click `Save` | ||||||||||
| - Copy the generated webhook secret and add it to your `.env.server` file: | ||||||||||
|
|
||||||||||
| ```ini title=".env.server" | ||||||||||
| POLAR_WEBHOOK_SECRET=polar_whs_... | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| ### Testing Payments via the Local Application | ||||||||||
Genyus marked this conversation as resolved.
Show resolved
Hide resolved
Member
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 are missing instructions for |
||||||||||
|
|
||||||||||
| Make sure that your **`ngrok` tunnel is running** and that the webhook is configured as described above. | ||||||||||
|
|
||||||||||
| You can then test the payment flow: | ||||||||||
|
|
||||||||||
| - Click a Buy button for any product on the homepage | ||||||||||
| - You should be redirected to Polar's checkout page | ||||||||||
| - Fill in the checkout form with [test payment information](https://docs.polar.sh/integrate/sandbox#testing-payments) | ||||||||||
| - Complete the payment | ||||||||||
| - You should be redirected back to your success page | ||||||||||
|
|
||||||||||
| Check the Polar dashboard for webhook event logs and verify the user's subscription status in your database: | ||||||||||
|
|
||||||||||
| <Image src={polarWebhookLogs} alt="How to check webhook logs in Polar dashboard" loading="lazy" /> | ||||||||||
|
|
||||||||||
| ```sh | ||||||||||
| wasp db studio | ||||||||||
| ``` | ||||||||||
|
Comment on lines
+440
to
+442
Member
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 block just appears without any introduction e.g. check your DB by running... |
||||||||||
|
|
||||||||||
| Navigate to `localhost:5555` and check the `User` table to confirm the `subscriptionStatus` is `active` for the user who made the purchase. | ||||||||||
|
|
||||||||||
| <Image src={polarUserTable} alt="User table showing updated user" loading="lazy" /> | ||||||||||
|
|
||||||||||
| :::note[Polar Test Cards] | ||||||||||
| Polar uses Stripe's test card numbers for development. Check their [testing documentation](https://docs.stripe.com/testing#cards) for further information. | ||||||||||
| ::: | ||||||||||
|
|
||||||||||
| ## Deploying | ||||||||||
|
|
||||||||||
| Once you deploy your app, you can follow the same steps, just make sure that you are no longer in test mode within the Stripe or Lemon Squeezy Dashboards. After you've repeated the steps in live mode, add the new API keys and price/variant IDs to your environment variables in your deployed environment. | ||||||||||
| Once you deploy your app, you can follow the same steps, just make sure that you are no longer in test/sandbox mode within the Stripe, Lemon Squeezy, or Polar dashboards. After you've repeated the steps in live mode, add the new API keys and price/variant IDs to your environment variables in your deployed environment. | ||||||||||
|
Member
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.
Suggested change
Makes it more maintainable for the future |
||||||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -186,19 +186,19 @@ For development purposes, Wasp provides a `Dummy` email sender which Open SaaS c | |||||
|
|
||||||
| We will explain more about these auth methods, and how to properly integrate them into your app, in the [Authentication Guide](/guides/authentication/). | ||||||
|
|
||||||
| ### Subscription Payments with Stripe or Lemon Squeezy | ||||||
| ### Subscription Payments with Stripe, Lemon Squeezy or Polar | ||||||
|
|
||||||
| No SaaS is complete without payments, specifically subscription payments. That's why this template comes with a fully functional Stripe or Lemon Squeezy integration. | ||||||
| No SaaS is complete without payments, specifically subscription payments. That's why this template comes with a fully functional Stripe, Lemon Squeezy or Polar integration. | ||||||
|
Member
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.
Suggested change
They are all included by default. |
||||||
|
|
||||||
| Let's take a quick look at how payments are handled in this template. | ||||||
|
|
||||||
| 1. a user clicks the `BUY` button and a **Checkout session** is created on the server | ||||||
| 2. the user is redirected to the Checkout page where they enter their payment info | ||||||
| 3. the user is redirected back to the app and the Checkout session is completed | ||||||
| 4. Stripe / Lemon Squeezy sends a webhook event to the server with the payment info | ||||||
| 4. Stripe / Lemon Squeezy / Polar 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 payment processor you choose (Stripe or Lemon Squeezy) and its related functions can be found at `src/payment/paymentProcessor.ts`. The `Payment Processor` object holds the logic for creating checkout sessions, webhooks, etc. | ||||||
| The payment processor you choose (Stripe, Lemon Squeezy or Polar) and its related functions can be found at `src/payment/paymentProcessor.ts`. The `Payment Processor` object holds the logic for creating checkout sessions, webhooks, etc. | ||||||
|
Member
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.
Suggested change
Since |
||||||
|
|
||||||
| The logic for creating the Checkout session is defined in the `src/payment/operation.ts` file. [Actions](https://wasp.sh/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: | ||||||
|
|
||||||
|
|
@@ -226,7 +226,7 @@ const handleBuyClick = async (paymentPlanId) => { | |||||
| }; | ||||||
| ``` | ||||||
|
|
||||||
| The webhook handler is defined in the `src/payment/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 | ||||||
| The webhook handler is defined in the `src/payment/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 the payment processor. | ||||||
|
|
||||||
| ```js title="main.wasp" | ||||||
| api paymentsWebhook { | ||||||
|
|
@@ -277,7 +277,7 @@ For more info on integrating Plausible or Google Analytics, check out the [Analy | |||||
| When you first start your Open SaaS app straight from the template, it will run, but many of the services won't work because they lack your own API keys. Here are list of services that need your API keys to work properly: | ||||||
|
|
||||||
| - Auth Methods (Google, GitHub) | ||||||
| - Stripe or Lemon Squeezy | ||||||
| - Stripe, Lemon Squeezy or Polar | ||||||
| - OpenAI (Chat GPT API) | ||||||
| - Email Sending (Sendgrid) -- you must set this up if you're using the `email` Auth method | ||||||
| - Analytics (Plausible or Google Analytics) | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,15 @@ LEMONSQUEEZY_STORE_ID=012345 | |
| # define your own webhook secret when creating a new webhook on https://app.lemonsqueezy.com/settings/webhooks | ||
| LEMONSQUEEZY_WEBHOOK_SECRET=my-webhook-secret | ||
|
|
||
| # After creating an organization, you can find your organization id in the organization settings https://sandbox.polar.sh/dashboard/[your org slug]/settings | ||
| POLAR_ORGANIZATION_ID=00000000-0000-0000-0000-000000000000 | ||
| # Generate a token at https://sandbox.polar.sh/dashboard/[your org slug]/settings | ||
| POLAR_ACCESS_TOKEN=polar_oat_... | ||
| # Define your own webhook secret when creating a new webhook at https://sandbox.polar.sh/dashboard/[your org slug]/settings/webhooks | ||
| POLAR_WEBHOOK_SECRET=polar_whs_... | ||
| # For production, set this to false, then generate a new organization and products from the live dashboard | ||
| POLAR_SANDBOX_MODE=true | ||
|
|
||
| # If using Stripe, go to https://dashboard.stripe.com/test/products and click on + Add Product | ||
| # If using Lemon Squeezy, go to https://app.lemonsqueezy.com/products and create new products and variants | ||
|
Member
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 are missing Polar instructions here. |
||
| PAYMENTS_HOBBY_SUBSCRIPTION_PLAN_ID=012345 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,8 +8,11 @@ import { | |
| getSources, | ||
| } from "./providers/plausibleAnalyticsUtils"; | ||
| // import { getDailyPageViews, getSources } from './providers/googleAnalyticsUtils'; | ||
| import { OrderStatus } from "@polar-sh/sdk/models/components/orderstatus.js"; | ||
| import { paymentProcessor } from "../payment/paymentProcessor"; | ||
| import { SubscriptionStatus } from "../payment/plans"; | ||
| import { polarClient } from "../payment/polar/polarClient"; | ||
| import { assertUnreachable } from "../shared/utils"; | ||
|
|
||
| export type DailyStatsProps = { | ||
| dailyStats?: DailyStats; | ||
|
|
@@ -60,10 +63,11 @@ export const calculateDailyStats: DailyStatsJob<never, void> = async ( | |
| case "lemonsqueezy": | ||
| totalRevenue = await fetchTotalLemonSqueezyRevenue(); | ||
| break; | ||
| case "polar": | ||
| totalRevenue = await fetchTotalPolarRevenue(); | ||
| break; | ||
| default: | ||
| throw new Error( | ||
| `Unsupported payment processor: ${paymentProcessor.id}`, | ||
| ); | ||
| assertUnreachable(paymentProcessor.id); | ||
| } | ||
|
|
||
| const { totalViews, prevDayViewsChangePercent } = await getDailyPageViews(); | ||
|
|
@@ -211,3 +215,24 @@ async function fetchTotalLemonSqueezyRevenue() { | |
| throw error; | ||
| } | ||
| } | ||
|
|
||
| async function fetchTotalPolarRevenue(): Promise<number> { | ||
|
Member
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. Can we create an issue to maybe move the
Member
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. All the stats code being in the same file made it annoying to delete all the Stripe and LemonSqueezy stuff from this file |
||
| let totalRevenue = 0; | ||
|
|
||
| const result = await polarClient.orders.list({ | ||
FranjoMindek marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| limit: 100, | ||
| }); | ||
|
|
||
| for await (const page of result) { | ||
| const orders = page.result.items || []; | ||
|
|
||
| for (const order of orders) { | ||
|
Member
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. I'd maybe break this fn into two maybe, it feels very low level right now: async function getAllPolarOrders(): Promise<Order[]> {
const result = await polarClient.orders.list({
limit: 100,
});
const pages = [];
for await (const page of result) {
pages.push(page);
}
return pages.flatMap(page => page.result.items || []);
}
function sumPaidOrderRevenue(orders: Order[]): number {
const totalRevenue = orders
.filter(order => order.status === OrderStatus.Paid && order.totalAmount > 0)
.map(order => order.totalAmount)
.reduce((sum, amount) => sum + amount, 0);
return totalRevenue / 100;
}
async function fetchTotalPolarRevenue(): Promise<number> {
const orders = await getAllPolarOrders();
return sumPaidOrderRevenue(orders);
}This all makes more sense if it lives in a separate file as I mentioned above, now it will overcrowd the file. I'm not sure if it makes sense to do it now. Your call. |
||
| if (order.status === OrderStatus.Paid && order.totalAmount > 0) { | ||
| totalRevenue += order.totalAmount; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Revenue is in cents so we convert to dollars | ||
| return totalRevenue / 100; | ||
| } | ||



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.
It feels weird that every bullet ends with a period but start with a lowercase letter.