Credit Plans
@kapa.ai - my LLM agent has to do some work on credit plans. Specifically it needs to implement a system where an external backend (not part of Wasp's front and backend) will be interfacing it - the container processes audio files. We're not to interested in that as the moment, but I'd appreciate you point out generating an LLM optimized set of content that outlines the key files and how the current credit system is implemented.
17 Replies
Hi @Gareth_Simpson. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:
----------------------
Based on the available knowledge sources, here’s an outline of how the current credit system is implemented in Wasp, particularly as seen in the Open SaaS template. This should help your LLM agent understand the key files and flow, especially for integrating with an external backend.
Key Concepts and Flow
1. Stripe Product Setup
- Credits are purchased via Stripe as a one-time product (not a subscription).
- The Stripe product ID for credits is stored in an environment variable, e.g.,
The main logic for crediting users is in the webhook handler, e.g.,
Actions like
CREDITS_PRICE_ID
in your .env
file.
2. Webhook Handling
- When a payment is successful, a Stripe webhook is triggered.
- The webhook handler checks if the purchased product matches the credits product ID.
- If it matches, the user’s credits are incremented in the database.
3. Relevant Files and Code Locations
- Stripe Webhook Handler:The main logic for crediting users is in the webhook handler, e.g.,
app/src/server/webhooks/stripe.ts
(see Open SaaS example).
- Environment Variables:.env
file contains CREDITS_PRICE_ID
for the Stripe product.
- Wasp Actions:Actions like
stripeCreditsPayment
are defined in main.wasp
and implemented in server-side JS/TS files (e.g., @server/actions.js
).
4. Example Logic for Crediting Users
This code checks the product ID and increments the user's credits accordingly.
5. Client Integration
- The client triggers the payment flow and imports the correct action (e.g., stripeCreditsPayment
).
- After payment, the webhook updates the user’s credits, which can then be reflected in the UI.
Integration with External Backend
- The credit system is managed within Wasp’s backend (and database), but you can expose this via an API route if your external backend needs to check or update credits. This is a recommended approach for secure, limited access (API route docs).
Summary
- Key files: Stripe webhook handler (stripe.ts
), environment config (.env
), Wasp actions (main.wasp
and server actions).
- Flow: Stripe payment → webhook → check product ID → increment credits in DB.
- External integration: Use API routes to allow your external backend to interact with the credit system.
For more details and code examples, see the Open SaaS template’s Stripe webhook handler and related Discord discussions for troubleshooting and setup tips Payment for Credits / Credit Balance System.
If you need a more detailed breakdown or have a specific integration scenario, let me know!
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------
Sorry @Gareth_Simpson, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist:
Perhaps try again in 15mins?@kapa.ai - is there any documentation online avalible about the credit system? I'm pretty sure the code relies on metadata.priceId being present in the payment intent - is setting this a Stripe metadata UI issue? There is a UI for key value store in the metadata.
Let me check the knowledge base for you again. One moment! :books:
----------------------
Based on the knowledge sources, there is no dedicated, official documentation specifically for the credit system in Wasp or Open SaaS. Most guidance comes from Discord discussions and code comments, rather than a formal guide.
Regarding your question about
The available sources do not explicitly confirm that the credit system relies on
- There is no official documentation for the credit system—most info is in Discord threads and code comments. - The standard implementation checks
metadata.priceId
in the payment intent:The available sources do not explicitly confirm that the credit system relies on
metadata.priceId
being present in the payment intent. Instead, the code examples and discussions show that the system typically checks the line_items.data[0].price.id
from the Stripe webhook event to determine which product was purchased and whether to increment credits. Here’s a representative code snippet:
This logic does not reference metadata.priceId
, but rather the price.id
from the line items in the event payload. There is no mention in the sources of needing to set metadata.priceId
via the Stripe UI or any key-value store in the metadata for the credit system to function. The focus is on matching the product/price ID from the Stripe event to the environment variable in your Wasp app, not on Stripe metadata fieldsPayment for Credits / Credit Balance System.
If you are seeing code that uses metadata.priceId
, it may be a custom modification or a different implementation not covered in the provided sources. The standard approach, as discussed in the community, is to use the price.id
from the event data.
If you need to set metadata in Stripe, there is indeed a UI for adding key-value pairs to products or payment intents, but this is not referenced as a requirement for the Wasp/Open SaaS credit system in the available sources.
In summary:- There is no official documentation for the credit system—most info is in Discord threads and code comments. - The standard implementation checks
line_items.data[0].price.id
, not metadata.priceId
.
- Setting metadata in Stripe is possible, but not required for the default credit system as described in the sources.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------
Sorry @Gareth_Simpson, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist:
Perhaps try again in 15mins?@kapa.ai - this is the suggestion my LLM coding agent has suggest:
<suggestion>
What Should You Do?
A. Update Your Webhook Handler to Use Line Items for Credits
In your webhook handler (handleCheckoutSessionCompleted or handlePaymentIntentSucceeded), you should:
Retrieve the line items from the session or event.
Check if the price ID matches your credits product.
Increment the user's credits if it matches.
Example (Pseudocode):
Apply to checkoutUtil...
}
This is the approach most Wasp/OpenSaaS users follow.
B. Why Your Current Approach Might Not Work Reliably
If you rely solely on metadata.priceId, it will only be present if you set it in the payment intent (which you do for one-time payments, but not for subscriptions).
For maximum compatibility and clarity, use the line items array for all product/price checks.
C. You Do Not Need to Set Metadata in the Stripe UI
All required metadata is set in your backend code when creating the checkout session.
The Stripe Dashboard UI’s metadata fields are not used by the webhook handler.
Recommended Next Steps
Update your webhook handler to check line_items.data[0].price.id for credit purchases.
Test a credit purchase and confirm the webhook increments credits.
(Optional) Keep the metadata.priceId logic for extra safety, but the line items check should be primary.
No need to set any metadata in the Stripe UI.
Summary Table
Approach Works for Credits? Works for Subscriptions? Official/Community Standard?
metadata.priceId Yes (if set) No Not standard
line_items[].price.id Yes Yes Yes (recommended)
If you want, I can provide a concrete code patch for your webhook handler to implement this logic. Would you like that?
</suggestion>
Can you give me your oppionon?
Let me check the knowledge base for you again. One moment! :books:
----------------------
Your LLM agent’s suggestion aligns well with the practices described in the Wasp/Open SaaS community discussions and code examples.
A. Using line_items[].price.id is the recommended approach.
The knowledge sources show that the standard way to determine which product was purchased (and thus whether to increment credits) is by checking the
As your agent notes,
The knowledge sources confirm that all necessary product identification is handled in code, not via manual metadata in the Stripe Dashboard UI. Summary:
- Your agent’s advice to use
The knowledge sources show that the standard way to determine which product was purchased (and thus whether to increment credits) is by checking the
line_items.data[0].price.id
in the Stripe webhook event. This is the method used in the Open SaaS template and is what community members recommend. There is no mention of relying on metadata.priceId
in the official or community guidance for the credit system. This approach works for both one-time payments and subscriptions, making it more robust and future-proofPayment for Credits / Credit Balance System.
B. Relying on metadata.priceId is not standard and may be unreliable.As your agent notes,
metadata.priceId
would only be present if you explicitly set it when creating the payment intent, and this is not the default or recommended pattern in Wasp/Open SaaS. The webhook handler does not expect this field, and the Stripe Dashboard’s metadata UI is not used in this flow.
C. No need to set metadata in the Stripe UI.The knowledge sources confirm that all necessary product identification is handled in code, not via manual metadata in the Stripe Dashboard UI. Summary:
- Your agent’s advice to use
line_items[].price.id
is correct and matches community best practices.
- There is no need to set or rely on metadata.priceId
for the credit system to work.
- You do not need to use the Stripe Dashboard’s metadata UI for this purpose.
If you want to see a concrete code example, the community has provided one that checks line_items.data[0].price.id
and increments credits accordingly, which you can adapt for your webhook handlerPayment for Credits / Credit Balance System.
If you need a code patch, let me know!
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------
Sorry @Gareth_Simpson, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist:
Perhaps try again in 15mins?@kapa.ai - can you review an LLM handover note and give me some feeback?
Let me check the knowledge base for you again. One moment! :books:
Wohooo @Gareth_Simpson, you just became a Waspeteer level 4!
----------------------
Yes, I can review an LLM handover note and provide feedback, as long as the review is related to Wasp or its ecosystem. Please share the handover note you’d like me to review.
If your request is not about Wasp, please note that I am tuned to answer only Wasp-related questions.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------
Sorry @Gareth_Simpson, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist:
Perhaps try again in 15mins?
@kapa.ai - did you review that txt file?
Let me check the knowledge base for you again. One moment! :books:
----------------------
I'm sorry, but I do not see a .txt file or its contents in the conversation or knowledge sources provided. If you would like me to review a specific handover note or .txt file, please paste its contents here or provide a direct reference to it. Without the actual content, I cannot perform a review or provide feedback.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------
Sorry @Gareth_Simpson, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist:
Perhaps try again in 15mins?
@kapa.ai
Let me check the knowledge base for you again. One moment! :books:
Sorry @Gareth_Simpson, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist:
Perhaps try again in 15mins?
@kapa.ai I'm trying to do some logging on the Stripe webhooks and some of there console.log's appearing in the server. Here’s what we now know:
The stripeWebhook handler is called for manual requests (e.g., via curl), and the log appears.
The handler is not called for real Stripe webhooks, even though Stripe receives a 200 response.
This strongly points to a body parsing or signature verification issue—specifically, Stripe requires the raw request body for signature verification, and if the body is parsed as JSON (or otherwise altered) before reaching your handler, signature verification will fail or the handler may not run as expected.
What do you think?
Let me check the knowledge base for you again. One moment! :books:
----------------------
Your analysis is correct and matches known issues discussed in the Wasp community. Stripe webhooks require the raw request body for signature verification. If the request body is parsed (e.g., as JSON) before the handler runs, signature verification will fail, and the handler may not behave as expected—even if Stripe receives a 200 response.
Wasp uses Express middleware by default, including
- Your diagnosis is correct. - Use
express.json
, which parses the body as JSON. To handle Stripe webhooks properly, you need to override this middleware for your webhook route and use express.raw
instead. This ensures the raw body is available for Stripe’s signature verification.
Wasp supports per-API middleware customization. Here’s how you can set it up:
main.wasp
src/apis.js
This setup ensures the raw body is passed to your handler, resolving the issue you described. For more details, see the Wasp middleware customization documentation.
In summary:- Your diagnosis is correct. - Use
express.raw
for the Stripe webhook route to avoid body parsing issues.
- Wasp supports this via per-API middleware configuration.
Let me know if you need a more detailed example or further troubleshooting steps!
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------
Sorry @Gareth_Simpson, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist:
Perhaps try again in 15mins?@kapa.ai - my stripe webhook handler isn't returning my userStripeId - its for the credit system
Let me check the knowledge base for you again. One moment! :books:
----------------------
Based on the knowledge sources, a common reason your Stripe webhook handler isn't returning the
userStripeId
(for the credit system) is that the handler may not be correctly extracting the Stripe customer ID from the event payload, or the event type being handled does not contain the expected data.
From the Open SaaS template and community discussions, the typical approach is to extract the Stripe customer ID from the session or event object, for example:
This line is usually placed after retrieving the session object from the event. If userStripeId
is undefined, it could be due to:
- The event type being handled (e.g., checkout.session.completed
or invoice.paid
) does not include the customer
field as expected.
- The session object is not being correctly retrieved from the event.
- The webhook is not listening to the correct event types, or the event payload structure has changed.
Community members have debugged similar issues by adding console.log(event.type)
and logging the session/customer data to ensure the correct values are being accessed. For example, one suggestion was to move the assignment of userStripeId
directly after retrieving the session:
If you are using a different event type or a newer Stripe API version, the payload structure may differ, so double-check the event object for the presence of the customer
field. Also, ensure that your webhook is correctly configured to listen for the relevant events and that the customer is being set during the checkout session creation.
If you continue to have issues, try logging the entire event and session objects to see what data is available, as suggested in the community:
can you put aIf after these checksconsole.log(event.type)
statement on line 88 and share what it says when you checkout? Also, where is youruserStripeId
being defined? are you still listening for 'checkout.session.completed' events?
...
Try this. Copy this lineuserStripeId = session.customer as string;
and paste it right under line 87. Then see what happens
I am trying to understand the subsciptionStatus logic in stripe.ts > Answers Part 3
userStripeId
is still not being returned, it's likely due to the event payload not containing the expected data, or the handler not accessing it correctly. Double-check your event type, payload, and where you extract the customer ID.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------
Sorry @Gareth_Simpson, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist:
Perhaps try again in 15mins?