mirror of
https://github.com/stripe/ai.git
synced 2026-09-14 18:39:59 +08:00
Add Agent Plugins 1.0 provider package (#487)
* bootstrapping agent-plugins provider * nesting into a folder in case we start hosting more than one plugin in the future * Align Agent Plugin paths with nested layout Document the multi-plugin provider convention and keep sync automation pointed at the relocated portable package. Co-authored-by: Cursor <cursoragent@cursor.com> Committed-By-Agent: cursor * changing installation instructions --------- Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -9,6 +9,7 @@ on:
|
||||
- 'providers/codex/plugin/skills/**'
|
||||
- 'providers/cursor/plugin/skills/**'
|
||||
- 'providers/grok/plugin/skills/**'
|
||||
- 'providers/agent-plugins/plugin/skills/**'
|
||||
|
||||
jobs:
|
||||
block:
|
||||
|
||||
@@ -53,7 +53,8 @@ jobs:
|
||||
providers/claude/plugin/.claude-plugin/plugin.json \
|
||||
providers/codex/plugin/.codex-plugin/plugin.json \
|
||||
providers/cursor/plugin/.cursor-plugin/plugin.json \
|
||||
providers/grok/plugin/.grok-plugin/plugin.json
|
||||
providers/grok/plugin/.grok-plugin/plugin.json \
|
||||
providers/agent-plugins/plugin/plugin.json
|
||||
|
||||
# Skip if nothing changed
|
||||
if git diff --staged --quiet; then
|
||||
|
||||
@@ -55,6 +55,13 @@ Run this command in your project:
|
||||
grok plugin install stripe --trust
|
||||
```
|
||||
|
||||
### Agent Plugins
|
||||
|
||||
Installation methods currently vary by client for the new [Agent Plugins](https://agent-plugins.org/) standard, but you can point your client to our package via:
|
||||
- Git URL: `https://github.com/stripe/ai`
|
||||
- Subdirectory: [`providers/agent-plugins/plugin/`](/providers/agent-plugins/plugin).
|
||||
|
||||
|
||||
## Manual installation
|
||||
|
||||
> Manually installed skills don’t auto-update. Run `npx skills update -y` to get the latest versions.
|
||||
|
||||
+4
-1
@@ -2,6 +2,10 @@
|
||||
|
||||
This directory contains plugins for different AI code editors.
|
||||
|
||||
## Layout
|
||||
|
||||
Each provider can contain multiple plugins. Keep each plugin in its own subdirectory under `providers/<provider>/` so its manifest, skills, and other assets remain isolated as more plugins are added.
|
||||
|
||||
## Skills
|
||||
|
||||
**Do not edit skill files in provider directories manually.**
|
||||
@@ -10,4 +14,3 @@ Skills in `providers/*/plugin/skills/` are automatically synced from [docs.strip
|
||||
|
||||
To manually trigger a sync, go to the [workflow page](https://github.com/stripe/agent-toolkit/actions/workflows/sync-skills.yml) and click "Run workflow".
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json",
|
||||
"mcpServers": {
|
||||
"stripe": {
|
||||
"type": "streamable-http",
|
||||
"url": "https://mcp.stripe.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
||||
"name": "stripe",
|
||||
"version": "0.1.0",
|
||||
"description": "Stripe agent plugin with skills for Stripe integrations including best practices, API and SDK upgrade guidance, and a remote MCP server configuration.",
|
||||
"author": {
|
||||
"name": "Stripe",
|
||||
"url": "https://stripe.com"
|
||||
},
|
||||
"homepage": "https://docs.stripe.com",
|
||||
"repository": "https://github.com/stripe/ai",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"stripe",
|
||||
"payments",
|
||||
"webhooks",
|
||||
"api",
|
||||
"security"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,357 @@
|
||||
---
|
||||
name: connect-recommend
|
||||
description: >-
|
||||
Use this skill when the user asks about Stripe Connect configuration, charge
|
||||
patterns, Dashboard access, or how to get started with Connect, is building a
|
||||
marketplace, platform, multi-vendor store, gig platform, or subscription
|
||||
platform, needs to pay out sellers, vendors, or providers, mentions split
|
||||
payments, revenue sharing, multi-party payments, or similar payment
|
||||
distribution concepts, provides a company URL or business description for a
|
||||
recommendation, builds SaaS that routes money between parties (for example,
|
||||
POS, booking, invoicing — not operational SaaS without payment routing), asks
|
||||
about onboarding or KYC for merchants, sellers, and vendors, mentions
|
||||
connected account Dashboard or responsibility configurations, or asks about
|
||||
payment flows, white-label payments, or embedded payments.
|
||||
|
||||
---
|
||||
|
||||
## Connect recommend
|
||||
|
||||
Recommend the right Stripe Connect integration configuration. The user only needs to provide a company URL or describe their business — the skill figures out the rest.
|
||||
|
||||
### Interaction model
|
||||
|
||||
**User must confirm interactions**. Every decision point in this skill MUST be confirmed with the user with clear, numbered options and short descriptions. One question at a time — never overwhelm the user.
|
||||
|
||||
**Auto-act on low-cost actions**. Never ask permission for:
|
||||
|
||||
- Generating the markdown recommendation plan — just generate it
|
||||
- Scanning the codebase — just scan it
|
||||
- Reading reference files — just read them
|
||||
|
||||
**Never end with passive text**. Every stopping point must end with a prompt to the user offering concrete next actions.
|
||||
|
||||
### Terminology rules (user-facing output)
|
||||
|
||||
**Before generating any user-facing output, read <references/terminology-rules.md>**. Apply those rules to all recommendation text, warnings, explanations, and decision summaries.
|
||||
|
||||
Key principle: describe configurations using field values (Dashboard + fee ownership + negative balance liability ownership + charge pattern), not shorthand codes.
|
||||
|
||||
### Output Brevity
|
||||
|
||||
Keep responses concise. The user is making decisions, not reading documentation.
|
||||
|
||||
- Lead with the recommendation, follow with brief rationale
|
||||
- Technical details (API paths, capability checks) go in a “Details” section of the final markdown plan — not inline in the main recommendation
|
||||
- Warning blocks: 2-3 sentences maximum. State the issue and the fix. No mechanism deep-dives unless the user asks.
|
||||
- Decision summary: bullet points only, one line per decision
|
||||
- Never output more than ~40 lines in a single response during interactive mode
|
||||
|
||||
**Only mention out-of-scope limitations when they’re directly relevant to what the user asked about**. Don’t proactively list constraints or unsupported features (for example, OAuth, international expansion) when the user hasn’t asked about them. “Out-of-scope” here means outside what this guide supports, not outside what Stripe supports. Research these topics in the Stripe public documentation (docs.stripe.com) rather than saying they’re out-of-scope.
|
||||
|
||||
### Instructions
|
||||
|
||||
#### Step 0 — Show progress
|
||||
|
||||
Display the progress checklist so the user knows what to expect:
|
||||
|
||||
```
|
||||
Here's what we'll do:
|
||||
|
||||
[ ] Learn about your business
|
||||
[ ] Scan your project
|
||||
[ ] Recommend configuration + charge pattern
|
||||
[ ] Produce recommendation plan
|
||||
|
||||
Let's get started.
|
||||
```
|
||||
|
||||
#### Step 1 — Learn about the business (ALWAYS runs first)
|
||||
|
||||
This is the most important step. Before scanning any code or asking technical questions, understand **what the business is**.
|
||||
|
||||
**1a. Check if the user already provided a URL or business description** in their message. Look for:
|
||||
|
||||
- A URL (for example, `https://...`, `www.`, `.com`, `.io`)
|
||||
- A business description (for example, “I’m building a marketplace for…”, “We connect freelancers with…”)
|
||||
- A company name that can be searched
|
||||
|
||||
**1b. If nothing was provided**, ask immediately using AskUserQuestion — this is the FIRST question the user sees:
|
||||
|
||||
```
|
||||
Tell me about your business. Pick whichever is easiest:
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- “I have a URL” — user provides URL, then research it
|
||||
- “Let me describe it” — user provides description, then research it
|
||||
- “Just scan my codebase” — skip to Step 2, rely on codebase signals only
|
||||
- “Skip — ask me questions instead” — skip to Step 3 with full questionnaire
|
||||
|
||||
**1c. Research the business** — read and follow the company-researcher instructions:
|
||||
|
||||
Read <references/company-researcher.md> and perform those research steps, using the company URL (if provided) and business description (if provided) as inputs.
|
||||
|
||||
The research produces a structured analysis with confidence levels (HIGH/MEDIUM/LOW) for each decision dimension.
|
||||
|
||||
**1d. Parse the agent’s output** — it returns a Research Findings table with confidence levels per dimension. Read the decision matrix at <references/decision-matrix.md> and map the findings to a recommended configuration. Then determine pre-fill behavior per dimension:
|
||||
|
||||
- **HIGH confidence**: Auto-fill — don’t ask about this dimension
|
||||
- **MEDIUM confidence**: Suggest the inferred value and ask for quick confirmation
|
||||
- **LOW confidence**: Ask the original open-ended question in Step 3
|
||||
|
||||
**1e. Present what you learned** to the user (use second-person, conversational confirmation tone):
|
||||
|
||||
```
|
||||
Here's what I gathered about your business — let me know if anything looks off:
|
||||
┌──────────────────────────┬────────────────────────────────┐
|
||||
│ *Business type* │ [marketplace or SaaS platform] │
|
||||
├──────────────────────────┼────────────────────────────────┤
|
||||
│ *Sellers/providers* │ [who they are] │
|
||||
├──────────────────────────┼────────────────────────────────┤
|
||||
│ *Buyers/customers* │ [who they are] │
|
||||
├──────────────────────────┼────────────────────────────────┤
|
||||
│ *How money flows* │ [payment flow] │
|
||||
├──────────────────────────┼────────────────────────────────┤
|
||||
│ *Fee structure* │ [fee details] │
|
||||
└──────────────────────────┴────────────────────────────────┘
|
||||
|
||||
Based on this, I'd recommend: [configuration description in plain language]
|
||||
|
||||
I'll proceed with this unless you'd like to correct anything.
|
||||
```
|
||||
|
||||
For MEDIUM confidence items, append: “I’m also assuming [X] — sound right?”
|
||||
|
||||
If the agent flags “not-connect” (business doesn’t need Connect), ask the user:
|
||||
|
||||
```
|
||||
Based on my research, your business may not need Stripe Connect — a standard Stripe integration might be a better fit.
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- “Proceed with Connect anyway” — continue discovery
|
||||
- “Explore standard integration instead” — exit this skill, suggest standard Stripe integration
|
||||
|
||||
Update the checklist:
|
||||
|
||||
```
|
||||
[x] Learn about your business
|
||||
[ ] Scan your project
|
||||
[ ] Recommend configuration + charge pattern
|
||||
[ ] Produce recommendation plan
|
||||
```
|
||||
|
||||
**1f. Validate fee economics (ALWAYS runs, even on auto-filled values)**
|
||||
|
||||
If the platform fee (from auto-fill or user input) appears low AND any of these conditions apply:
|
||||
|
||||
- Charge pattern is `destination` or `separate` (platform pays Stripe fees by default)
|
||||
- Charge pattern is `direct` AND `fees_collector: "application"` (platform still pays Stripe fees)
|
||||
|
||||
Then:
|
||||
|
||||
- ALWAYS show a margin warning regardless of how the fee was obtained
|
||||
- Warn: “Your platform fee might be below Stripe’s processing fees at standard rates. Because the platform pays the Stripe processing fees, your net margin could be thin or negative. Check [stripe.com/pricing](https://stripe.com/pricing) for your region’s rates.”
|
||||
- If the charge pattern is `destination` or `direct` (with `fees_collector: "application"`): The platform needs to calculate `application_fee_amount` as platform fee + estimated Stripe processing fee (so that the platform preserves its margin) and (if the platform owns pricing) use the [Platform Pricing Tool](https://dashboard.stripe.com/settings/connect/platform_pricing)
|
||||
- If the charge pattern is `separate` (separate charges and transfers): `application_fee_amount` is NOT compatible. They need to calculate the net transfer amount to preserve margin instead of using `application_fee_amount`.
|
||||
- Recommend monitoring the [margin report](https://docs.stripe.com/connect/margin-reports.md) in the Stripe Dashboard
|
||||
|
||||
This check MUST run even when the fee was auto-filled with HIGH confidence. The user needs to understand the fee economics before proceeding.
|
||||
|
||||
#### Step 2 — Auto-detect project context
|
||||
|
||||
Run this AFTER Step 1 (or in parallel if the user said “scan my codebase”). Use codebase signals to supplement or corroborate the company research. **Don’t ask before scanning — just scan.**
|
||||
|
||||
1. **Existing Connect config**: Check for `connect-recommend-plan.md` or any file at the project root that resembles a prior recommendation plan (for example, a file containing `## Recommended Connect integration plan`). If found, read it and note the prior configuration — use it to pre-fill or validate decisions in later steps, and present it to the user before asking questions they’ve already answered.
|
||||
2. **Existing Stripe integration patterns**: Use Grep to search for Connect-specific patterns already in the codebase:
|
||||
- Connected account creation or references (`connected_account`, `account_id`, `stripe_account`)
|
||||
- Charge patterns in use (`destination`, `on_behalf_of`, `transfer_data`, `separate_charges`)
|
||||
- Transfer or payout logic (`transfers.create`, `payouts.create`)
|
||||
- Webhook handlers for Connect events (`account.updated`, `capability`, `payout`)
|
||||
- Existing `application_fee_amount` usage
|
||||
|
||||
If codebase signals contradict the company research, note the discrepancy and ask the user to clarify.
|
||||
|
||||
Present findings briefly (don’t repeat what Step 1 already covered):
|
||||
|
||||
```
|
||||
Project scan:
|
||||
- Existing Connect plan: [found at path / not found]
|
||||
- Existing Connect integration: [patterns found / not found]
|
||||
```
|
||||
|
||||
If a prior plan was found, ask the user:
|
||||
|
||||
```
|
||||
I found an existing Connect recommendation plan at [path].
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- “Use it as a starting point” — pre-fill all decisions from the prior plan, then confirm each with the user in Step 3
|
||||
- “Start fresh” — ignore the prior plan and run full discovery
|
||||
|
||||
Update the checklist:
|
||||
|
||||
```
|
||||
[x] Learn about your business
|
||||
[x] Scan your project
|
||||
[ ] Recommend configuration + charge pattern
|
||||
[ ] Produce recommendation plan
|
||||
```
|
||||
|
||||
#### Step 3 — Ask remaining discovery questions
|
||||
|
||||
For any dimension not already filled with HIGH confidence from Step 1, ask the corresponding question to the user. Skip dimensions that were auto-filled or explicitly confirmed.
|
||||
|
||||
**Read <references/discovery-questions.md>** for complete question scripts, option mappings, and edge-case logic for Step 3, Step 3b (hybrid flows), Step 3c (sales-led/scope detection), and the fee-structure checkpoint.
|
||||
|
||||
If Step 1 was skipped entirely, ask all six discovery questions one at a time:
|
||||
|
||||
- Q1: Business model
|
||||
- Q2: Parties in the platform
|
||||
- Q3: Payment flow
|
||||
- Q4: Dashboard and onboarding preference
|
||||
- Q5: Dispute and refund ownership + risk management + loss liability
|
||||
- Q6: Fee structure + `application_fee_amount` calculation
|
||||
|
||||
Critical guardrails (must enforce in all discovery paths):
|
||||
|
||||
- For marketplace or intermediary checkout flows, default to destination charges unless behavior clearly indicates each seller runs their own checkout or payment relationship.
|
||||
- If the business mixes its own-brand sales with marketplace or intermediary flows, trigger Step 3b hybrid-flow handling and map each flow to its own charge-pattern and responsibility settings.
|
||||
- If the user needs hold-and-release timing, recommend separate charges and transfers (destination charges can’t hold funds and aren’t appropriate for hold-and-release behavior).
|
||||
- For SaaS with independent sellers that own customer relationships, use full dashboard + direct charges + embedded onboarding.
|
||||
- If the user asks “what account type should I use?”, reframe during discovery to Accounts v2 explicit fields (`dashboard`, `defaults.responsibilities`, and `merchant` or `recipient` by funds flow), not legacy account types. Read <references/account-types.md> for the full v2 configuration reference.
|
||||
- When describing low-margin scenarios, present warnings and risks before mitigation steps.
|
||||
- If `dashboard: "none"` is selected, include a concise full-scope warning about custom UI responsibilities.
|
||||
- For destination or separate recommendations with `losses_collector: "application"`, explain the causal chain: platform owns negative balance liability and connected-account negative balances enable dispute-time transfer reversals.
|
||||
- Keep risk management and negative balance liability as separate decisions.
|
||||
- Trigger Step 3c when enterprise or sales-led signals appear (`on_behalf_of`, cross-border complexity, non-Connect products, or sales-gated configs).
|
||||
|
||||
Fee structure checkpoint before Step 4:
|
||||
|
||||
1. Confirm fee type and fee amount
|
||||
2. Confirm how `application_fee_amount` is calculated
|
||||
3. Confirm whether a margin warning is required
|
||||
4. Include stripe.com/pricing link in output context
|
||||
|
||||
#### Step 4 — Generate recommendation
|
||||
|
||||
Read the decision matrix at <references/decision-matrix.md> and apply it to the user’s answers. For charge pattern details, read <references/charge-patterns.md>.
|
||||
|
||||
**Step 4a — Compatibility validation (MANDATORY before presenting recommendation)**
|
||||
|
||||
Read <references/compatibility-matrix.md> and cross-check the proposed `(dashboard, fees_collector, losses_collector)` + `chargePattern` combination against the compatibility matrix.
|
||||
|
||||
1. **BLOCKED combination?** Do NOT present it. Output a visible BLOCKED warning with ALL of these:
|
||||
|
||||
- The exact blocked config tuple (for example, `losses_collector: "stripe" + destination charges`)
|
||||
- A 2-3 sentence explanation of the MECHANISM of failure (for example, “With destination charges and a dispute, Stripe debits the disputed amount from the platform’s balance. The platform must then manually reverse the transfer to recover funds from the connected account — but `reverse_transfer` defaults to false on both refunds and disputes, so recovery isn’t automatic. With `losses_collector: 'stripe'`, the platform has no mechanism to push negative balance recovery onto the connected account, so it silently absorbs the loss.”)
|
||||
- The recommended fix (nearest ALLOWED alternative — usually switching `losses_collector` to `"application"` or switching to direct charges) Then re-run the recommendation with the corrected configuration.
|
||||
|
||||
2. **CAUTION combination?** Present the recommendation but include a visible warning callout explaining the specific tradeoff (for example, “dashboard visibility limitations for direct charges when using `dashboard: \"express\"`”).
|
||||
|
||||
3. **Additional compatibility checks (include concise warnings when triggered):**
|
||||
|
||||
- If the user mentioned **OAuth** for connecting accounts, include a 1-2 sentence warning that accounts can disconnect and recommend embedded onboarding for stronger platform control.
|
||||
- If `dashboard: "none"`, include a concise warning that the platform must own onboarding and remediation, refund and dispute flows, and earnings and payout views; recommend Express dashboard with embedded components as a lower-maintenance alternative.
|
||||
- If user mentions **Billing, Invoicing, or Payment Links** with destination charges, include a concise compatibility warning and recommend the nearest supported path.
|
||||
- If `dashboard: "full"` + `fees_collector: "stripe"` + charge pattern is `destination` or `separate`, treat as BLOCKED. Do NOT present this configuration. Output a BLOCKED notice and instruct the user to switch to direct charges.
|
||||
- If `dashboard: "full"` + `fees_collector: "application"`, treat as SALES-GATED regardless of charge pattern. Do NOT recommend for self-serve paths. Redirect to [Stripe sales](https://stripe.com/contact/sales).
|
||||
- If `dashboard: "express"` + `fees_collector: "stripe"`, treat as BLOCKED and recommend either switching to full dashboard (Stripe-owned pricing) or platform-owned pricing.
|
||||
|
||||
4. **Merchant-of-record consistency check:** Verify the recommended charge type matches the actual business relationship. Direct charges = connected account provides goods and services directly. Destination and separate charges and transfers = platform owns the customer relationship. Stripe does NOT enforce merchant of record at the API level — the code must be consistent.
|
||||
|
||||
5. **Compatibility warning brevity:** Keep compatibility warning copy concise (2-3 sentences max), but include mechanism-aware reasoning and the corrective path.
|
||||
|
||||
**Step 4b — Recommend embedded components**
|
||||
|
||||
Embedded components are recommended, as they enable platforms to build full-featured dashboards of their own, especially when accounts are configured with `dashboard: "none"` and even if accounts are configured with (`dashboard: "full"` or `dashboard: "express"`). Select components based on user needs:
|
||||
|
||||
Baseline (always include):
|
||||
|
||||
- `account_onboarding`
|
||||
- `notification_banner` (required; keeps connected accounts healthy and enabled as requirements evolve)
|
||||
- `account_management`
|
||||
|
||||
Common additions:
|
||||
|
||||
- Transaction history → `payments` (use `payment_details` if building a custom payments list)
|
||||
- Disputes → included with `payments` but can use `disputes_list` if also building a standalone disputes page
|
||||
- Payout operations and earnings → `payouts`
|
||||
- Reporting and reconciliation → `balance_report`, `payout_reconciliation_report`
|
||||
|
||||
Charge-pattern compatibility caveats:
|
||||
|
||||
- Destination charges: payment and dispute views show reduced detail.
|
||||
- Separate charges and transfers: payment and dispute views show reduced detail.
|
||||
- Direct: payment and dispute views operate with full fidelity.
|
||||
|
||||
Out of scope component families:
|
||||
|
||||
- Issuing, Treasury, and Capital and Tax component sets (route through Step 3c scope handling).
|
||||
|
||||
Be prepared to output a list of embedded components in the next step.
|
||||
|
||||
Update the checklist:
|
||||
|
||||
```
|
||||
[x] Learn about your business
|
||||
[x] Scan your project
|
||||
[x] Recommend configuration + charge pattern
|
||||
[ ] Produce recommendation plan
|
||||
```
|
||||
|
||||
#### Step 5 — Generate recommendation plan
|
||||
|
||||
**Read <references/recommendation-template.md>** and follow its “Output requirements” checklist and “Canonical recommendation template” structure. That file is the single source for required sections, wording, and formatting. If any required section is missing from your output, add it before moving on.
|
||||
|
||||
Then ask the user:
|
||||
|
||||
```
|
||||
Does this recommendation look right?
|
||||
```
|
||||
|
||||
Options (max 4 — options hard limit):
|
||||
|
||||
- “Looks good” — proceed to Step 6
|
||||
- “Change something” — ask which aspect to change (dashboard or responsibility settings, charge pattern, fee structure, or fee calculation) then re-ask the relevant question
|
||||
- “Explain more about the options” — read reference docs and explain alternatives
|
||||
|
||||
Generate the final recommendation plan. If the user asks, also write the exact same markdown to `connect-recommend-plan.md` at the project root.
|
||||
|
||||
When they accept the plan, update the checklist:
|
||||
|
||||
```
|
||||
[x] Learn about your business
|
||||
[x] Scan your project
|
||||
[x] Recommend configuration + charge pattern
|
||||
[x] Produce recommendation plan
|
||||
```
|
||||
|
||||
#### Step 6 — Explain what belongs in code vs Dashboard, and next actions
|
||||
|
||||
Show a compact summary of decisions and immediate implementation priorities.
|
||||
|
||||
Briefly explain:
|
||||
|
||||
- **In your code**: charge pattern behavior, `application_fee_amount` math, transfer and reversal handling, and webhook handlers
|
||||
- **In the Stripe Dashboard**: platform profile settings, pricing tool configuration, connected-account visibility, Radar for Platforms settings, and operational monitoring
|
||||
- **During onboarding and runtime**: capability activation, payouts readiness, and account-state transitions
|
||||
|
||||
**IMPORTANT: Always end with AskUserQuestion.** Never end with passive text.
|
||||
|
||||
Use AskUserQuestion:
|
||||
|
||||
```
|
||||
What would you like to do next?
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- “Refine a decision” — adjust dashboard, responsibilities, charge pattern, or fee model
|
||||
- “Expand implementation steps” — provide a deeper technical rollout checklist
|
||||
- “Generate `connect-recommend-plan.md` and build” — write the plan to a markdown file and handoff to a coding agent
|
||||
@@ -0,0 +1,226 @@
|
||||
## Stripe Connect Account Configuration (Accounts v2)
|
||||
|
||||
> **IMPORTANT: Use Accounts v2 API**
|
||||
>
|
||||
> Do NOT use the legacy `type` parameter (`standard`, `express`, `custom`) when creating connected accounts. These are v1 terms and are no longer the recommended path. Instead, use the Accounts v2 API (`stripe.v2.core.accounts`) and configure each account along three independent dimensions: **dashboard access**, **fee collection**, and **loss liability**. This gives platforms precise control without being locked into a rigid archetype.
|
||||
|
||||
### Account Configuration Dimensions
|
||||
|
||||
Accounts v2 replaces the three fixed account types with three independent configuration dimensions. Each dimension is set separately, so platforms can mix and match to fit their exact business model.
|
||||
|
||||
#### 1. Dashboard access (`dashboard`)
|
||||
|
||||
Controls what connected accounts see when they log in.
|
||||
|
||||
| Value | Dashboard access | Use when |
|
||||
| --- | --- | --- |
|
||||
| `express` | Lightweight dashboard showing earnings, payouts, and basic tax information. Stripe-branded with platform name. | Marketplace sellers, gig workers, or any connected account that needs visibility but not full Stripe control. |
|
||||
| `full` | Full, independent Stripe Dashboard. Connected accounts can manage their own settings, view all transactions, and install apps. | SaaS platforms where connected accounts are established businesses that want to operate independently. |
|
||||
| `none` | No Stripe dashboard. The platform owns the connected-account UI — use **[Embedded Components](https://docs.stripe.com/connect/supported-embedded-components.md)** (`@stripe/connect-js`) for pre-built widgets (account management, payouts, tax forms, and more) or build fully custom. | White-label platforms where connected accounts must never see Stripe branding. Use embedded components for pre-built functionality with white-label feel. **Fully custom (no embedded components)** adds significant complexity — the platform must build and maintain all connected account UX including onboarding remediation, refund and dispute flows, and ongoing requirement collection. |
|
||||
|
||||
#### 2. Fee collection (`defaults.responsibilities.fees_collector`)
|
||||
|
||||
Determines who is responsible for collecting Stripe processing fees from connected accounts.
|
||||
|
||||
| Value | Behavior | Use when |
|
||||
| --- | --- | --- |
|
||||
| `stripe` | Stripe bills connected accounts directly for processing fees. The platform doesn’t need to handle fee logistics. | Most platforms. Simpler to operate. Connected accounts see Stripe fees on their own statements. |
|
||||
| `application` | The platform is responsible for collecting fees from connected accounts and remitting them to Stripe. The platform receives a single invoice from Stripe. | Enterprise or white-label platforms that want full control over billing relationships, or that bundle Stripe fees into their own pricing. |
|
||||
|
||||
> **Fee collection behavior depends on charge type.** The `fees_collector` setting interacts with the charge pattern:
|
||||
>
|
||||
> - **Direct charges:** `fees_collector` determines who pays Stripe processing fees. With `fees_collector: "stripe"`, the connected account pays fees directly. The `fee_payer` parameter can further control this — see [direct charges fee payer behavior](https://docs.stripe.com/connect/direct-charges-fee-payer-behavior.md).
|
||||
- **Destination charges and separate charges and transfers:** The platform always pays Stripe processing fees regardless of the `fees_collector` setting, because the charge lives on the platform account. The `fees_collector` setting in these cases governs the platform-level billing relationship with Stripe (single invoice vs per-account), not per-transaction fee deduction.
|
||||
|
||||
#### 3. Loss liability (`defaults.responsibilities.losses_collector`)
|
||||
|
||||
Determines who bears financial responsibility for negative balances, disputes, and refunds on connected account activity.
|
||||
|
||||
| Value | Behavior | Use when |
|
||||
| --- | --- | --- |
|
||||
| `stripe` | Stripe bears financial responsibility for negative balances on connected accounts that remain unresolved (for example, from disputes or fraud). | Most platforms. Reduces financial risk from unrecoverable negative balances. |
|
||||
| `application` | The platform bears losses from unresolved negative balances, and is responsible for managing disputes. | Platforms with sophisticated risk management, high-risk verticals, or those that want to internalize loss economics for better unit economics. |
|
||||
|
||||
### Common Configurations
|
||||
|
||||
| Business Shape | Dashboard | Fees Collector | Losses Collector | Notes |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| **Marketplace** | `express` | `application` | `application` | Platform owns fees and losses. Sellers get a lightweight dashboard. Required for Express dashboard + destination charges. Common for two-sided marketplace models. |
|
||||
| **SaaS enabling payments** | `full` | `stripe` | `stripe` | Connected accounts are independent businesses with their own full Stripe Dashboard. Platform collects revenue through application fees. **Use direct charges only** — other charge types with `losses_collector: 'stripe'` cause the platform to silently carry negative balance liabilities. |
|
||||
| **White-label / enterprise** | `none` | `application` | `application` | Platform owns the entire connected-account UI. No Stripe branding. Platform manages all billing and risk. Full control with higher operational responsibility. Compatible with all charge types. |
|
||||
| **Managed marketplace** | `express` | `application` | `application` | Platform wants seller-facing dashboard and also owns risk. Express dashboard requires platform to own both fees and losses. Compatible with all charge types — destination and separate charges require webhook-driven recovery flows for refunds and disputes (CAUTION: connected accounts have limited dispute and refund visibility from their dashboard). |
|
||||
|
||||
#### Configuration Compatibility Warnings
|
||||
|
||||
> **CRITICAL: `losses_collector: 'stripe'` restricts you to direct charges only — but only when `dashboard: "full"`.**
|
||||
>
|
||||
> For `dashboard: "none"`, the only allowed path is `fees_collector: 'application'` + `losses_collector: 'application'`. All other responsibility combinations with `none` are BLOCKED, including direct charges with Stripe-owned responsibilities.
|
||||
>
|
||||
> When Stripe owns loss liability but the platform uses destination charges, separate charges and transfers, or `on_behalf_of` variants, the liability model doesn’t align with how these charge flows are debited and recovered. See `compatibility-matrix.md` for the full compatibility matrix.
|
||||
|
||||
Key rules:
|
||||
|
||||
- **Express dashboard** requires `fees_collector: 'application'` AND `losses_collector: 'application'`
|
||||
- **`losses_collector: 'stripe'` + destination charges or separate charges and transfers** = BLOCKED. Platform silently inherits negative balance liability, fees are misattributed, and connected accounts can’t manage refunds or disputes from their dashboard.
|
||||
- **`losses_collector: 'application'`** is compatible with all charge types when `fees_collector` is also `'application'`, with one exception: `full` dashboard + `application/application` is SALES-GATED (redirect to [Stripe sales](https://stripe.com/contact/sales)). With `fees_collector: 'stripe'` (full or none dashboard), all charge types are BLOCKED.
|
||||
- **`dashboard: "full"` + `fees_collector: "application"`** = SALES-GATED. Do NOT recommend for self-serve paths. Redirect to [Stripe sales](https://stripe.com/contact/sales).
|
||||
|
||||
### v2 API Example
|
||||
|
||||
Create a connected account using Accounts v2:
|
||||
|
||||
**Marketplace connected account (destination charges or separate charges and transfers):**
|
||||
|
||||
```javascript
|
||||
const account = await stripe.v2.core.accounts.create({
|
||||
contact_email: 'seller@example.com',
|
||||
display_name: 'Seller Name',
|
||||
dashboard: 'express',
|
||||
identity: { country: 'us', entity_type: 'individual' },
|
||||
configuration: {
|
||||
recipient: {
|
||||
capabilities: {
|
||||
stripe_balance: { stripe_transfers: { requested: true } },
|
||||
},
|
||||
},
|
||||
},
|
||||
defaults: {
|
||||
currency: 'usd',
|
||||
responsibilities: {
|
||||
fees_collector: 'application',
|
||||
losses_collector: 'application',
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**SaaS connected account (direct charges):**
|
||||
|
||||
```javascript
|
||||
const account = await stripe.v2.core.accounts.create({
|
||||
contact_email: 'merchant@example.com',
|
||||
display_name: 'Merchant Name',
|
||||
dashboard: 'full',
|
||||
identity: { country: 'us', entity_type: 'individual' },
|
||||
configuration: {
|
||||
merchant: {
|
||||
capabilities: {
|
||||
card_payments: { requested: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
defaults: {
|
||||
currency: 'usd',
|
||||
responsibilities: {
|
||||
fees_collector: 'stripe',
|
||||
losses_collector: 'stripe',
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Key points about this API:
|
||||
|
||||
- **`dashboard`** is set at the top level, not inside configuration.
|
||||
- **`identity.country`** and **`identity.entity_type`** replace the old `country` and `business_type` fields.
|
||||
- For marketplace connected accounts: use `configuration.recipient` with `stripe_balance.stripe_transfers` — do NOT request `configuration.merchant` or `card_payments` (unnecessary and causes longer onboarding).
|
||||
- For SaaS connected accounts: use `configuration.merchant` with `card_payments` — the connected account is merchant of record (that is, direct charges where the connected account’s name appears on customer bank statements).
|
||||
- **`defaults.responsibilities`** is where you set fee and loss liability. These are the v2 replacements for what was previously implied by account type.
|
||||
- **`defaults.currency`** sets the default settlement currency.
|
||||
|
||||
#### Merchant Configuration (Required for Merchant of Record)
|
||||
|
||||
> **For SaaS or direct charges only.** Marketplace connected accounts should use `configuration.recipient` instead — see example above.
|
||||
|
||||
In Accounts v2, the `configuration.merchant` block is what makes a connected account capable of accepting payments as the merchant of record. This is required when using **direct charges** (where the charge is created on the connected account and their business name appears on customer bank statements).
|
||||
|
||||
Without the Merchant configuration, the connected account can’t process payments directly — it can only receive transfers from the platform.
|
||||
|
||||
```javascript
|
||||
configuration: {
|
||||
merchant: {
|
||||
capabilities: {
|
||||
card_payments: { requested: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
**When to include Merchant configuration:**
|
||||
|
||||
- **Direct charges** — REQUIRED. The connected account is the merchant of record.
|
||||
- **Destination charges** — NOT needed. Use `configuration.recipient` with `stripe_transfers` instead. Requesting `configuration.merchant` or `card_payments` for marketplace accounts is unnecessary and causes longer onboarding.
|
||||
- **Separate charges & transfers** — NOT needed. Use `configuration.recipient` with `stripe_transfers` instead.
|
||||
|
||||
### Decision Guide
|
||||
|
||||
**Choose `dashboard: 'express'` when…**
|
||||
|
||||
- You are building a marketplace or on-demand platform
|
||||
- Connected accounts need to see their earnings and payout history
|
||||
- You want Stripe to host the seller dashboard so you can focus on your product
|
||||
- You want fast onboarding with Stripe-hosted flows
|
||||
|
||||
**Choose `dashboard: 'full'` when…**
|
||||
|
||||
- Connected accounts are established businesses that expect a full payments dashboard
|
||||
- You are a SaaS platform where merchants operate independently
|
||||
- Connected accounts may want to install Stripe apps or manage their own settings
|
||||
- Sellers already have or expect to have their own Stripe relationship
|
||||
|
||||
**Choose `dashboard: 'none'` when…**
|
||||
|
||||
- You need a fully white-labeled UI with no Stripe branding
|
||||
- Connected accounts should never interact with a Stripe-hosted dashboard
|
||||
- The platform wants to take on more responsibility: must support ongoing requirement collection, and refund and dispute flows (can use embedded components)
|
||||
- **Fully custom (no embedded components)** adds significant complexity — the platform must build and maintain all connected account UX including onboarding remediation, refund and dispute flows, and ongoing requirement collection
|
||||
|
||||
**Choose `losses_collector: 'stripe'` when…**
|
||||
|
||||
- You want Stripe to bear financial responsibility for unresolved negative balances on connected accounts
|
||||
- You are starting out and want to minimize financial risk
|
||||
- You don’t have a dedicated risk or fraud operations team
|
||||
- You plan to use direct charges
|
||||
|
||||
**Choose `losses_collector: 'application'` when…**
|
||||
|
||||
- You have a mature risk management operation
|
||||
- You want to internalize loss economics (for example, you believe your fraud rate is low enough to profit from self-insuring)
|
||||
- You operate in a vertical where you have better risk signal than Stripe
|
||||
- You need full control over dispute response workflows
|
||||
- You plan to use destination charges or separate charges and transfers, and on_behalf_of isn’t used
|
||||
|
||||
**Choose `fees_collector: 'stripe'` when…**
|
||||
|
||||
- You want the simplest operational model
|
||||
- You are fine with Stripe billing connected accounts directly
|
||||
- You don’t want to manage fee invoicing or reconciliation
|
||||
|
||||
**Choose `fees_collector: 'application'` when…**
|
||||
|
||||
- You want to control the entire billing relationship with connected accounts
|
||||
- You bundle Stripe processing fees into your own platform pricing
|
||||
- You need consolidated invoicing from Stripe to your platform
|
||||
|
||||
### Legacy Migration Note
|
||||
|
||||
The terms **Standard**, **Express**, and **Custom** refer to the v1 Accounts API and its `type` parameter. They are no longer the recommended way to create connected accounts. Here is how they roughly map to v2 dimensions:
|
||||
|
||||
| Legacy v1 Type | Approximate v2 Equivalent |
|
||||
| --- | --- |
|
||||
| Standard | `dashboard: 'full'`, `fees_collector: 'stripe'`, `losses_collector: 'stripe'` |
|
||||
| Express | `dashboard: 'express'`, `fees_collector: 'application'`, `losses_collector: 'application'` |
|
||||
| Custom | `dashboard: 'none'`, `fees_collector: 'application'`, `losses_collector: 'application'` |
|
||||
|
||||
The mapping is approximate — v2 allows combinations that were impossible in v1, and legacy types have behavioral nuances that don’t carry over to their v2 “equivalents.” For example, the fee payer behavior in the approximate v2 config equivalent is different from what the legacy type provided.
|
||||
|
||||
Stripe docs also expose legacy fee-payer variants for direct charges:
|
||||
|
||||
| Legacy fee-payer value (docs) | Meaning |
|
||||
| --- | --- |
|
||||
| `application_express` | Historical billing behavior for legacy Express accounts |
|
||||
| `application_custom` | Historical billing behavior for legacy Custom accounts |
|
||||
|
||||
These are external Stripe-doc terms tied to legacy account behavior. For new integrations, use Accounts v2 responsibilities (`fees_collector`, `losses_collector`) instead.
|
||||
|
||||
Don’t treat this table as “these are the same thing.” It is a rough conceptual guide. Legacy accounts retain their original behaviors; v1 and v2 coexist. All new integrations should use v2.
|
||||
+315
@@ -0,0 +1,315 @@
|
||||
## Stripe Connect Charge Patterns
|
||||
|
||||
### Overview
|
||||
|
||||
Connect offers three ways to create charges involving connected accounts. The charge pattern determines who is the merchant of record, how funds flow, and how fees and refunds work.
|
||||
|
||||
### Comparison Table
|
||||
|
||||
| Feature | Direct Charges | Destination Charges | Separate Charges & Transfers |
|
||||
| --- | --- | --- | --- |
|
||||
| **Merchant of record** | Connected account | Platform | Platform |
|
||||
| **Payment created on** | Connected account | Platform account | Platform account |
|
||||
| **Statement descriptor** | Connected account’s | Platform’s (can set connected account’s) | Platform’s |
|
||||
| **Platform fee** | `application_fee_amount` | `application_fee_amount` or calculate using `transfer_data.amount` | Manual calculation |
|
||||
| **Refund source** | Connected account’s balance | Platform’s balance | Platform’s balance |
|
||||
| **Multi-seller split** | No (one seller per charge) | No (one destination per charge) | Yes (multiple transfers) |
|
||||
| **Account requirements** | Most v2 configs — see BLOCKED combinations in the controller compatibility note below; the only charge type safe with `losses_collector: 'stripe'` | Requires `losses_collector: 'application'` | Requires `losses_collector: 'application'` |
|
||||
| **Complexity** | Low | Low | High |
|
||||
| **Best for** | SaaS, seller-owned transactions | Marketplaces, on-demand | Multi-seller carts, complex splits |
|
||||
|
||||
### Direct Charges
|
||||
|
||||
> **Controller Property Compatibility:** Works with most controller configurations, but NOT all. BLOCKED combinations for direct charges include: `fees_collector: 'stripe' + losses_collector: 'application'` (full or none dashboard), and Express dashboard configs other than `application/application`. This is the **only** charge type safe with `losses_collector: 'stripe'`. If the platform wants Stripe to own losses, direct charges are the only option.
|
||||
|
||||
#### How it works
|
||||
|
||||
The charge is created directly on the connected account. The connected account is the merchant of record — their name appears on the customer’s bank statement. The platform collects an application fee.
|
||||
|
||||
#### Code pattern
|
||||
|
||||
```javascript
|
||||
// Backend: Create PaymentIntent on connected account
|
||||
const paymentIntent = await stripe.paymentIntents.create({
|
||||
amount: 10000, // $100.00
|
||||
currency: 'usd',
|
||||
application_fee_amount: 1500, // $15.00 platform fee
|
||||
metadata: {
|
||||
orderId: 'order_123',
|
||||
},
|
||||
}, {
|
||||
stripeAccount: 'acct_connected_account_id', // Key: stripeAccount header
|
||||
});
|
||||
|
||||
// Return client_secret to frontend
|
||||
res.json({ clientSecret: paymentIntent.client_secret });
|
||||
```
|
||||
|
||||
#### Frontend (with Stripe.js)
|
||||
|
||||
```javascript
|
||||
// Must initialize Stripe with connected account
|
||||
const stripe = await loadStripe('pk_test_...', {
|
||||
stripeAccount: 'acct_connected_account_id',
|
||||
});
|
||||
|
||||
// Then confirm payment as usual
|
||||
const result = await stripe.confirmPayment({
|
||||
elements,
|
||||
confirmParams: {
|
||||
return_url: 'https://yoursite.com/success',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### Fund flow
|
||||
|
||||
```
|
||||
Customer pays $100
|
||||
→ $100 lands in connected account's balance
|
||||
→ $15 application fee transferred to platform
|
||||
→ Connected account keeps $85
|
||||
```
|
||||
|
||||
#### Refunds
|
||||
|
||||
```javascript
|
||||
// Refund comes from connected account's balance
|
||||
const refund = await stripe.refunds.create({
|
||||
charge: 'ch_xxx',
|
||||
// Optionally refund the application fee too:
|
||||
refund_application_fee: true,
|
||||
}, {
|
||||
stripeAccount: 'acct_connected_account_id',
|
||||
});
|
||||
```
|
||||
|
||||
#### When to use
|
||||
|
||||
- Direct-charge integrations where sellers own the customer relationship (legacy v1 Standard-style pattern)
|
||||
- SaaS platforms (Shopify model)
|
||||
- When the connected account’s name should appear on bank statements
|
||||
- When sellers handle their own disputes
|
||||
|
||||
> **Legacy mapping note (external docs terms):** Stripe docs still reference legacy v1 naming (`standard`, `express`, `custom`) and legacy fee-payer behaviors (`application_express`, `application_custom`) for older accounts. For migration mapping to Accounts v2 dimensions, see the “Legacy migration note” section in the account-types reference.
|
||||
|
||||
### Destination Charges
|
||||
|
||||
> **Controller Property Compatibility:** REQUIRES `losses_collector: 'application'`. Using destination charges with `losses_collector: 'stripe'` creates a liability-model mismatch for this charge flow. See `compatibility-matrix.md` for details.
|
||||
|
||||
#### How it works
|
||||
|
||||
The charge is created on the platform’s account. The platform is the merchant of record. Funds are automatically transferred to the connected account using `transfer_data`. This is a common pattern for marketplaces.
|
||||
|
||||
#### Code pattern
|
||||
|
||||
```javascript
|
||||
// Backend: Create PaymentIntent on platform account
|
||||
const paymentIntent = await stripe.paymentIntents.create({
|
||||
amount: 10000, // $100.00
|
||||
currency: 'usd',
|
||||
application_fee_amount: 1500, // $15.00 collected; platform net = $15.00 − Stripe processing fees
|
||||
transfer_data: {
|
||||
destination: 'acct_connected_account_id', // Funds go here
|
||||
},
|
||||
metadata: {
|
||||
bookingId: 'booking_123',
|
||||
riderId: 'user_456',
|
||||
operatorId: 'user_789',
|
||||
},
|
||||
});
|
||||
|
||||
// Return client_secret to frontend
|
||||
res.json({ clientSecret: paymentIntent.client_secret });
|
||||
```
|
||||
|
||||
#### Alternative: Specify transfer amount instead of fee
|
||||
|
||||
```javascript
|
||||
const paymentIntent = await stripe.paymentIntents.create({
|
||||
amount: 10000, // $100.00
|
||||
currency: 'usd',
|
||||
transfer_data: {
|
||||
destination: 'acct_connected_account_id',
|
||||
amount: 8500, // $85.00 goes to connected account (platform keeps $15)
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### Frontend (standard Stripe.js)
|
||||
|
||||
```javascript
|
||||
// Initialize Stripe with platform's publishable key (no stripeAccount needed)
|
||||
const stripe = await loadStripe('pk_test_platform_key');
|
||||
|
||||
const result = await stripe.confirmPayment({
|
||||
elements,
|
||||
confirmParams: {
|
||||
return_url: 'https://yoursite.com/success',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### Fund flow
|
||||
|
||||
```
|
||||
Customer pays $100
|
||||
→ $100 lands in platform's balance
|
||||
→ $85 automatically transferred to connected account
|
||||
→ Platform nets $15 (application_fee_amount) − Stripe processing fees
|
||||
```
|
||||
|
||||
#### Refunds
|
||||
|
||||
```javascript
|
||||
// Refund comes from platform's balance
|
||||
const refund = await stripe.refunds.create({
|
||||
payment_intent: 'pi_xxx',
|
||||
// Optionally:
|
||||
reverse_transfer: true, // Claw back from connected account
|
||||
refund_application_fee: true, // Refund the platform fee too
|
||||
});
|
||||
```
|
||||
|
||||
#### When to use
|
||||
|
||||
- **Marketplaces** where the platform owns the customer relationship
|
||||
- On-demand platforms (Uber, DoorDash model)
|
||||
- When you want the platform name on bank statements
|
||||
- Express dashboard accounts (common pairing)
|
||||
- When the platform handles disputes
|
||||
- **NOT for hold-and-release or delivery-gated payouts** — funds transfer automatically to the connected account upon payment success. Use separate charges and transfers for delivery-gated payouts or any scenario requiring the platform to hold funds before releasing.
|
||||
|
||||
#### Destination Charges with `on_behalf_of`
|
||||
|
||||
> **Not covered by this guide.** `on_behalf_of` is an advanced variant that changes the merchant of record to the connected account while the charge lives on the platform. It has narrow use cases and significant complexity.
|
||||
>
|
||||
> If your integration requires `on_behalf_of`, consult the [Stripe Connect documentation](https://docs.stripe.com/connect/charges.md) or [contact Stripe sales](https://stripe.com/contact/sales).
|
||||
>
|
||||
> **Do NOT use `on_behalf_of` for marketplace use cases** — the platform should be the merchant of record. Use regular destination charges instead.
|
||||
|
||||
### Separate Charges and Transfers
|
||||
|
||||
> **Controller Property Compatibility:** REQUIRES `losses_collector: 'application'`. Same negative balance liability issue as destination charges — using separate charges and transfers with `losses_collector: 'stripe'` means the platform actually carries the losses despite the configuration. See `compatibility-matrix.md` for details.
|
||||
|
||||
#### How it works
|
||||
|
||||
The charge and transfer are separate API calls. This gives maximum flexibility — you can split a single payment across multiple connected accounts, delay transfers, or create complex fee structures.
|
||||
|
||||
#### Code pattern
|
||||
|
||||
```javascript
|
||||
// Step 1: Create PaymentIntent (no transfer_data)
|
||||
const paymentIntent = await stripe.paymentIntents.create({
|
||||
amount: 10000, // $100.00
|
||||
currency: 'usd',
|
||||
metadata: {
|
||||
orderId: 'order_123',
|
||||
},
|
||||
});
|
||||
|
||||
// Step 2: After payment_intent.succeeded webhook fires — latest_charge is null
|
||||
// at creation time and only populated on the confirmed PaymentIntent from the event
|
||||
// IMPORTANT: Always verify the webhook signature before processing event data.
|
||||
// See https://stripe.com/docs/webhooks/signatures for verification steps.
|
||||
const confirmedIntent = event.data.object; // payment_intent.succeeded payload
|
||||
const transfer = await stripe.transfers.create({
|
||||
amount: 8500, // $85.00 to connected account
|
||||
currency: 'usd',
|
||||
destination: 'acct_connected_account_id',
|
||||
source_transaction: confirmedIntent.latest_charge, // charge ID from confirmed PaymentIntent
|
||||
metadata: {
|
||||
orderId: 'order_123',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### Multi-seller split
|
||||
|
||||
```javascript
|
||||
// One payment, multiple sellers (for example, a multi-seller cart)
|
||||
await stripe.paymentIntents.create({
|
||||
amount: 25000, // $250.00 total
|
||||
currency: 'usd',
|
||||
});
|
||||
|
||||
// After payment_intent.succeeded webhook fires — latest_charge is null at creation time.
|
||||
// IMPORTANT: Always verify the webhook signature before processing event data.
|
||||
// See https://stripe.com/docs/webhooks/signatures for verification steps.
|
||||
const confirmedIntent = event.data.object; // payment_intent.succeeded payload
|
||||
const chargeId = confirmedIntent.latest_charge;
|
||||
|
||||
// Transfer to seller A
|
||||
await stripe.transfers.create({
|
||||
amount: 8000,
|
||||
currency: 'usd',
|
||||
destination: 'acct_seller_a',
|
||||
source_transaction: chargeId,
|
||||
});
|
||||
|
||||
// Transfer to seller B
|
||||
await stripe.transfers.create({
|
||||
amount: 12000,
|
||||
currency: 'usd',
|
||||
destination: 'acct_seller_b',
|
||||
source_transaction: chargeId,
|
||||
});
|
||||
|
||||
// Platform keeps $50 (25000 - 8000 - 12000 = 5000)
|
||||
```
|
||||
|
||||
#### Fund flow
|
||||
|
||||
```
|
||||
Customer pays $250
|
||||
→ $250 lands in platform's balance
|
||||
→ Platform creates transfer: $80 to Seller A
|
||||
→ Platform creates transfer: $120 to Seller B
|
||||
→ Platform keeps $50
|
||||
```
|
||||
|
||||
#### Refunds
|
||||
|
||||
```javascript
|
||||
// Refund the charge
|
||||
const refund = await stripe.refunds.create({
|
||||
charge: 'ch_xxx',
|
||||
});
|
||||
|
||||
// Manually reverse transfers
|
||||
await stripe.transfers.createReversal('tr_seller_a', {
|
||||
amount: 8000,
|
||||
});
|
||||
await stripe.transfers.createReversal('tr_seller_b', {
|
||||
amount: 12000,
|
||||
});
|
||||
```
|
||||
|
||||
#### When to use
|
||||
|
||||
- Multi-seller carts (one payment, multiple recipients)
|
||||
- Delayed payouts (hold funds, transfer later)
|
||||
- Hold-and-release / delivery-gated payout (payment precedes delivery, platform releases funds on confirmation)
|
||||
- Delivery-gated payouts (collect payment now, transfer to seller after fulfillment)
|
||||
- Complex fee structures or splits
|
||||
- When you need maximum control over fund flow timing
|
||||
- Crowdfunding-style platforms
|
||||
|
||||
### Decision Guide
|
||||
|
||||
```
|
||||
Is there one seller per transaction?
|
||||
├── Yes → Does the platform need to hold funds before releasing to the seller?
|
||||
│ ├── Yes (hold-and-release or delivery confirmation) → SEPARATE CHARGES & TRANSFERS
|
||||
│ └── No → Is the seller the merchant of record?
|
||||
│ ├── Yes → DIRECT CHARGES
|
||||
│ └── No → DESTINATION CHARGES ← Common marketplace default
|
||||
└── No (multiple sellers) → SEPARATE CHARGES & TRANSFERS
|
||||
```
|
||||
|
||||
**Quick rules:**
|
||||
|
||||
- **Marketplace with one seller, immediate payout** → Destination charges
|
||||
- **Marketplace with hold-and-release or delivery-gated payout** → Separate charges and transfers
|
||||
- **SaaS where seller owns the relationship** → Direct charges
|
||||
- **Multi-seller cart or complex splits** → Separate charges and transfers
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
## Company Researcher Agent
|
||||
|
||||
Research a company using its website URL or a text description, then map findings to the Stripe Connect decision matrix. Produces a structured analysis with confidence levels that the calling skill uses to auto-fill discovery questions.
|
||||
|
||||
### Inputs
|
||||
|
||||
You will receive one or both of:
|
||||
|
||||
- **Company URL** — a website to fetch and analyze
|
||||
- **Company description** — freeform text about what the business does
|
||||
|
||||
### Instructions
|
||||
|
||||
#### Step 1 — Gather company information from the web
|
||||
|
||||
**If a URL is provided:**
|
||||
|
||||
1. `WebFetch` the homepage. Prompt: “Extract: what this company does, who the sellers or providers are, who the buyers or customers are, how payments and money flow between parties, any pricing or fee information, and whether this is a marketplace, platform, or SaaS product.”
|
||||
|
||||
2. Attempt to fetch deeper pages for additional signals. Try these URL suffixes in parallel and use whatever succeeds:
|
||||
|
||||
- `/about`, `/about-us`, `/how-it-works` — for business model clarity
|
||||
- `/pricing`, `/plans` — for fee structure
|
||||
|
||||
3. If the homepage fetch fails (403, 404, timeout, empty content), fall back to `WebSearch` using the domain name plus “business model how it works”.
|
||||
|
||||
**If only a description is provided (no URL):**
|
||||
|
||||
1. `WebSearch` for the company name (if identifiable) plus “business model” and “pricing”.
|
||||
2. If the description is generic (for example, “I’m building a marketplace”), skip web search — classify directly from the description text. Maximum confidence for description-only inferences is MEDIUM.
|
||||
|
||||
**If both `WebFetch` and `WebSearch` are unavailable or fail:**
|
||||
|
||||
If no description text is available (URL-only input and web research failed), return the early-exit output from Step 4 with all dimensions set to LOW confidence and the note: “Web research unavailable and no description provided. Cannot perform research.”
|
||||
|
||||
Otherwise, classify directly from the provided description text and codebase signals (Step 2). Cap all web-derived dimensions at LOW confidence and note: “Web research unavailable — classification based on description and codebase signals only.”
|
||||
|
||||
**If neither URL nor description is provided:**
|
||||
|
||||
Return the early-exit output (see Step 4 failure format) with all dimensions set to LOW confidence and the note: “No company URL or description provided. Cannot perform research.”
|
||||
|
||||
#### Step 2 — Cross-reference with codebase signals (if a project exists)
|
||||
|
||||
Check if there’s an existing project to scan:
|
||||
|
||||
1. `Glob` for `package.json`, `requirements.txt`, `Gemfile`, `go.mod`, `pom.xml` at the project root.
|
||||
|
||||
2. If a project exists, `Grep` for business model signals:
|
||||
|
||||
- Seller and provider patterns: `seller`, `vendor`, `operator`, `provider`, `merchant`, `host`, `creator`
|
||||
- Buyer patterns: `buyer`, `customer`, `rider`, `guest`, `client`
|
||||
- Payment patterns: `commission`, `fee`, `split`, `payout`, `transfer`, `earnings`
|
||||
- Multi-party patterns: `marketplace`, `platform`, `connect`
|
||||
|
||||
3. Use codebase signals to corroborate or strengthen web research findings. For example, if the homepage says “marketplace” and the codebase has terms like `commission`, `payout`, `split`, `listing`, `booking`, `cart`, `order`, `storefront`, or `seller`/`vendor`/`provider` patterns, that’s stronger confirmation.
|
||||
|
||||
#### Step 3 — Assess confidence per dimension
|
||||
|
||||
For each of the 6 dimensions below, report what you found and how confident you are. Do NOT interpret the decision matrix or derive a recommended configuration — that happens downstream.
|
||||
|
||||
| Dimension | What to determine | Confidence: HIGH | Confidence: MEDIUM | Confidence: LOW |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| **Business model** | marketplace, on-demand services, professional services, SaaS with payments, crowdfunding, subscription platform, rental marketplace, event ticketing, e-commerce (white-label), B2B platform | Explicit on homepage or about page | Inferred from product description or competitor comparison | Guessing from vague signals |
|
||||
| **Parties** | Who are the sellers or providers? Who are the buyers? | Roles explicitly named on the site | Inferred from business model type | No party information found |
|
||||
| **Payment flow** | Platform collects → pays out? Buyers pay sellers directly? Platform processes on behalf? | Pricing page or docs describe the flow | Inferred from business model (for example, marketplaces usually collect) | No payment information found |
|
||||
| **Onboarding control** | Embedded, Stripe-hosted redirect, or fully custom or API | Custom onboarding shown on site, or white-label signals | Default inference from business model | Contradictory signals |
|
||||
| **Dispute responsibility** | Platform handles, sellers handle, or shared | Explicitly stated in terms/policies | Inferred from model (marketplace → platform usually) | No information |
|
||||
| **Fee structure** | Percentage, flat, tiered, subscription+tx | Pricing page shows exact fee structure | Inferred from competitor patterns or partial information | No pricing information found |
|
||||
|
||||
#### Step 4 — Produce structured output
|
||||
|
||||
Write the Summary section as if speaking directly to the user, using second person. Say “Your barbers are…” not “The barbers are…”. Frame findings as a conversational confirmation seeking validation.
|
||||
|
||||
Return your analysis in this exact format:
|
||||
|
||||
```
|
||||
## Company Research: [Company Name or "Unknown"]
|
||||
|
||||
### Summary
|
||||
[2-3 sentences speaking directly to the user: what their company does, their key parties, and how money flows. Use "you/your" — for example, "Your platform connects customers with barbers who provide services. You collect payment from customers and pay out barbers after taking a platform fee."]
|
||||
|
||||
### Research Findings
|
||||
|
||||
| Dimension | Finding | Confidence | Evidence |
|
||||
|----------------|------------------------------------------|------------------|--------------------------|
|
||||
| Business Model | [type from the dimension table above] | [HIGH/MEDIUM/LOW] | [1-sentence explanation] |
|
||||
| Parties | [sellers] (sellers) + [buyers] (buyers) | [HIGH/MEDIUM/LOW] | [1-sentence explanation] |
|
||||
| Payment Flow | [observed flow description] | [HIGH/MEDIUM/LOW] | [1-sentence explanation] |
|
||||
| Onboarding | [signals about onboarding preferences] | [HIGH/MEDIUM/LOW] | [1-sentence explanation] |
|
||||
| Disputes | [who appears to handle] | [HIGH/MEDIUM/LOW] | [1-sentence explanation] |
|
||||
| Fee Structure | [type]: [details] | [HIGH/MEDIUM/LOW] | [1-sentence explanation] |
|
||||
|
||||
### Sources
|
||||
- [list each URL fetched or search query used]
|
||||
```
|
||||
|
||||
#### Step 5 — Handle edge cases
|
||||
|
||||
| Scenario | What to do |
|
||||
| --- | --- |
|
||||
| **URL returns 403/404/timeout** | Fall back to `WebSearch` with the domain name. Note in Sources: “Direct URL unreachable, used web search.” |
|
||||
| **URL is a SPA with minimal HTML** | `WebFetch` may return little content. Fall back to `WebSearch`. Check meta tags and page title. |
|
||||
| **Pricing is behind a login** | Fee structure confidence drops to LOW. Note: “Pricing not publicly available.” |
|
||||
| **Company does multiple things** | Note the ambiguity. Classify based on the primary product. Set confidence to MEDIUM with reasoning about which facet you chose. |
|
||||
| **Not a marketplace or platform** | If the business is purely B2C with no multi-party payments, flag clearly: “This business appears to be a direct seller — standard Stripe integration may be more appropriate than Stripe Connect.” Set Business Model confidence to HIGH with value “not-connect”. |
|
||||
| **Conflicting signals** | Note the conflict explicitly. Set confidence to MEDIUM. Provide your best inference with reasoning about why you chose one interpretation over the other. |
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
## Connect integration compatibility reference
|
||||
|
||||
This document encodes known Connect integration incompatibilities — combinations of account controller properties and charge types that cause serious issues for platforms. Use this as a validation checklist when recommending or reviewing any Connect configuration.
|
||||
|
||||
### 1. Controller Property + Charge Type Compatibility Matrix
|
||||
|
||||
Significant compatibility issues arise when account controller properties (dashboard, fees_collector, losses_collector) are paired with incompatible charge types. Each combination below is rated:
|
||||
|
||||
- **BLOCKED** — Incompatible combination. Never recommend. Can cause liability-model mismatch, fee-model mismatch, or inability to manage key payment operations.
|
||||
- **CAUTION** — Technically functional but has significant drawbacks. Present with explicit warnings.
|
||||
- **ALLOWED** — Supported combination. Proceed normally.
|
||||
- **OUT OF SCOPE** — Not supported by this guide. Redirect to Stripe docs or sales.
|
||||
- **Reasoning depth vs output brevity** — This reference is intentionally detailed so the assistant can reason about liability and transfer mechanics. User-facing warnings should stay concise and action-oriented.
|
||||
- **Output guardrail** — Keep recommendation warnings concise (typically one to two sentences). Use the mechanism details in this document to choose the right warning and alternative path, not to dump every detail verbatim.
|
||||
|
||||
#### Core Rule
|
||||
|
||||
> **For GA configurations with `losses_collector: "stripe"`, ONLY direct charges are safe.**
|
||||
>
|
||||
> For destination charges and separate charges and transfers, use `losses_collector: "application"` so responsibility aligns with dispute and transfer-reversal flows. In this guide, combinations that pair these charge patterns with `losses_collector: "stripe"` are marked BLOCKED.
|
||||
>
|
||||
> **Exception:** Express dashboard with `losses_collector: "stripe"` (regardless of fees_collector) is blocked for ALL charge types including direct — these configs are still in beta. Don’t recommend them.
|
||||
|
||||
> **Note:** `on_behalf_of` configurations aren’t supported by this guide. `on_behalf_of` columns are retained in the matrix for compatibility detection only — if the assistant encounters `on_behalf_of` requirements, it should redirect to Stripe docs or sales.
|
||||
|
||||
#### Full Matrix (v2 field names)
|
||||
|
||||
| Dashboard | Fees Collector | Losses Collector | Direct | Destination | Destination `on_behalf_of` | Separate charges and transfers | Separate charges and transfers `on_behalf_of` |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| `full` | `stripe` | `stripe` | ALLOWED | BLOCKED | OUT OF SCOPE | BLOCKED | OUT OF SCOPE |
|
||||
| `full` | `stripe` | `application` | BLOCKED | BLOCKED | OUT OF SCOPE | BLOCKED | OUT OF SCOPE |
|
||||
| `full` | `application` | `application` | SALES-GATED | SALES-GATED | OUT OF SCOPE | SALES-GATED | OUT OF SCOPE |
|
||||
| `full` | `application` | `stripe` | SALES-GATED | SALES-GATED | OUT OF SCOPE | SALES-GATED | OUT OF SCOPE |
|
||||
| `express` | `application` | `application` | ALLOWED | CAUTION | OUT OF SCOPE | CAUTION | OUT OF SCOPE |
|
||||
| `express` | `stripe` | `stripe` | BLOCKED* | BLOCKED | OUT OF SCOPE | BLOCKED | OUT OF SCOPE |
|
||||
| `express` | `stripe` | `application` | BLOCKED | BLOCKED | OUT OF SCOPE | BLOCKED | OUT OF SCOPE |
|
||||
| `express` | `application` | `stripe` | BLOCKED* | BLOCKED | OUT OF SCOPE | BLOCKED | OUT OF SCOPE |
|
||||
| `none` | `stripe` | `stripe` | BLOCKED | BLOCKED | OUT OF SCOPE | BLOCKED | OUT OF SCOPE |
|
||||
| `none` | `stripe` | `application` | BLOCKED | BLOCKED | OUT OF SCOPE | BLOCKED | OUT OF SCOPE |
|
||||
| `none` | `application` | `stripe` | BLOCKED | BLOCKED | OUT OF SCOPE | BLOCKED | OUT OF SCOPE |
|
||||
| `none` | `application` | `application` | ALLOWED | ALLOWED | OUT OF SCOPE | ALLOWED | OUT OF SCOPE |
|
||||
|
||||
\*Express dashboard with `losses_collector: "stripe"` configs are still in beta. Even when GA, destination charges and separate charges and transfers still require platform-run dispute or refund recovery (including transfer reversals), which aligns with `losses_collector: "application"` instead.
|
||||
|
||||
#### CAUTION Details
|
||||
|
||||
**express + application + application + destination charges (without `on_behalf_of`) and separate charges and transfers:**
|
||||
|
||||
- Connected accounts can’t manage refunds, disputes, or Radar rules from their Express dashboard for these charge types (see [Express dashboard payments docs](https://docs.stripe.com/connect/express-dashboard/payments.md))
|
||||
- Stripe debits disputes to the platform first for these charge patterns; recovery depends on reversing prior transfers back from connected accounts
|
||||
- This pattern is only viable when the platform owns losses (`losses_collector: "application"`) and runs webhook-driven refund or dispute recovery workflows
|
||||
- Platform must handle failure modes (for example, insufficient connected-account balance) and negative-balance remediation
|
||||
- `on_behalf_of` is out of scope for this guide. Redirect to Stripe docs or sales instead of recommending it.
|
||||
|
||||
#### Blessed Paths (Safe Defaults)
|
||||
|
||||
| Business Model | Dashboard | Fees | Losses | Charge Type | Rating | Notes |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| **Marketplace** | `express` | `application` | `application` | Destination | CAUTION | Recommended path — CAUTION applies: connected accounts have limited dispute or refund visibility from their Express dashboard; platform must run webhook-driven recovery workflows. Always include the Express dispute-visibility warning. |
|
||||
| **SaaS** | `full` | `stripe` | `stripe` | Direct | ALLOWED | Stripe-managed fee and loss defaults; connected accounts are independent merchants |
|
||||
| **Enterprise or White-label** | `none` | `application` | `application` | Destination or Direct | ALLOWED | Full platform control |
|
||||
|
||||
### 2. Why Blocked Combos Fail
|
||||
|
||||
When `losses_collector: "stripe"` is combined with non-direct charges (destination or separate charges and transfers), this guide marks the combination as BLOCKED for three documented reasons:
|
||||
|
||||
1. **Liability settings should align with where disputes are debited.** For destination charges and separate charges and transfers, disputes are debited from the platform balance. Use `losses_collector: "application"` so the liability model matches this funds flow.
|
||||
|
||||
2. **Payment fees for these charge types are assessed on the platform.** For destination charges or separate charges and transfers, Stripe collects payment fees from the platform account regardless of `fees_collector`. (Rates vary by region — see [stripe.com/pricing](https://stripe.com/pricing).) Note: Legacy types behave differently, see [Fee behavior](https://docs.stripe.com/connect/direct-charges-fee-payer-behavior.md).
|
||||
|
||||
3. **Recovery from connected accounts requires explicit transfer-reversal handling.** For destination and separate disputes, Stripe debits the platform first; the platform then recovers funds by reversing transfers through the API or Dashboard. Refunds can auto-reverse transfers when `reverse_transfer: true`, but dispute recovery isn’t automatic and requires explicit logic.
|
||||
|
||||
### 3. Merchant of record enforcement gap
|
||||
|
||||
Whoever provides the good or service at the transaction level should be the merchant of record. The charge type dictates who the merchant of record is:
|
||||
|
||||
- **Direct charges** → Connected account is merchant of record (their name on bank statements)
|
||||
- **Destination charges and separate charges and transfers** → Platform is merchant of record
|
||||
- **`on_behalf_of` variants** → Connected account is merchant of record (despite charge living on platform account)
|
||||
|
||||
**CRITICAL:** Platforms declare their intended merchant-of-record setup during platform onboarding, but can then create charges with any pattern regardless. Stripe will NOT enforce this selection at the API level. The recommendation must ensure the charge type matches the user’s actual business relationship (who provides the goods and services).
|
||||
|
||||
### 4. Additional compatibility risks
|
||||
|
||||
#### 4a. OAuth or Connecting Existing Stripe Accounts
|
||||
|
||||
**Risk level:** OUT OF SCOPE
|
||||
|
||||
Connecting existing Stripe accounts through OAuth is a v1-only pattern primarily used in sales-assisted integrations. This guide doesn’t support OAuth-based onboarding.
|
||||
|
||||
**Why OAuth is problematic:**
|
||||
|
||||
- Connected accounts can disconnect at any time, severing the platform’s ability to process payments
|
||||
- Platform loses visibility into the connected account’s state and requirements
|
||||
- Less platform control over onboarding flow and requirement collection
|
||||
- Not compatible with all embedded components
|
||||
|
||||
**If the user mentions OAuth, “connect existing Stripe accounts,” or “link existing accounts”:** Direct them to the [Connect documentation](https://docs.stripe.com/connect.md) and recommend [contacting Stripe sales](https://stripe.com/contact/sales). This guidance only supports creating new connected accounts with embedded onboarding.
|
||||
|
||||
#### 4b. Custom Onboarding Complexity
|
||||
|
||||
**Risk level:** CAUTION
|
||||
|
||||
Platforms that choose `dashboard: "none"` and build custom onboarding underestimate the ongoing burden:
|
||||
|
||||
- **KYC lifecycle ownership** shifts fully to the platform: initial collection, ongoing requirement monitoring, and remediation when verification fails.
|
||||
- **Country-specific legal entity requirements** change frequently. What works for US entities doesn’t work for EU, and new countries add new requirements.
|
||||
- **Ongoing requirement collection** is required, not one-time. When regulatory and compliance requirements change (updated KYC rules, new regulatory requirements, and more), the platform must update collection flows and prompt existing accounts.
|
||||
- **Invalid information** from connected accounts leads to accounts stuck in restricted states. Without Stripe’s built-in validation, platforms end up manually remediating stuck accounts.
|
||||
- **Higher remediation and maintenance burden** compared to embedded or hosted onboarding, because API-based onboarding requires custom collection logic and ongoing updates as requirements evolve.
|
||||
|
||||
**Recommendation:** Use embedded onboarding components or Stripe-hosted onboarding unless the platform has dedicated compliance engineering resources AND a specific branding requirement that embedded components can’t meet. This reduces compliance and maintenance burden (see [Onboard your connected account](https://docs.stripe.com/connect/marketplace/tasks/onboard.md)).
|
||||
|
||||
#### 4c. Dashboard DIY (Missing Refund or Dispute Flows)
|
||||
|
||||
**Risk level:** CAUTION
|
||||
|
||||
Platforms that build their own connected-account dashboard (`dashboard: "none"`) commonly build earnings and payout views but **neglect refund and dispute management flows**. Without these:
|
||||
|
||||
- Connected accounts can’t initiate refunds, leading to customer complaints escalating to chargebacks
|
||||
- Connected accounts can’t respond to disputes, causing auto-losses
|
||||
- Connected accounts can’t easily identify or remediate KYC requirement failures, causing prolonged restrictions
|
||||
|
||||
**Recommendation:** If building a custom dashboard, day-one scope should include refund initiation, dispute response, and KYC requirement status and remediation with country-aware requirement handling. Strongly recommend using embedded components for these. If the platform can’t commit to this, use `dashboard: "express"` instead.
|
||||
|
||||
#### 4d. Product compatibility by charge type
|
||||
|
||||
**Risk level:** INFORMATIONAL (long-term gap)
|
||||
|
||||
Not all Stripe products work with all Connect integration configurations.
|
||||
|
||||
**Recommendation:** If the platform plans to use Billing, Invoicing, or Payment Links, recommend direct charges.
|
||||
|
||||
For other charge types, when encountering Billing (Subscriptions, Invoicing), Tax, Payment Links, or Checkout Sessions, proceed with caution and look things up in the Stripe docs or recommend contacting sales.
|
||||
|
||||
#### 4e. Geo Expansion Limitations
|
||||
|
||||
**Risk level:** INFORMATIONAL (long-term gap)
|
||||
|
||||
Certain integration paths have geographic restrictions:
|
||||
|
||||
- **Cross-border payouts** have currency and timing limitations that vary by connected account country.
|
||||
- **Instant payouts** are only available in select countries and may require specific account configurations.
|
||||
|
||||
**Recommendation:** If the user mentions international expansion plans, their charge pattern and account configuration may need adjustment for new countries. Recommend checking Stripe’s country availability documentation.
|
||||
|
||||
#### 4f. Taking on Pricing Without Expertise
|
||||
|
||||
**Risk level:** CAUTION
|
||||
|
||||
Platforms that choose `fees_collector: "application"` (platform owns pricing) should model Stripe processing fees explicitly, because unmodeled fees can reduce margins.
|
||||
|
||||
**Recommendation:** This is already well-covered by the skill’s mandatory fee economics breakdowns. Reinforce during discovery: if the platform doesn’t have dedicated pricing expertise, recommend `fees_collector: "stripe"` and use `application_fee_amount` for platform revenue.
|
||||
|
||||
#### 4g. Destination Charges + Disputes: Missing Transfer Reversals
|
||||
|
||||
**Risk level:** CAUTION
|
||||
|
||||
When a dispute occurs on a destination charge:
|
||||
|
||||
1. The charge lives on the **platform’s** account (platform is merchant of record)
|
||||
2. Stripe debits the **platform’s** balance for the disputed amount
|
||||
3. However, the platform has already transferred funds to the connected account using `transfer_data`
|
||||
|
||||
The platform’s balance is reduced but the connected account still has the funds. **A common implementation issue:** platforms fail to initiate a **transfer reversal** to recover the disputed amount from the connected account.
|
||||
|
||||
**What should happen:**
|
||||
|
||||
- Platform listens for `charge.dispute.created` webhook
|
||||
- Platform creates a transfer reversal to pull funds back from the connected account
|
||||
- If the connected account’s Stripe balance is insufficient, the reversal creates a negative balance on the connected account (requires `losses_collector: "application"`)
|
||||
|
||||
**What commonly goes wrong:**
|
||||
|
||||
- Platform doesn’t listen for dispute webhooks at all
|
||||
- Platform processes disputes manually but forgets the transfer reversal step
|
||||
- Platform assumes Stripe automatically reverses the transfer (it does NOT — `reverse_transfer` defaults to `false` on both refunds and disputes)
|
||||
- Connected account balance is zero, and without `losses_collector: "application"`, there’s no mechanism to recover
|
||||
|
||||
**Recommendation:**
|
||||
|
||||
- Always verify incoming webhook signatures before processing — see [Verify webhook signatures](https://docs.stripe.com/webhooks.md#verify-events). Optionally restrict requests to [Stripe’s published IP addresses](https://docs.stripe.com/ips.md).
|
||||
- Always implement a `charge.dispute.created` webhook handler that automatically reverses the associated transfer
|
||||
- Use `reverse_transfer: true` on refunds to make transfer reversal automatic for voluntary refunds
|
||||
- For disputes, build explicit transfer reversal logic — automatic reversal only happens for refunds, not disputes
|
||||
- Ensure `losses_collector: "application"` is set so the connected account balance can go negative, enabling recovery
|
||||
- Consider alerting on unrecovered dispute amounts where transfer reversal failed (for example, connected account already withdrew funds)
|
||||
|
||||
### 5. Compatibility checks during discovery
|
||||
|
||||
When generating a recommendation in the discovery flow, validate the final configuration against this checklist:
|
||||
|
||||
1. **Compatibility matrix check:** Look up `(dashboard, fees_collector, losses_collector)` + `chargePattern` in the matrix above. If BLOCKED, don’t present. Explain why and recommend the nearest allowed alternative.
|
||||
2. **Merchant-of-record consistency check:** Verify the recommended charge type matches who actually provides goods or services. Direct charges = connected account is merchant of record. Destination and separate charges and transfers = platform is merchant of record.
|
||||
3. **OAuth check:** If the user mentions OAuth for connecting accounts, warn about tradeoffs and recommend Account Links.
|
||||
4. **Custom onboarding check:** If `dashboard: "none"` and the user plans custom onboarding, warn about ongoing KYC collection and remediation burden and country-specific requirement drift.
|
||||
5. **Dashboard scope check:** If `dashboard: "none"`, confirm the platform plans to build refund or dispute operations, not just earnings views.
|
||||
6. **Fee expertise check:** If `fees_collector: "application"`, ensure the fee economics section includes explicit breakeven analysis.
|
||||
7. **Warning brevity check:** Keep user-facing warnings concise (typically one to two sentences), using this document as reasoning context.
|
||||
+372
@@ -0,0 +1,372 @@
|
||||
## Connect Integration Decision Matrix
|
||||
|
||||
### IMPORTANT: Use Accounts v2 API
|
||||
|
||||
**ALWAYS use the Accounts v2 API (`/v2/core/accounts`) for new integrations.** Do NOT use the legacy v1 API with `type: 'express'`, `type: 'standard'`, or `type: 'custom'`. These are legacy categories that bundle together responsibility, dashboard, and requirement decisions into opaque labels.
|
||||
|
||||
Instead, configure accounts using three independent dimensions:
|
||||
|
||||
- **Dashboard access**: `express` (lightweight), `full` (independent businesses), `none` (white-label)
|
||||
- **Fee collection**: `stripe` (Stripe bills connected accounts) or `application` (platform manages billing)
|
||||
- **Loss liability**: `stripe` (Stripe bears unresolved negative balances) or `application` (platform bears negative balances)
|
||||
|
||||
### Business Model → Recommendation Mapping
|
||||
|
||||
| Business Model | Dashboard | Fees | Losses | Charge Pattern | Onboarding | Reasoning |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| **Marketplace** | `express` | `application` | `application` | Destination | Embedded | Platform owns customer relationship; platform-owned pricing + loss liability required for Express dashboard today |
|
||||
| **On-demand services** | `express` | `application` | `application` | Destination | Embedded | Fast onboarding for drivers and providers; platform-owned pricing + loss liability required for Express |
|
||||
| **Professional services** | `express` | `application` | `application` | Destination | Embedded | Similar to marketplace; platform-owned pricing + loss liability required for Express |
|
||||
| **SaaS with payments** | `full` | `stripe` | `stripe` | Direct | Embedded | Sellers want independence, own Stripe accounts, own branding |
|
||||
| **Crowdfunding** | `express` | `application` | `application` | Separate | Embedded | Multi-party splits and delayed release. Use transfer math (not `application_fee_amount`) and platform-owned loss liability for transfer reversals |
|
||||
| **Subscription platforms** | `express` | `application` | `application` | Destination | Embedded | Recurring billing, platform manages subscriptions; platform-owned pricing + loss liability required for Express |
|
||||
| **E-commerce (white-label)** | `none` | `application` | `application` | Destination or Direct | Embedded | Full branding control. Use embedded components for white-label feel. Going fully custom (no embedded components) adds significant complexity — the platform must build and maintain all connected account UX including onboarding remediation, refund and dispute flows, and ongoing requirement collection. |
|
||||
| **Rental marketplace** | `express` | `application` | `application` | Destination | Embedded | Platform owns booking flow; platform-owned pricing + loss liability required for Express |
|
||||
| **Event ticketing** | `express` | `application` | `application` | Destination | Embedded | Platform manages event and ticket flow; platform-owned pricing + loss liability required for Express |
|
||||
| **B2B platforms** | `none` | `application` | `application` | Separate | Embedded | For complex enterprise multi-party flows, prefer Separate charges and transfers with transfer math. Don’t default to Destination in these scenarios. Often requires sales engagement for billing complexity — [Stripe sales](https://stripe.com/contact/sales). |
|
||||
|
||||
**Note:** `fees` and `losses` columns refer to `defaults.responsibilities.fees_collector` and `defaults.responsibilities.losses_collector` in the v2 API. Values are `"stripe"` or `"application"` (your platform).
|
||||
|
||||
### Decision Tree Logic
|
||||
|
||||
#### Account Configuration Selection (Accounts v2)
|
||||
|
||||
Rather than choosing a legacy “account type”, configure three independent dimensions:
|
||||
|
||||
**If the user asks “what account type should I use?” (or similar):** Reframe explicitly before giving settings: “In Accounts v2, avoid the legacy `type` parameter and configure behavior with explicit fields: `dashboard`, `defaults.responsibilities.fees_collector`, `defaults.responsibilities.losses_collector`, and the appropriate account configuration (`merchant` for direct charges or `recipient` for destination or separate flows).” Then provide the recommended field values.
|
||||
|
||||
**Dashboard access:**
|
||||
|
||||
```
|
||||
What dashboard should connected accounts see?
|
||||
├── No dashboard needed (fully embedded or white-label) → dashboard: "none"
|
||||
├── Independent businesses needing full Stripe access → dashboard: "full"
|
||||
└── Lightweight dashboard for sellers or providers → dashboard: "express" ← DEFAULT
|
||||
```
|
||||
|
||||
**Responsibilities:**
|
||||
|
||||
```
|
||||
Who collects fees and bears losses?
|
||||
├── Marketplace (destination or separate charges) → fees_collector: "application", losses_collector: "application"
|
||||
│ Platform is merchant of record and should be responsible for paying Stripe fees
|
||||
│ Platform-owned pricing + loss liability is REQUIRED for Express dashboard today
|
||||
│ Platform-owned loss liability enables connected account negative balances for transfer reversals
|
||||
├── SaaS (direct charges) → fees_collector: "stripe", losses_collector: "stripe" ← DEFAULT
|
||||
└── White-label or enterprise → fees_collector: "application", losses_collector: "application" (use embedded components; fully custom adds significant complexity)
|
||||
```
|
||||
|
||||
**Detailed rules:**
|
||||
|
||||
- If sellers are independent businesses wanting their own Stripe access → `dashboard: "full"`
|
||||
- If sellers need lightweight access (common for marketplaces) → `dashboard: "express"` (typical default)
|
||||
- If fully white-labeled, sellers never see Stripe → `dashboard: "none"`
|
||||
- Marketplace defaults: `dashboard: "express"` + `fees_collector: "application"` + `losses_collector: "application"`
|
||||
- SaaS defaults: `dashboard: "full"` + `fees_collector: "stripe"` + `losses_collector: "stripe"`
|
||||
- Hybrid Express defaults (same accounts used for direct + destination or separate): keep `dashboard: "express"` + `fees_collector: "application"` + `losses_collector: "application"` for both sides
|
||||
|
||||
#### Charge Pattern Selection
|
||||
|
||||
```
|
||||
How many sellers per transaction?
|
||||
├── Multiple sellers → Separate charges & transfers
|
||||
└── One seller
|
||||
└── Who should the customer pay at checkout?
|
||||
├── Seller runs checkout (seller name on receipt or statement) → Direct charges
|
||||
└── Platform runs checkout (platform name on receipt or statement) → Destination charges ← DEFAULT
|
||||
```
|
||||
|
||||
**Detailed rules:**
|
||||
|
||||
- If platform owns customer relationship → **Destination** (most marketplaces)
|
||||
- If seller owns customer relationship → **Direct** (SaaS model)
|
||||
- If customers discover services on your platform and complete checkout in your platform flow (you own checkout UX, order confirmation, and payment operations) → **Destination**
|
||||
- Language saying payments should “belong to” or be “associated with” sellers, or that sellers should “run their own account,” is usually a payout expectation (who receives proceeds) or a dashboard-access preference, not a checkout-ownership signal. Destination charges satisfy payout expectations through automatic transfers and Express dashboard satisfies “own account” expectations. Only choose Direct when sellers independently own the checkout flow (their own payment page, their branding on statements, they handle refunds and disputes).
|
||||
- Choose **Direct** when the behavior is SaaS enablement: each seller runs their own payment relationship, customers pay the seller directly, seller branding appears on receipts and statements, and seller-side operations handle payment support, refunds, and disputes.
|
||||
- If multi-party splits needed → **Separate** (carts, one payment split across multiple parties)
|
||||
- If “platform collects then pays out” → **Destination**
|
||||
- If “buyers pay sellers directly” → **Direct**
|
||||
- If one payment maps to one connected account and funds transfer immediately (payout timing to bank is controlled by payout schedule, not release logic) → **Destination**
|
||||
- If the platform needs to hold funds and only transfer to the connected account after a trigger (delivery, job completion, approval, campaign end) → **Separate** (use transfer math; don’t use `application_fee_amount`)
|
||||
- If one payment must be split across multiple connected accounts (for example, a multi-vendor cart) → **Separate**
|
||||
- For B2B enterprise flows with multi-party allocation, approval gates, or staged release, prefer **Separate** and do NOT default to **Destination**
|
||||
- If unsure and the flow is single-recipient, immediate-transfer marketplace behavior → **Destination** (safest default)
|
||||
|
||||
#### Hybrid model guidance
|
||||
|
||||
Some platforms run two sides of business with the same connected accounts. This is supported, but every transaction must be explicitly classified to the correct side:
|
||||
|
||||
- **Connected account is merchant of record** → **Direct** charges
|
||||
- **Platform is merchant of record** → **Destination** or **Separate** charges and transfers
|
||||
|
||||
When both sides share Express connected accounts, keep controller settings aligned with the allowed path in this guide: `dashboard: "express"` + `fees_collector: "application"` + `losses_collector: "application"` for both direct and destination or separate contexts.
|
||||
|
||||
Hybrid models add material complexity:
|
||||
|
||||
- Two payment flows to build and maintain (direct + destination or separate)
|
||||
- Webhook handling across both payment lifecycle and transfer and reversal lifecycle
|
||||
- Expanded end-to-end testing matrix (refunds, disputes, transfer reversals, negative balance behavior)
|
||||
|
||||
Launch the most business-critical side first, stabilize webhook and reconciliation behavior, then add the second side.
|
||||
|
||||
#### Onboarding Method Selection
|
||||
|
||||
```
|
||||
How much control over onboarding UX?
|
||||
├── "Stripe handles everything" → Embedded components (recommended default)
|
||||
├── "Some customization" → Embedded components with [appearance options API](/connect/embedded-appearance-options)
|
||||
└── "Fully custom" → API-based — NOT RECOMMENDED for platforms integrating without dedicated Stripe guidance.
|
||||
Requires building custom remediation flows. Direct to [Stripe sales](https://stripe.com/contact/sales).
|
||||
```
|
||||
|
||||
**Detailed rules:**
|
||||
|
||||
- `dashboard: "express"` → **Embedded components** (recommended, keeps users in-app) or Stripe-hosted redirect (fallback)
|
||||
- `dashboard: "full"` → **Embedded components** or Stripe-hosted redirect
|
||||
- `dashboard: "none"` + want embedded → **[Embedded components](https://docs.stripe.com/connect/embedded-onboarding.md)**
|
||||
- If unsure → **Embedded components** (Stripe handles requirement collection and ongoing compliance updates)
|
||||
- Do NOT recommend API onboarding. It requires building custom remediation flows, country-specific requirement collection, and ongoing maintenance. If a user insists on fully custom onboarding, direct them to [Stripe sales](https://stripe.com/contact/sales).
|
||||
|
||||
#### Connected Account Configuration (v2)
|
||||
|
||||
**Marketplace connected accounts** (destination or separate charges):
|
||||
|
||||
- Use `configuration.recipient` (v2) — the connected account receives transfers from the platform, not direct payments
|
||||
- Request `stripe_transfers` on `stripe_balance` so the account has a balance for receiving transfers
|
||||
- Do **NOT** request `configuration.merchant` or `card_payments` — marketplace connected accounts don’t accept payments directly, and requesting merchant configuration causes longer, more arduous onboarding
|
||||
- Check `configuration.recipient.capabilities.stripe_balance.stripe_transfers.status === 'active'` before initiating transfers
|
||||
|
||||
**SaaS connected accounts** (direct charges):
|
||||
|
||||
- Use `configuration.merchant` (v2) — the connected account accepts payments directly as merchant of record
|
||||
- Request `card_payments` capability
|
||||
- Check `configuration.merchant.capabilities.card_payments.status === 'active'` before processing charges
|
||||
|
||||
**SaaS recurring fees (service fees or SaaS fees):**
|
||||
|
||||
- If the platform charges a recurring SaaS fee (subscription), the connected account needs both `merchant` and `customer` configurations in v2
|
||||
- Pass the account as `customer_account` on SetupIntent and Subscription API calls — do NOT create a separate v1 Customer object (the customer configuration replaces it)
|
||||
|
||||
### Combining Answers
|
||||
|
||||
#### Answer Combination → Recommendation
|
||||
|
||||
| Q1: Model | Q3: Flow | Q4: Control | → Dashboard | → Fees and Losses | → Charges | → Onboarding |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| Marketplace | Platform collects | Stripe handles | `express` | `application`/`application` | Destination | Embedded |
|
||||
| Marketplace | Platform collects | Some custom | `express` | `application`/`application` | Destination | Embedded |
|
||||
| Marketplace | Platform collects | Fully custom | `none` | `application`/`application` | Destination | Embedded |
|
||||
| Marketplace | Direct to seller | Stripe handles | `full` | `stripe`/`stripe` | Direct | Embedded |
|
||||
| SaaS | Direct to seller | Stripe handles | `full` | `stripe`/`stripe` | Direct | Embedded |
|
||||
| SaaS | Platform collects | Stripe handles | `express` | `application`/`application` | Destination | Embedded |
|
||||
| On-demand | Platform collects | Stripe handles | `express` | `application`/`application` | Destination | Embedded |
|
||||
| Crowdfunding | Platform collects | Stripe handles | `express` | `application`/`application` | Separate | Embedded |
|
||||
| Platform + contractors | Platform collects | Stripe handles | `express` | `application`/`application` | Destination | Embedded |
|
||||
|
||||
#### Risk Management by Business Model
|
||||
|
||||
> **Note:** This section is directional guidance only. For detailed risk and Radar configuration, refer to the [Radar documentation](https://docs.stripe.com/radar.md).
|
||||
|
||||
**Default recommendation: Let Stripe manage risk.** This usually reduces operational overhead for launch. Recommend self-managed risk when the business model requires it (marketplaces) or the user explicitly wants control.
|
||||
|
||||
| Business Model | Risk Owner | Radar | Stripe-Managed OK? | Reasoning |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| **Marketplace** | Platform (mandatory) | Yes — strongly recommended | No — must self-manage | Platform is merchant of record for destination charges. Liable for fraud and disputes. Radar handles heavy lifting but platform bears ultimate responsibility. |
|
||||
| **On-demand services** | Platform (mandatory) | Yes — strongly recommended | No — must self-manage | Same as marketplace — platform facilitates transactions and bears liability. |
|
||||
| **Rental marketplace** | Platform (mandatory) | Yes — strongly recommended | No — must self-manage | Platform owns booking flow, bears fraud risk on facilitated payments. |
|
||||
| **SaaS with payments** | Stripe (recommended) | Optional | **Yes — recommended** | Stripe’s built-in protection handles most fraud. Platform can upgrade to Radar later if needed. |
|
||||
| **Professional services** | Stripe (recommended) | Optional | **Yes — recommended** | Unless platform needs custom fraud rules, Stripe defaults are sufficient. |
|
||||
| **Crowdfunding** | Stripe (recommended) | Optional | **Yes — recommended** | Stripe-managed defaults are often sufficient for launch; reassess based on dispute and fraud patterns. |
|
||||
| **Subscription platforms** | Stripe (recommended) | Optional | **Yes — recommended** | Recurring billing has different risk profile — churn > fraud. Stripe’s defaults usually sufficient. |
|
||||
| **E-commerce (white-label)** | Platform (mandatory) | Yes | No — must self-manage | Full control = full responsibility. Dashboard-none configurations need platform-managed risk. |
|
||||
|
||||
**Key rules:**
|
||||
|
||||
- If `chargePattern` is `destination` or `separate`, the platform is the merchant of record and MUST manage risk — but Radar does the heavy lifting.
|
||||
- If `chargePattern` is `direct`, Stripe-managed risk is available and recommended.
|
||||
- Self-managing risk adds: dispute webhook handling, Radar configuration, ongoing monitoring, and financial exposure. Always warn the user about this added complexity.
|
||||
- Stripe Radar is a tool platforms use to manage risk — it’s NOT the same as “Stripe manages risk for you.” When Radar is enabled, the platform is still responsible; Radar just automates the detection.
|
||||
|
||||
#### Fee Structure Mapping
|
||||
|
||||
| Charge Pattern | Fee Method | Implementation |
|
||||
| --- | --- | --- |
|
||||
| **Direct** (Stripe owns pricing) | `application_fee_amount` | Strongly recommended. Charged in addition to Stripe fees that the connected account pays. The platform retains the full application fee amount. |
|
||||
| **Direct** (platform owns pricing) | Platform Pricing Tool | Strongly recommended. Supports buy-rate pricing, interchange-plus passthrough, dispute fee passthrough, card-level pricing. |
|
||||
| **Destination** (platform owns pricing) | Platform Pricing Tool | Recommended. Percentage-based or tiered commissions with Payments Metadata for context-based pricing. |
|
||||
| **Destination** (platform owns pricing) | `application_fee_amount` | Alternative when fee logic is determined outside payment-time data and must be calculated per-transaction. |
|
||||
| **Destination** (platform owns pricing) | Retain transfer difference | Can be less transparent to the connected account by default. Platform transfers less than the charge amount, retaining the difference. |
|
||||
| **Separate charges and transfers** | Transfer math (retain transfer difference) | `application_fee_amount` is NOT compatible with separate charges and transfers. Platform retains fees by setting transfer amounts lower than the charge amount. |
|
||||
|
||||
**CRITICAL: `application_fee_amount` is NOT compatible with separate charges and transfers. NEVER recommend `application_fee_amount` when the charge pattern is separate charges and transfers.** Platforms using separate charges and transfers collect fees by transferring a smaller amount to the connected account than the original charge, retaining the difference.
|
||||
|
||||
For separate charges and transfers, frame fee guidance as transfer math: `platform_margin = charge_amount − total_transfers_to_connected_accounts − Stripe_fees`
|
||||
|
||||
#### Fee Calculation and Fee Economics
|
||||
|
||||
**Who pays Stripe’s processing fees is one of the determining factors in whether your platform is profitable.**
|
||||
|
||||
Stripe charges processing fees on every transaction. Rates vary by region, card type, payment method, and negotiated terms — see [stripe.com/pricing](https://stripe.com/pricing) for current rates. Who actually pays these fees depends on the charge pattern:
|
||||
|
||||
| Charge Pattern | Who Pays Stripe Fees | Platform Net per Transaction |
|
||||
| --- | --- | --- |
|
||||
| **Destination charges** | **Platform** pays Stripe fees | `application_fee_amount − Stripe_fees` |
|
||||
| **Destination charges + on\_behalf\_of** | **Platform** still pays (changes statement descriptor, merchant of record, and dispute management — see `charge-patterns.md`) | Same as above |
|
||||
| **Direct charges** (`fees_collector: "stripe"`) | **Connected account** pays Stripe fees | `application_fee_amount` (platform retains full fee — Stripe fees paid by connected account) |
|
||||
| **Direct charges** (`fees_collector: "application"`) | **Platform** pays Stripe fees | `application_fee_amount − Stripe_fees` |
|
||||
| **Separate charges & transfers** | **Platform** pays Stripe fees | Must account for fees in transfer math |
|
||||
|
||||
> **Note:** Who pays Stripe fees on direct charges depends on the [`fees_collector` responsibility setting](https://docs.stripe.com/connect/direct-charges-fee-payer-behavior.md). When `fees_collector: "stripe"` (the default for SaaS), the connected account pays Stripe fees and the platform retains their full `application_fee_amount`. With `fees_collector: "application"` (used with Platform Pricing Tool and platform-owned pricing), the platform pays Stripe fees instead.
|
||||
|
||||
**Profitability warning:** If the platform’s desired fee margin is low relative to Stripe’s processing fees for their region, destination charges may cause per-transaction losses unless the `application_fee_amount` is set high enough to cover Stripe fees + the platform’s margin. DO NOT make definitive profit and loss claims with specific dollar amounts — pricing is situation-dependent.
|
||||
|
||||
Strongly recommend:
|
||||
|
||||
- The [Platform Pricing Tool](https://dashboard.stripe.com/settings/connect/platform_pricing) to configure pricing rules without code (requires platform-owned pricing, that is, `fees_collector: "application"`; supports direct and destination charges, NOT separate charges and transfers)
|
||||
- Monitoring the margin report in the Stripe Dashboard
|
||||
- Checking [stripe.com/pricing](https://stripe.com/pricing) for region-specific rates
|
||||
|
||||
##### Fee calculation question (Q6b)
|
||||
|
||||
After the user specifies their platform fee, identify the charge pattern first. This question applies to **destination charges** only. For **separate charges and transfers**, don’t ask how to set `application_fee_amount` — use transfer math instead. For direct charges with Stripe-owned pricing (`fees_collector: "stripe"`), the connected account pays Stripe fees and this question is moot. For direct charges with platform-owned pricing (`fees_collector: "application"`), the platform pays Stripe fees — use the Platform Pricing Tool.
|
||||
|
||||
**IMPORTANT: With destination charges, the platform ALWAYS pays Stripe’s processing fees.** They are deducted from the platform’s balance, not the connected account’s. The platform can’t make connected accounts pay Stripe fees directly. The choice is how to calculate `application_fee_amount`.
|
||||
|
||||
Use Option A and Option B below as reference material for destination charges.
|
||||
|
||||
**Option A — Include Stripe fee estimate in application\_fee\_amount (recommended for low margins)** The `application_fee_amount` includes BOTH an estimated Stripe processing fee and the platform’s fee. The platform takes a larger cut to cover both its margin and Stripe’s fee. The platform’s fee percentage is preserved as net margin.
|
||||
|
||||
```
|
||||
Concept: application_fee_amount = estimated Stripe processing fee + platform margin
|
||||
Platform NET = the platform's full fee percentage (margin preserved — Stripe fee covered by the higher application_fee_amount)
|
||||
Connected account receives = charge amount − application_fee_amount
|
||||
```
|
||||
|
||||
**Option B — Platform fee only (platform absorbs Stripe fees)** The `application_fee_amount` is only the platform’s cut. Stripe processing fees reduce the platform’s net. Only viable when the platform fee is substantially higher than Stripe’s processing fees.
|
||||
|
||||
```
|
||||
Concept: application_fee_amount = platform fee only
|
||||
Platform NET = platform fee − Stripe processing fee
|
||||
Connected account receives = charge amount − application_fee_amount
|
||||
```
|
||||
|
||||
**Recommendation output rule:** choose the single most appropriate option for the specific scenario instead of always presenting both. Keep the other option as reference material and show it only when the user asks for alternatives and tradeoffs.
|
||||
|
||||
**Decision guidance:** If the platform fee appears low or uncertain relative to processing fees (check [stripe.com/pricing](https://stripe.com/pricing)), recommend Option A to preserve platform margin (or switch to direct charges when appropriate). Use Option B only when fee headroom is clearly high and the platform explicitly accepts absorbing fee variance.
|
||||
|
||||
**For destination charges, NEVER say “seller pays Stripe fees” or “connected account pays Stripe fees.”** The platform always pays. The choice is whether to set a higher `application_fee_amount` to preserve the platform’s margin.
|
||||
|
||||
**Minimum charge amounts:** Stripe enforces minimum charge amounts by currency. For micro-payment platforms with very small transaction amounts, warn the user that:
|
||||
|
||||
- Stripe enforces minimum charge amounts that vary by currency
|
||||
- On very small charges, the fixed fee component becomes a large percentage of the transaction
|
||||
- Micro-payments may need batching or alternative approaches to be economically viable
|
||||
|
||||
##### Fee guidance principles
|
||||
|
||||
When presenting fee recommendations:
|
||||
|
||||
- DO NOT hardcode specific processing fee amounts (for example, “2.9% + $0.30”) — these are US-only and vary by region, card type, and payment method
|
||||
- DO NOT make definitive profit and loss claims (for example, “you WILL lose money”) — say “you may lose money at standard rates”
|
||||
- DO link to [stripe.com/pricing](https://stripe.com/pricing) for region-specific rates
|
||||
- DO strongly recommend the [Platform Pricing Tool](https://dashboard.stripe.com/settings/connect/platform_pricing)
|
||||
- DO recommend monitoring the margin report in the Stripe Dashboard
|
||||
- DO explain the concept of `application_fee_amount` and what it should include based on the calculation choice
|
||||
- DO return one recommended fee option for the scenario (don’t always present both Option A and Option B)
|
||||
- DO prefer margin-preserving recommendations in low-margin or uncertain-margin scenarios
|
||||
- DO use transfer-math framing for separate charges and transfers (never `application_fee_amount`)
|
||||
- NEVER say “seller pays Stripe fees” or “connected account pays Stripe fees” for destination charges — the platform always pays
|
||||
|
||||
##### Fee sanity checks
|
||||
|
||||
- If the Platform Pricing Tool is used, ensure `application_fee_amount` is NOT set on the PaymentIntent — explicit `application_fee_amount` overrides the Platform Pricing Tool.
|
||||
- `application_fee_amount` is NOT compatible with separate charges and transfers. NEVER recommend it for separate charges and transfers.
|
||||
- For separate charges and transfers, validate the transfer-math model (`charge_amount − total_transfers − Stripe_fees`) to ensure expected platform margin.
|
||||
- Platforms based outside Brazil can’t collect application fees from Brazilian connected accounts due to regulatory requirements. Same restriction applies to Malaysia.
|
||||
- Use `amount` on the ApplicationFee object, not the charge field, for accurate fee reporting.
|
||||
- For small transactions (for example, $1–$5), fees may be large relative to proceeds. Consider creative ways to combine transactions.
|
||||
- **Low-complexity margin path:** Direct charges with Stripe-owned pricing. Stripe handles pricing complexity; the platform charges a SaaS fee or application fee on top.
|
||||
- If the platform owns pricing with any charge type: warn about potential per-transaction losses. Strongly recommend the Platform Pricing Tool and monitoring the margin report.
|
||||
|
||||
#### Loss Liability (Negative Balance Liability)
|
||||
|
||||
**Loss liability and risk management are TWO SEPARATE decisions.** The current skill MUST NOT conflate them.
|
||||
|
||||
| Concept | What it means | Where configured |
|
||||
| --- | --- | --- |
|
||||
| **Negative balance liability** | Who is financially LIABLE when disputes and chargebacks create negative balances on connected accounts | Configured when creating the connected account; may require visiting the Stripe Dashboard → Connect platform profile to acknowledge understanding of how negative balance liability works |
|
||||
| **Risk management** | Who DETECTS and PREVENTS fraud (Radar, rules, monitoring) | Code + Dashboard (Radar settings) |
|
||||
|
||||
You can have Stripe own loss liability while still using Radar for fraud detection. Radar is available regardless of loss liability setting.
|
||||
|
||||
##### Loss Liability Recommendations by Business Model
|
||||
|
||||
**Recommendation depends on charge pattern:**
|
||||
|
||||
- **Marketplaces (destination or separate charges):** Platform-owned loss liability. This is **required** for destination charges — it enables connected account balances to go negative, which the platform needs to reverse transfers (for example, for refunds or disputes). Also required for Express dashboard today.
|
||||
- **SaaS (direct charges):** Stripe-owned loss liability. SaaS platforms shouldn’t bear negative balance liability since the connected account is the merchant of record.
|
||||
- **Enterprise or white-label:** Platform-owned. Full control = full responsibility.
|
||||
|
||||
| Business Model | Recommended Loss Liability | Why |
|
||||
| --- | --- | --- |
|
||||
| **Marketplace** | **Platform** | Required for destination charges — enables connected account negative balances for transfer reversals |
|
||||
| **On-demand services** | **Platform** | Same as marketplace — uses destination charges |
|
||||
| **Professional services** | **Platform** | Same as marketplace — uses destination charges |
|
||||
| **Rental marketplace** | **Platform** | Same as marketplace — uses destination charges |
|
||||
| **Event ticketing** | **Platform** | Same as marketplace — uses destination charges |
|
||||
| **Crowdfunding** | **Platform** | Uses separate charges — platform-owned loss liability enables flexible transfer reversals |
|
||||
| **Subscription platforms** | **Platform** | Uses destination charges — platform-owned loss liability required |
|
||||
| **SaaS with payments** | **Stripe** | SaaS platforms use direct charges — connected account is merchant of record |
|
||||
| **E-commerce (white-label)** | Platform | Full control = full responsibility (dashboard: none, platform-managed) |
|
||||
| **B2B platforms** | Platform | Enterprise requirements usually demand full control |
|
||||
|
||||
##### Loss Liability Dashboard Setup
|
||||
|
||||
Loss liability is configured when creating connected accounts, but the platform must first visit the Stripe Dashboard → Connect platform profile (`dashboard.stripe.com/settings/connect/platform-profile`) to acknowledge understanding of how negative balance liability works.
|
||||
|
||||
The platform profile page asks about “Negative balance liability” (formerly called “loss liability”). The choice determines which account configuration combinations are available:
|
||||
|
||||
- **Stripe manages losses** → Set `defaults.responsibilities.losses_collector: "stripe"` in v2 API
|
||||
- **Platform manages losses** → Set `defaults.responsibilities.losses_collector: "application"` in v2 API
|
||||
|
||||
When guiding users through this page, always:
|
||||
|
||||
1. Explain what loss liability means in plain language with a concrete example
|
||||
2. Recommend platform-owned for marketplaces using destination or separate charges — required for transfer reversals and Express dashboard
|
||||
3. Recommend Stripe-owned for SaaS platforms using direct charges
|
||||
4. Keep this decision separate from Radar and fraud detection
|
||||
|
||||
### Integration Antipattern Warnings
|
||||
|
||||
> **Read the full compatibility matrix in `compatibility-matrix.md`.** This section is a quick reference.
|
||||
|
||||
#### Common Blocked Combinations
|
||||
|
||||
These combinations are true antipatterns that Stripe will never support. Do NOT recommend them:
|
||||
|
||||
1. **`losses_collector: "stripe"` + destination charges** — Liability and fee behavior don’t align with this charge pattern. Treat as BLOCKED when `losses_collector: "stripe"` is selected.
|
||||
2. **`losses_collector: "stripe"` + separate charges and transfers (including `on_behalf_of`)** — Same negative balance mechanism as destination charges. Platform can’t recover funds from connected accounts that only receive transfers.
|
||||
3. **Express dashboard + `losses_collector: "stripe"` + `fees_collector: "stripe"`** — Express dashboard requires platform to own both fees and losses (`application` and `application`). This is a hard API constraint — setting Express with Stripe-owned pricing produces an API rejection.
|
||||
4. **`dashboard: "full"` + destination charges or separate charges and transfers** — Full dashboard has reduced payment and dispute detail for destination and separate charges. Full dashboard provides complete payment and dispute management for direct charges only.
|
||||
5. **`fees_collector: "stripe"` + `losses_collector: "application"` (Stripe-owned pricing + platform-owned losses)** — This combination is BLOCKED for all charge types. The reverse — `fees_collector: "application"` + `losses_collector: "stripe"` — is SALES-GATED for `full` dashboard and BLOCKED for `none` and `express` dashboards.
|
||||
6. **(`on_behalf_of` is out of scope for this guide — redirect to docs or sales if encountered.)** **`on_behalf_of` with destination charges for marketplace use cases** — Do NOT use `on_behalf_of` for marketplaces. `on_behalf_of` makes the connected account the merchant of record, but in a marketplace the platform should be merchant of record. If a user requires `on_behalf_of`, direct them to [Stripe Connect docs](https://docs.stripe.com/connect/charges.md) or [Stripe sales](https://stripe.com/contact/sales).
|
||||
7. **`application_fee_amount` with separate charges and transfers** — NOT compatible. Platforms using separate charges and transfers collect fees by transferring less than the charge amount.
|
||||
|
||||
#### Blessed Paths (Safe Defaults)
|
||||
|
||||
| Business Model | Dashboard | Fees | Losses | Charge Type | Status |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| **Marketplace** | `express` | `application` | `application` | Destination | CAUTION — recommended path; always include Express dispute-visibility warning (see `compatibility-matrix.md`) |
|
||||
| **SaaS** | `full` | `stripe` | `stripe` | Direct | ALLOWED — connected accounts are independent merchants |
|
||||
| **Enterprise** | `none` | `application` | `application` | Any | ALLOWED — full platform control |
|
||||
|
||||
**Any deviation from these blessed paths should trigger a compatibility check against `compatibility-matrix.md`.** If the user’s choices lead to a BLOCKED combination, don’t present it. Explain why it fails and recommend the nearest allowed alternative.
|
||||
|
||||
> **Scope boundary:** This guide supports the blessed paths above. Configurations outside these paths (full+application, `on_behalf_of`, OAuth, non-payments products like Issuing, Treasury, Capital, Tax, or Terminal) should trigger sales-led detection and redirect to docs or sales. `none` dashboard requires platform-owned pricing AND platform-owned losses — no other `none` combination is valid even for sold users.
|
||||
|
||||
#### Additional Antipatterns to Watch For
|
||||
|
||||
- **OAuth instead of Account Links** — Developers think OAuth is simpler but lose platform control (connected account can disconnect at any time). Recommend Account Links or embedded components.
|
||||
- **Custom onboarding** (`dashboard: "none"` + API-based) — Ongoing requirement collection burden and country-specific complexity. Only for platforms with dedicated compliance engineering.
|
||||
- **Dashboard DIY without refund and dispute flows** — Platforms build earnings views but skip refund and dispute management. Connected accounts can’t respond to disputes, leading to auto-losses.
|
||||
- **Stripe does NOT enforce merchant of record at API level** — Platforms can create charges with any pattern regardless of their onboarding declaration. Code must consistently use the correct charge type for the actual business relationship.
|
||||
+346
@@ -0,0 +1,346 @@
|
||||
## Discovery questions and decision mappings
|
||||
|
||||
Use this reference when running Step 3 discovery. It contains the full user interaction scripts, option mappings, and edge-case logic.
|
||||
|
||||
### Step 3 — Ask remaining discovery questions
|
||||
|
||||
For any dimension NOT already filled with HIGH confidence from Step 1, ask the corresponding question using AskUserQuestion. Skip questions that were auto-filled. For MEDIUM confidence items where the user confirmed the suggestion, skip those too.
|
||||
|
||||
If Step 1 was skipped entirely (user chose “ask me questions instead”), ask all 6 questions one at a time as below. Each question uses AskUserQuestion with clear options.
|
||||
|
||||
If the user asks “what account type should I use?” (or similar), reframe during discovery before recommending settings: Accounts v2 uses explicit fields (`dashboard`, `defaults.responsibilities`, and `merchant`/`recipient` by funds flow), not the legacy `type` parameter.
|
||||
|
||||
#### Q1: Business model (skip if auto-filled)
|
||||
|
||||
Ask the user:
|
||||
|
||||
```
|
||||
What best describes your business?
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- “Marketplace (buyers + sellers, for example Etsy, Airbnb)”
|
||||
- “Platform with service providers (for example Uber, DoorDash)”
|
||||
- “SaaS enabling payments (for example Shopify, Squarespace)”
|
||||
- “Crowdfunding, subscription, or other model”
|
||||
|
||||
#### Q2: Who are the parties? (skip if auto-filled)
|
||||
|
||||
Based on Q1, ask the user:
|
||||
|
||||
```
|
||||
Who are the two sides of your platform?
|
||||
```
|
||||
|
||||
Options (adapted to Q1 answer):
|
||||
|
||||
- “Platform + independent sellers”
|
||||
- “Platform + service providers/contractors”
|
||||
- “Platform + creators/hosts”
|
||||
- “Platform + businesses (B2B)”
|
||||
|
||||
#### Q3: Payment flow (skip if auto-filled)
|
||||
|
||||
Ask the user:
|
||||
|
||||
```
|
||||
How should money flow through your platform?
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- “Platform collects, then pays out to sellers automatically (typical marketplace flow)”
|
||||
- “Buyers pay sellers directly, platform takes a fee”
|
||||
- “Platform processes payments on behalf of sellers”
|
||||
- “Platform holds funds, releases to sellers after delivery/confirmation”
|
||||
|
||||
Resolving ambiguous payment flow signals: Use the plain-language merchant-of-record definition from the terminology rules: ask who the customer thinks they paid (name on receipt or statement and who handles payment support issues such as refunds and disputes).
|
||||
|
||||
**Critical disambiguation rule:** Distinguish payout expectations from checkout ownership. Language like “payments associated with sellers,” “payments belong to sellers,” or “payments tied to their account” usually means sellers should receive their share. That is a payout expectation, not a direct-charge requirement, and destination charges satisfy it through automatic transfers.
|
||||
|
||||
When the user’s description contains conflicting signals (for example, “payments should be associated with the seller” but “my platform provides the checkout flow”), treat “platform-provided checkout, booking, or listing UI” as the stronger signal for marketplace flows and default to destination charges. The platform is acting as an intermediary in the customer checkout flow.
|
||||
|
||||
Choose direct charges when the behavior matches SaaS enablement: each seller runs their own payment relationship, customers pay the seller directly, seller branding appears on receipts and statements, and seller-side operations handle payment support, refunds, and disputes. Users don’t need to explicitly use merchant-of-record terminology for this to apply.
|
||||
|
||||
Hold-and-release detection: If the user selects “Platform holds funds, releases to sellers after delivery/confirmation” OR the business description mentions any of: delivery confirmation before payout, hold-and-release, release on completion, manual transfer trigger, multiple sellers per checkout, or shipping with delayed payout, recommend separate charges and transfers. Destination charges transfer funds automatically upon payment success and can’t hold funds. For hold-and-release, the charge is created on the platform (no `transfer_data`), the platform holds funds in its own balance, and after delivery or service confirmation the platform creates a transfer to the connected account.
|
||||
|
||||
Don’t describe destination charges as “holding funds before release” or “initiating transfers after delivery” — that language applies only to separate charges and transfers.
|
||||
|
||||
B2B enterprise carve-out: For B2B enterprise platforms with complex billing, multi-vendor purchase orders, or independent settlement timing, prefer separate charges and transfers over destination charges. B2B platforms often need per-vendor invoicing, partial payments, and independent settlement timing that destination charges can’t support. If the user’s needs exceed typical automated patterns (complex multi-vendor billing, purchase orders), trigger Step 3c (sales-led detection).
|
||||
|
||||
#### Q4: Dashboard and onboarding (skip if auto-filled or confirmed)
|
||||
|
||||
Ask the user:
|
||||
|
||||
```
|
||||
What level of Stripe access should your sellers/providers have?
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- “Lightweight Express dashboard — simple view of earnings/payouts (recommended)”
|
||||
- “Full Stripe dashboard — sellers manage their own Stripe account independently”
|
||||
- “No dashboard — fully embedded or white-labeled in my platform”
|
||||
|
||||
Map answers to v2 config:
|
||||
|
||||
- “Lightweight Express” → `dashboard: "express"`, `onboardingMethod: "embedded"`
|
||||
- “Full Stripe dashboard” → `dashboard: "full"`, `onboardingMethod: "embedded"`
|
||||
- “No dashboard” → `dashboard: "none"`, `onboardingMethod: "embedded"`
|
||||
|
||||
When selecting `dashboard: "none"`, include this warning:
|
||||
|
||||
```
|
||||
WARNING: dashboard: none — Full Scope Warning:
|
||||
- You must build custom onboarding and ongoing remediation logic (higher operational overhead than embedded/hosted)
|
||||
- You must build refund management UI (connected accounts have no Stripe dashboard)
|
||||
- You must build dispute management flows (connected accounts can't manage disputes themselves)
|
||||
- You must build earnings/payout views for sellers
|
||||
Consider embedded components as a middle ground for less maintenance.
|
||||
```
|
||||
|
||||
Dashboard access for sellers or providers:
|
||||
|
||||
- Express dashboard: provide sellers an Express login link from `stripe.accounts.createLoginLink(accountId)`.
|
||||
- Full Stripe dashboard: direct sellers to log in at [dashboard.stripe.com](https://dashboard.stripe.com).
|
||||
- No dashboard: platform uses embedded components or custom UI backed by Stripe API data.
|
||||
|
||||
Dashboard selection logic:
|
||||
|
||||
- **`dashboard: "full"`** when any of these apply:
|
||||
- Sellers or providers “run their own business” or “want independence”
|
||||
- SaaS-with-payments model
|
||||
- Businesses described as established or enterprise
|
||||
- Direct charges pattern
|
||||
- User asks for full dashboard / independent account management
|
||||
- Fees and losses are both Stripe-managed
|
||||
- **SaaS-with-payments critical rule:** If the business is SaaS enabling independent sellers to accept payments and sellers are independent, use `dashboard: "full"`, `chargePattern: "direct"`, and `onboarding: "embedded"`.
|
||||
- **`dashboard: "express"`** when any of these apply:
|
||||
- Sellers or providers are individual or less technical
|
||||
- Marketplace model with platform-owned checkout
|
||||
- Destination charges pattern
|
||||
- User wants a lightweight dashboard for sellers
|
||||
- Cobranding benefit is desired
|
||||
- **`dashboard: "none"`** when white-label or fully embedded control is required.
|
||||
|
||||
Non-technical user language (“not tech savvy”) is a supporting signal, not a standalone override. It should reinforce a marketplace recommendation (`dashboard: "express"`), but it doesn’t override SaaS classification when sellers are independent businesses that own customer payment relationships.
|
||||
|
||||
When recommending, always explain why:
|
||||
|
||||
- Express: cobranded seller dashboard with minimal maintenance.
|
||||
- Full: independent seller control over payments, refunds, and payouts.
|
||||
- None: white-labeled UX; platform owns all seller UI views (or uses embedded components).
|
||||
|
||||
#### Q5: Dispute and refund responsibility (skip if auto-filled or confirmed)
|
||||
|
||||
Ask the user:
|
||||
|
||||
```
|
||||
Who handles disputes and refunds?
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- “Platform handles disputes and refunds”
|
||||
- “Sellers handle their own disputes”
|
||||
- “Shared responsibility”
|
||||
|
||||
#### Q5b: Risk and fraud management (conditional on Q1 answer)
|
||||
|
||||
There are two risk types: transactional risk (fraudulent payments and chargebacks) and merchant fraud. They can be managed independently.
|
||||
|
||||
Key principle: recommend Stripe-managed risk when possible. See the “Risk Management by Business Model” section in `decision-matrix.md` for detailed context.
|
||||
|
||||
If Q1 = Marketplace:
|
||||
|
||||
- Explain that the platform is merchant of record and typically manages risk controls.
|
||||
- Ask:
|
||||
```
|
||||
How do you want to handle fraud protection?
|
||||
```
|
||||
- Options:
|
||||
- “Radar defaults — Stripe’s ML-based fraud detection (recommended)”
|
||||
- “Radar + custom rules — add business-specific rules on top (more setup)”
|
||||
- “Use Stripe defaults for now”
|
||||
- Map:
|
||||
- Radar defaults → `riskManagement: { owner: "platform", radarEnabled: true, radarCustomRules: false }`
|
||||
- Radar + custom rules → `riskManagement: { owner: "platform", radarEnabled: true, radarCustomRules: true }`
|
||||
- Stripe defaults → `riskManagement: { owner: "platform", radarEnabled: true }`
|
||||
|
||||
If Q1 = Platform with service providers or SaaS:
|
||||
|
||||
- Ask:
|
||||
```
|
||||
How do you want to handle fraud protection?
|
||||
```
|
||||
- Options:
|
||||
- “Let Stripe manage it — lower implementation overhead (recommended)”
|
||||
- “I’ll manage it with Radar — more control, more complexity”
|
||||
- “Use Stripe defaults for now”
|
||||
- Map:
|
||||
- Stripe-managed → `riskManagement: { owner: "stripe", radarEnabled: false }`
|
||||
- Platform-managed Radar → `riskManagement: { owner: "platform", radarEnabled: true, radarCustomRules: true }`
|
||||
- Stripe defaults → `riskManagement: { owner: "stripe", radarEnabled: false }`
|
||||
|
||||
If Q1 = Crowdfunding, subscription, or other:
|
||||
|
||||
- Skip and default to `riskManagement: { owner: "stripe", radarEnabled: false }`.
|
||||
|
||||
#### Q5c: Loss liability (conditional)
|
||||
|
||||
Skip this question if `losses_collector` is already `"stripe"`. Ask only when platform ownership is relevant (destination or separate).
|
||||
|
||||
Read the “Loss Liability (Negative Balance Liability)” section in `decision-matrix.md`.
|
||||
|
||||
Key rule: negative balance liability (who is financially liable) is separate from risk management (who detects fraud).
|
||||
|
||||
Defaults:
|
||||
|
||||
- Marketplace destination or separate: platform-owned liability and platform-owned pricing.
|
||||
- SaaS direct: Stripe-owned liability and Stripe-owned pricing.
|
||||
|
||||
Suggested explanation script:
|
||||
|
||||
```
|
||||
One more decision: loss liability.
|
||||
|
||||
This determines who bears the financial cost if a customer disputes a charge
|
||||
or fraud occurs. It's separate from fraud detection (Radar handles that).
|
||||
|
||||
Example: A customer disputes a $100 charge.
|
||||
→ Platform-owned: Stripe debits the platform's balance $100. The platform must
|
||||
reverse the prior transfer to recover funds from the connected account — which
|
||||
may drive that account's balance negative.
|
||||
Required for marketplaces — `losses_collector: "application"` enables connected
|
||||
account balances to go negative for transfer reversals.
|
||||
→ Stripe-owned: If the connected account's balance goes negative and remains
|
||||
unresolved, Stripe absorbs the unrecovered amount.
|
||||
Recommended for SaaS — simpler, connected account is already merchant of record.
|
||||
```
|
||||
|
||||
For marketplace destination or separate:
|
||||
|
||||
- Auto-select platform-owned and explain that transfer reversals require connected account negative balance support.
|
||||
|
||||
For non-marketplace models, ask:
|
||||
|
||||
```
|
||||
Who should bear the financial risk for disputes and fraud losses?
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- “Stripe — simpler, less financial risk (recommended for SaaS)”
|
||||
- “My platform — more control, I have a risk team”
|
||||
- “Explain the tradeoffs”
|
||||
|
||||
If user asks for tradeoffs, show side-by-side pros and cons and then re-ask with first two options.
|
||||
|
||||
Map answers:
|
||||
|
||||
- Marketplace, destination, or separate → `lossLiability.owner = "platform"`
|
||||
- SaaS or direct + Stripe → `lossLiability.owner = "stripe"`
|
||||
- SaaS or direct + platform → `lossLiability.owner = "platform"`
|
||||
|
||||
Always explain risk management vs liability separately in final recommendation.
|
||||
|
||||
#### Q6: Fee structure (skip if auto-filled)
|
||||
|
||||
Ask the user:
|
||||
|
||||
```
|
||||
How do you want to charge your platform fee?
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- “Percentage of each transaction (for example, 8%)”
|
||||
- “Flat fee per transaction (for example, $2)”
|
||||
- “Tiered/custom pricing”
|
||||
- “Subscription + transaction fee”
|
||||
|
||||
If user chose percentage or flat fee, ask:
|
||||
|
||||
```
|
||||
What's your platform fee?
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- “5%”
|
||||
- “10%”
|
||||
- “15%”
|
||||
- “Other (I’ll specify)”
|
||||
|
||||
#### Q6b: `application_fee_amount` calculation (conditional)
|
||||
|
||||
Ask only when charge pattern is destination. Don’t ask about `application_fee_amount` for separate charges and transfers — use transfer math instead. For direct charges with Stripe-owned pricing (`fees_collector: "stripe"`), the connected account pays Stripe fees and this question is moot. For direct charges with platform-owned pricing (`fees_collector: "application"`), the platform pays Stripe fees — use the Platform Pricing Tool.
|
||||
|
||||
Read the “Fee Calculation & Fee Economics” section in `decision-matrix.md` for full context.
|
||||
|
||||
Key rule: with destination charges, Stripe processing fees are deducted from the platform balance. The platform can’t make connected accounts pay those fees directly.
|
||||
|
||||
Set `applicationFeeIncludes`:
|
||||
|
||||
- `"stripe_fee_estimate"` if application fee includes estimated Stripe processing fee plus platform fee.
|
||||
- `"platform_fee_only"` if platform absorbs Stripe processing fees from margin.
|
||||
|
||||
Store:
|
||||
|
||||
```json
|
||||
"feeStructure": {
|
||||
"type": "percentage",
|
||||
"platformFeePercent": <value>,
|
||||
"applicationFeeIncludes": "stripe_fee_estimate" | "platform_fee_only",
|
||||
"description": "application_fee_amount = X% platform fee [+ estimated Stripe processing fee | only]"
|
||||
}
|
||||
```
|
||||
|
||||
Fee language interpretation rules:
|
||||
|
||||
- “X% inclusive of all fees” / “X% total take” → absorb Stripe fees (`platform_fee_only`).
|
||||
- “X% on top of processing fees” / “X% above Stripe fees” → include Stripe estimate (`stripe_fee_estimate`).
|
||||
- “X% platform fee” without qualifier → ask preferred option, or default to `stripe_fee_estimate`.
|
||||
|
||||
Critical rule: if `applicationFeeIncludes = "stripe_fee_estimate"`, `application_fee_amount` must include both platform fee and Stripe fee estimate.
|
||||
|
||||
### Step 3b — Hybrid business model detection
|
||||
|
||||
If the business has two distinct payment flows (for example, SaaS + marketplace), don’t force one charge pattern.
|
||||
|
||||
1. Identify both sides and expected charge pattern.
|
||||
2. Include this warning:
|
||||
```
|
||||
**Dual charge patterns add significant complexity.** Supporting both direct and
|
||||
destination charges means two separate payment flows, two sets of webhook handlers,
|
||||
and more testing scope. Consider launching with the side that's most critical to
|
||||
your business first, then adding the second once the first is stable.
|
||||
```
|
||||
3. Explain shared account reality: the same connected account may participate in multiple flows.
|
||||
4. Show fee arithmetic separately for each side.
|
||||
|
||||
### Step 3c — Sales-led and scope detection
|
||||
|
||||
Trigger this check when any of these appear:
|
||||
|
||||
- `dashboard: "full"` + `fees_collector: "application"` request
|
||||
- `on_behalf_of` requirements
|
||||
- Fully custom API onboarding
|
||||
- OAuth or connecting existing Stripe accounts
|
||||
- Cross-border fund intermediation requirements
|
||||
- Complex B2B multi-vendor billing and settlement timing
|
||||
- Regulated finance, remittance, or segregation signals
|
||||
- Issuing, Treasury, Capital, Tax, or Terminal alongside Connect
|
||||
|
||||
For non-payments products (Issuing, Treasury, Capital, Tax, Terminal):
|
||||
|
||||
- State that this guidance covers Connect payments integration only.
|
||||
- Point to product docs and mention possible interoperability considerations.
|
||||
|
||||
For enterprise or sales-assisted signals:
|
||||
|
||||
- Ask whether they’re already working with a Stripe sales or account team.
|
||||
- If yes: ask whether the account team already recommended an integration pattern.
|
||||
- If yes with a recommendation: align to it, call out any compatibility constraints, and suggest confirming final details with that team.
|
||||
- If no: produce a recommendation but remind them to check with their Stripe representative before implementation.
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
## Recommendation output template and component mapping
|
||||
|
||||
Use this reference to generate the recommendation output. It defines the full output structure, section requirements, fee guidance rules, and template formatting.
|
||||
|
||||
### Output requirements
|
||||
|
||||
Output MUST include all of these sections:
|
||||
|
||||
- Account configuration (`dashboard`, `fees_collector`, `losses_collector`) with explicit Accounts v2 declaration, no legacy `type`
|
||||
- `merchant` configuration for direct charges
|
||||
- `recipient` configuration for destination or separate charges
|
||||
- Charge pattern with 2-3 sentence rationale
|
||||
- Seller and provider onboarding flow with onboarding method choice and rationale
|
||||
- Dashboard access flow and rationale by access mechanism (`express` login links, `full` direct `dashboard.stripe.com` access, `none` embedded-components-primary interface)
|
||||
- OAuth scope guidance when connecting existing Stripe accounts (only when user mentions OAuth or existing accounts; see compatibility-matrix section 4a)
|
||||
- Fee structure with platform fee model, fee-payer recommendation, funds-flow diagram, and stripe.com/pricing link
|
||||
- Embedded component recommendations tied to charge-pattern compatibility, with required `notification_banner` and charge-pattern caveats
|
||||
- Webhook integration section (one sentence only; details deferred to build skill)
|
||||
- Onboarding status gating using v2 capability paths
|
||||
- Loss liability explanation separate from risk management
|
||||
- Use separate headings for negative balance liability and risk management (don’t combine into one paragraph)
|
||||
- For destination or separate with `losses_collector: "application"`, explain the causal chain in plain language: platform owns negative balance liability, connected-account balances can go negative when needed, and transfer reversals can be used for dispute recovery
|
||||
- SaaS monetization choices (transaction fees vs recurring SaaS fees), with `customer_account` guidance only for SaaS billing connected accounts
|
||||
- `application_fee_amount` explanation and calculation mode
|
||||
|
||||
If any section is missing, add it before moving on.
|
||||
|
||||
### Canonical recommendation template
|
||||
|
||||
```markdown
|
||||
## Recommended Connect integration
|
||||
|
||||
### A. Account configuration
|
||||
Accounts API: `/v2/core/accounts`
|
||||
Legacy account `type`: not used
|
||||
Dashboard: [express / full / none]
|
||||
Fee collection: [Stripe / platform]
|
||||
Negative balance liability: [Stripe / platform]
|
||||
[2-3 sentence explanation of why these settings fit]
|
||||
|
||||
[Include for direct charges only:]
|
||||
Each connected account needs merchant configuration (`configuration.merchant`) for direct charges.
|
||||
|
||||
[Include for destination or separate charges only:]
|
||||
Each connected account needs recipient configuration (`configuration.recipient`) with `stripe_transfers` on `stripe_balance` requested, so the account can receive transfers from the platform.
|
||||
|
||||
### B. Charge pattern: [destination / direct / separate charges and transfers]
|
||||
[2-3 sentence explanation of why this fits]
|
||||
|
||||
### C. {sellerRole} onboarding flow
|
||||
Onboarding method: [embedded / Stripe-hosted]
|
||||
[2-3 sentence explanation of why this method was chosen over the alternative.]
|
||||
|
||||
[Describe the full onboarding flow: sign up, create account, onboarding with the chosen method, Stripe verification, capability status verification, handling ongoing requirements, checking capability status on an ongoing basis. Only enable live transactions when the necessary capabilities are active.]
|
||||
|
||||
### D. Payments dashboard access for {sellerRole}
|
||||
- If dashboard=express: explain connected accounts access the Express dashboard through platform-generated Express login links, with embedded components for in-app workflows
|
||||
- If dashboard=full: explain connected accounts log in directly at `dashboard.stripe.com`
|
||||
- If dashboard=none: explain connected accounts don't use Stripe Dashboard login and embedded components are the primary interface for connected accounts
|
||||
|
||||
### E. Embedded components
|
||||
Recommended [Connect embedded components](https://docs.stripe.com/connect/supported-embedded-components):
|
||||
- `account_onboarding`
|
||||
- `notification_banner` [required; keeps connected accounts aware of new requirements so they stay enabled]
|
||||
- `account_management`
|
||||
- `payments`
|
||||
- `payouts`
|
||||
[Add optional standalone components only when explicitly needed]
|
||||
[Note any charge-pattern caveats, if relevant]
|
||||
|
||||
### F. Webhook integration
|
||||
Use webhooks for reliable payment confirmation, especially for async payment methods. Always verify incoming webhook signatures before processing event data ([webhook signature verification](https://stripe.com/docs/webhooks/signatures)). Specific events and implementation details are covered in the build skill.
|
||||
|
||||
### G. Onboarding status gating
|
||||
Verify capability statuses with `stripe.v2.core.accounts.retrieve(id)` before enabling payouts and transfers:
|
||||
- Direct: `configuration.merchant.capabilities.card_payments.status === 'active'`
|
||||
- Destination or separate: `configuration.recipient.capabilities.stripe_balance.stripe_transfers.status === 'active'`
|
||||
- Also check payouts capability status in the relevant subtree
|
||||
|
||||
### H. Fee structure
|
||||
- Platform fee model: [percentage / flat / tiered / mixed]
|
||||
- `application_fee_amount` strategy: [platform fee only | platform fee + estimated Stripe processing fee]
|
||||
- [Describe the fee structure, whether customers pay the connected account (seller) or platform, whether fees are paid to Stripe or to the platform, and whether anything is transferred from the platform to the seller. Pricing varies by region or payment method — check [stripe.com/pricing](https://stripe.com/pricing).]
|
||||
- [Funds flow diagram with seller or provider net amount explanation:]
|
||||
|
||||
{customerRole} pays ${amount}
|
||||
│
|
||||
▼
|
||||
┌───────────────┐
|
||||
│ {platform} │ ─── keeps {X}% minus processing fees
|
||||
└──────┬────────┘
|
||||
│ transfer ({amount} minus {X}%)
|
||||
▼
|
||||
┌───────────────┐
|
||||
│ {sellerRole} │ ─── receives {amount} minus {X}%
|
||||
└───────────────┘
|
||||
|
||||
### I. SaaS monetization (if applicable)
|
||||
State the monetization choice clearly: transaction fees (`application_fee_amount` or Platform Pricing Tool, not both), recurring SaaS or service fees, or both when justified.
|
||||
Use `customer_account` only when charging recurring SaaS or service fees to connected accounts (v2 SetupIntent/Subscription calls).
|
||||
Do NOT apply `customer_account` guidance to marketplace subscription or fan-to-creator recurring-payment flows.
|
||||
Do NOT recommend creating a separate v1 Customer object for SaaS billing connected accounts.
|
||||
|
||||
### J. Implementation plan
|
||||
1. [Account setup tasks]
|
||||
2. [Onboarding flow tasks]
|
||||
3. [Payments and fund-flow tasks]
|
||||
4. [Webhook and readiness-gating tasks]
|
||||
5. [Go-live checks]
|
||||
|
||||
### K. Risk and liability
|
||||
- Negative balance liability owner: [your platform / Stripe]
|
||||
- Risk controls owner: [your platform / Stripe]
|
||||
- [Any required warnings from compatibility checks]
|
||||
|
||||
### L. Why this fits your business
|
||||
- [2-4 bullets linking business model, merchant of record, and operational constraints to the configuration choices above]
|
||||
|
||||
### M. Open questions
|
||||
- [Any unresolved assumptions to confirm before implementation]
|
||||
```
|
||||
|
||||
### Required wording snippets
|
||||
|
||||
#### Recipient configuration wording (destination or separate)
|
||||
|
||||
Include this wording (adapted to context) when charge pattern is destination or separate charges and transfers:
|
||||
|
||||
“Each connected account needs the recipient configuration (`configuration.recipient`) with `stripe_transfers` on `stripe_balance` requested, so the account can receive transfers from the platform. Marketplace connected accounts should NOT request merchant configuration or `card_payments` capability — this is unnecessary and causes longer onboarding.”
|
||||
|
||||
#### Webhook section guardrails
|
||||
|
||||
- Keep webhook section to one sentence that defers event details to the `connect-build` skill.
|
||||
- Do NOT list concrete webhook event names in recommend output.
|
||||
- Do NOT create a “Required Webhooks” section.
|
||||
- Do NOT mention embedded components in the webhook section.
|
||||
|
||||
### Risk and loss liability guidance
|
||||
|
||||
Always present loss liability and risk management as separate concepts:
|
||||
|
||||
- **Loss liability** (`losses_collector`): who is financially responsible for negative balances on connected accounts.
|
||||
- **Risk management**: who detects and prevents fraud (Stripe Radar vs platform-managed).
|
||||
|
||||
When `losses_collector: application` (platform owns loss liability), emphasize that Radar is essential — fraudulent charges that slip through come directly out of the platform’s balance. For marketplaces using destination charges, the platform is merchant of record and must manage risk.
|
||||
|
||||
### Fee guidance rules
|
||||
|
||||
- When `fees_collector: "stripe"` and using direct charges, the connected account is charged the processing fee directly. The `application_fee_amount` is in addition to that and goes directly to the platform.
|
||||
|
||||
- With destination or separate charges, the platform ALWAYS pays Stripe’s processing fees.
|
||||
|
||||
- Do NOT hardcode Stripe fee amounts (rates vary by region, card type, method, and negotiated pricing).
|
||||
|
||||
- Do NOT make absolute profit and loss guarantees.
|
||||
|
||||
- Do NOT recommend `application_fee_amount` for separate charges and transfers (instead, retain fee by transferring less than charge amount).
|
||||
|
||||
- Do NOT set explicit `application_fee_amount` when Platform Pricing Tool is used (doing so will override tool logic).
|
||||
|
||||
- Always link to [stripe.com/pricing](https://stripe.com/pricing).
|
||||
|
||||
- For platform-owned pricing, recommend [Platform Pricing Tool](https://dashboard.stripe.com/settings/connect/platform_pricing) and [margin report](https://docs.stripe.com/connect/margin-reports.md). Platform Pricing Tool and explicit `application_fee_amount` are mutually exclusive — don’t recommend both.
|
||||
|
||||
- Mention Brazil or Malaysia cross-border fee-collection constraints where relevant.
|
||||
|
||||
- For low flat fees on variable amounts, warn about margin compression at larger ticket sizes.
|
||||
|
||||
- For very small transactions, warn about currency minimums and fee-to-proceeds effects.
|
||||
|
||||
#### Fee output requirements
|
||||
|
||||
Every recommendation MUST explicitly:
|
||||
|
||||
- Name the `applicationFeeIncludes` value (`stripe_fee_estimate` or `platform_fee_only`) and explain what it means for the platform’s margin
|
||||
- Show a funds flow diagram with the platform fee
|
||||
- Recommend the single most appropriate fee approach for the scenario; explain both approaches only when the platform’s margin goal or constraints are genuinely unclear (see Funds-flow comparison guidance below)
|
||||
|
||||
#### Low-margin warning template
|
||||
|
||||
This section only applies when the platform is NOT using direct charges with Stripe-owned pricing (`fees_collector: "stripe"`). In that configuration, the connected account pays Stripe fees directly and this concern doesn’t apply.
|
||||
|
||||
When platform fee appears low relative to processing fees, keep this order:
|
||||
|
||||
1. **Warn first**: explicitly state that the selected platform fee may be below Stripe processing fees, so the platform may lose money per transaction when it absorbs fees.
|
||||
2. **Show downside before fix**: include one concise illustrative example of net margin without fee passthrough (label assumptions clearly and link to [stripe.com/pricing](https://stripe.com/pricing)).
|
||||
3. **Then provide the fix**: recommend margin-preserving `application_fee_amount` logic (platform fee + estimated Stripe fee) and explain why it preserves margin.
|
||||
4. **Close with validation path**: link to [stripe.com/pricing](https://stripe.com/pricing) and recommend monitoring the margin report.
|
||||
|
||||
Suggested warning phrasing:
|
||||
|
||||
> **Warning:** Your platform fee may be below Stripe processing fees at standard rates. With this charge pattern, your platform pays Stripe processing fees on every transaction. If you absorb those fees, your net per transaction may be negative. Check [stripe.com/pricing](https://stripe.com/pricing) for your region and payment-method mix.
|
||||
|
||||
#### Funds-flow comparison guidance
|
||||
|
||||
**Recommend the option that fits the user’s margin goal.** Present both options only when the margin goal or constraints are genuinely unclear.
|
||||
|
||||
To disambiguate, ask: “Are you trying to make X% margin, or do you want your users to pay X%?” The answer determines which option to recommend.
|
||||
|
||||
When destination or direct flow uses `application_fee_amount`, choose guidance as follows:
|
||||
|
||||
- Margin-preserving recommendation: `application_fee_amount = platform fee + estimated Stripe processing fee` (still an approximation — actual rates vary by region, card type, and payment method)
|
||||
- Platform-absorbs-fees recommendation: `application_fee_amount = platform fee only`
|
||||
- If unclear: present both options concisely with the tradeoff and call out what assumption decides the recommendation
|
||||
|
||||
### Onboarding status gating details
|
||||
|
||||
Always include gating guidance to prevent transfers and payouts for unready accounts. `stripe_balance.payouts` is auto-requested when `card_payments` or `stripe_transfers` is requested, so do NOT explicitly request `stripe_balance.payouts` in account create/update calls.
|
||||
|
||||
Use:
|
||||
|
||||
- `configuration.merchant.capabilities.card_payments.status`
|
||||
- `configuration.merchant.capabilities.stripe_balance.payouts.status`
|
||||
- `configuration.recipient.capabilities.stripe_balance.stripe_transfers.status`
|
||||
- `configuration.recipient.capabilities.stripe_balance.payouts.status`
|
||||
|
||||
Do NOT rely on v1 `charges_enabled` or `payouts_enabled` booleans for this flow.
|
||||
|
||||
### Embedded component template notes
|
||||
|
||||
The embedded components section should list the components selected during Step 4b (see SKILL.md for selection logic and charge-pattern compatibility caveats). See [Connect embedded components](https://docs.stripe.com/connect/supported-embedded-components.md) for documentation.
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
## Terminology rules
|
||||
|
||||
When generating text that will be shown to the user, follow these rules strictly. They apply to all output including warning blocks, explanations, recommendation text, and transition summaries.
|
||||
|
||||
This plugin is a public artifact. Never expose internal shorthand codes, internal taxonomy labels, or internal-only references in output.
|
||||
|
||||
Always describe configurations using human-readable field values: dashboard type + fee ownership + negative balance liability ownership + charge pattern.
|
||||
|
||||
Use full, user-friendly terminology in prose. Prefer complete terms such as “connected account,” “merchant of record,” “separate charges and transfers,” and “interchange-plus pricing.” Use API field names only when needed for implementation clarity (for example, `on_behalf_of`).
|
||||
|
||||
Use neutral framing for payout timing: “hold funds before releasing” or “delivery-gated payout.”
|
||||
|
||||
Describe pricing outcomes as margin mechanics and tradeoffs. Don’t guarantee profitability.
|
||||
|
||||
### Scope of advice
|
||||
|
||||
This skill is scoped to Stripe Connect integration guidance.
|
||||
|
||||
- Don’t suggest comparing other payment processors, acquirers, or financial infrastructure providers.
|
||||
- If asked about negotiating Stripe pricing, direct users to [Stripe sales](https://stripe.com/contact/sales) for volume-based or custom pricing discussions.
|
||||
- If a user asks whether they should use Stripe or another provider, state that this skill focuses on Stripe Connect integration and recommend evaluating alternatives against their own product requirements.
|
||||
|
||||
Legacy account type names can be mentioned only when explaining migration from v1 to v2. For new integrations, always recommend Accounts v2 dimensions instead of legacy account type labels.
|
||||
|
||||
### Business model terminology
|
||||
|
||||
Stripe’s public docs define two Connect business model categories. Use these when speaking to the user:
|
||||
|
||||
- **“SaaS platform”** — Sellers collect payments directly and pay fees to Stripe. Sellers are merchant of record and accept payments directly under their own business name. For example, an eCommerce platform that processes payments under the hood for independent sellers.
|
||||
- **“Marketplace”** — Platform collects payments and distributes funds to sellers. For example, a food delivery service that connects customers with restaurants and drivers.
|
||||
|
||||
When explaining the business classification, focus on the funds flows required, for example, in a marketplace, the platform collects payments from customers, takes a cut, and distributes the remainder to sellers; the platform’s name appears on the customer’s bank statement. In a SaaS platform, the seller collects payments directly under the seller’s own business name.
|
||||
|
||||
Do NOT use:
|
||||
|
||||
- “service marketplace” (service-based businesses are still “marketplaces”)
|
||||
- “platform with service providers” in final output (acceptable in Q1 options to help the user self-identify, but the classification result is “marketplace”)
|
||||
- Compound or invented terms: “marketplace platform”, “SaaS marketplace”, and similar mashups.
|
||||
|
||||
The decision matrix’s finer categories (on-demand services, professional services, rental marketplace, and more) are internal aids for config selection. Use them during analysis but present the user-facing label when speaking to the user.
|
||||
|
||||
### Merchant of record language
|
||||
|
||||
When users are unfamiliar with “merchant of record,” explain it in plain language:
|
||||
|
||||
- Merchant of record is the business the customer is paying for that transaction.
|
||||
- Practical check: whose name appears on the customer receipt or statement, and which party is expected to handle payment issues (refunds and disputes).
|
||||
|
||||
Use this as a behavioral signal in discovery:
|
||||
|
||||
- If checkout runs in the platform flow and platform branding and operations own payment support, treat as marketplace behavior.
|
||||
- If each seller runs their own payment relationship and seller branding and operations own payment support, treat as SaaS behavior.
|
||||
|
||||
### Human-readable labels for configuration values
|
||||
|
||||
When showing configuration values, ALWAYS pair them with a human-readable label. The human-readable label comes first; the technical name is parenthetical.
|
||||
|
||||
| Raw config term | Human-readable label |
|
||||
| --- | --- |
|
||||
| `losses_collector: application` | Negative balance liability: your platform |
|
||||
| `losses_collector: stripe` | Negative balance liability: Stripe |
|
||||
| `fees_collector: application` | Fee collection: your platform manages pricing |
|
||||
| `fees_collector: stripe` | Fee collection: Stripe bills connected accounts |
|
||||
| `dashboard=express` | Dashboard: Express (lightweight view for sellers) |
|
||||
| `dashboard=full` | Dashboard: Full Stripe Dashboard (independent access) |
|
||||
| `dashboard=none` | Dashboard: none (you build all seller-facing UIs) |
|
||||
|
||||
### “Platform-owned” and “Stripe-owned” labels
|
||||
|
||||
Don’t use “Platform-owned” or “Stripe-owned” as standalone labels — these are confusing when addressing the platform user directly. Instead say:
|
||||
|
||||
- “Your platform is liable for negative balances” or “Negative balance liability: your platform”
|
||||
- “Stripe is liable for negative balances” or “Negative balance liability: Stripe”
|
||||
|
||||
### Loss liability language
|
||||
|
||||
Use “negative balance liability” (not “loss liability” or “who pays for losses”). When explaining, say: “When a customer disputes a charge, the disputed amount may create a negative balance. Negative balance liability determines which party — your platform or Stripe — absorbs those negative balances.”
|
||||
|
||||
Do NOT use “who pays” framing — it is too vague. The concept is specifically about liability for negative balances on connected accounts.
|
||||
|
||||
### Compatibility wording
|
||||
|
||||
Use neutral compatibility wording in user-facing output. Say “compatibility issue,” “known incompatibility,” or “unsupported combination.”
|
||||
|
||||
### Stripe product language
|
||||
|
||||
Use confident, objective language about Stripe products and features. Frame guidance around product fit and implementation context, not product quality. When a feature works only in a specific context (for example, Connect embedded components run in a browser), state that directly and provide the best-fit path: “Connect embedded components run in a browser. For native mobile apps, use the Stripe API directly to build custom payment views.”
|
||||
|
||||
### Formatting
|
||||
|
||||
Use sentence case for all headings and subheadings in output. Example: “Recommended Connect integration” not “Recommended Connect Integration”. Exception: product names (Connect, Radar, Dashboard) remain capitalized per Stripe style.
|
||||
@@ -0,0 +1,207 @@
|
||||
---
|
||||
name: stripe-apps
|
||||
description: >-
|
||||
Use when building, modifying, or reviewing a Stripe App — or when the user
|
||||
describes something that implies one (e.g. "add a panel to the customer page",
|
||||
"customize my Stripe Dashboard", "react to Stripe events from my app",
|
||||
"connect my service to Stripe without sharing API keys"). Covers the full app
|
||||
development workflow (scaffold, preview, upload, versioning), UI extension
|
||||
architecture (sandboxed iframe, Stripe UI toolkit, viewports), extension types
|
||||
(UI extensions, backend-only, extension interfaces, embedded apps),
|
||||
authentication (platform keys, OAuth, restricted API keys), stripe-app.yaml
|
||||
manifest setup (permissions, viewports, CSP), webhook configuration for apps,
|
||||
Secret Store API, `fetchStripeSignature` auth, and marketplace publishing. Use
|
||||
when the user mentions Stripe Apps, UI extensions, @stripe/ui-extension-sdk,
|
||||
stripe-app.yaml, Dashboard extensions, or customizing the Stripe Dashboard.
|
||||
|
||||
---
|
||||
|
||||
## Stripe Apps — Agent Instructions
|
||||
|
||||
**FIRST ACTION:** Say “Loading Stripe Apps skill.” then Read `references/discovery.md`. This file has routing logic you need before asking the user questions.
|
||||
|
||||
### Your role
|
||||
|
||||
You are a PROJECT BUILDER and INSTRUCTOR. Your primary output is working files on the user’s machine that they can run immediately. If you explain code without also writing it to disk using your Write tool, the user has nothing they can execute.
|
||||
|
||||
You are also a patient guide. Many users have never heard of Stripe Apps, viewports, or webhooks. When they say “I’m not sure” or “what does that mean?”, explain concepts in plain language with examples from their specific idea.
|
||||
|
||||
**Your tool calls (Read, Write) are your real work. Your chat messages explain what you did and teach the user why.**
|
||||
|
||||
### Source of truth for code patterns
|
||||
|
||||
Your training data for Stripe Apps SDK patterns may be outdated or incorrect. Before writing any code file, you MUST read the relevant canonical docs page using WebFetch. See `references/canonical-docs.md` for the full list of docs pages.
|
||||
|
||||
If you cannot access the docs, tell the user: “I need to check the current Stripe Apps documentation to write correct code. Can you provide the current patterns from [relevant docs URL], or shall I proceed with the scaffold and you can verify against the docs?”
|
||||
|
||||
## HARD RULES — violating any of these is a failure
|
||||
|
||||
| \# | Rule | What failure looks like |
|
||||
| --- | --- | --- |
|
||||
| 0 | BEFORE ANYTHING ELSE: (1) Say “Loading Stripe Apps skill.” (2) Call Read on `references/discovery.md` to load the routing table. You need this data before you can ask informed questions. | Responding to the user before calling Read on discovery.md |
|
||||
| 1 | After reading discovery.md, your FIRST message to the user is ONLY the 4 discovery questions (see Step 1). No code, no plan, no summary. Even if the user’s request already mentions details — ask anyway. Users have unstated requirements that only emerge through questions. | Presenting a summary, plan, or any code before asking questions 1-4 and getting answers |
|
||||
| 2 | You MUST use your Write tool to create or modify files on disk. The scaffold creates base files via CLI — after that, use Write to modify scaffolded files and create new ones. A response with code only in chat gives the user nothing runnable. | Producing code in chat without calling Write to save it to disk |
|
||||
| 3 | Run `stripe generate app <name>` using your Bash tool to scaffold the project. Then use Write to modify scaffolded files and create additional files the app needs. | Writing stripe-app.yaml or package.json from scratch instead of modifying the scaffold output |
|
||||
| 4 | Before writing code for any topic (backend, UI, webhooks, auth), read the relevant canonical docs page using WebFetch. See `references/canonical-docs.md`. The docs are the source of truth — not this skill file, not your training data. | Writing code from memory without checking the current docs |
|
||||
| 5 | Tell user: `stripe apps upload` BEFORE testing fetchStripeSignature/Secret Store (the signing secret is generated during first upload). | Omitting upload-first requirement |
|
||||
| 6 | File names: `ui/src/views/App.tsx` (V2 workspace layout), `server.js` (project root). Only create files that are needed for the app’s architecture (see Step 3). | Using wrong filenames or creating files the architecture doesn’t need |
|
||||
| 7 | Every file you write to disk MUST be complete and runnable — not a skeleton or placeholder. The user should be able to run it immediately. Do not write partial files with TODOs. | Writing a file with TODO placeholders or incomplete implementations |
|
||||
| 8 | When presenting the development workflow, include `pnpm build` and `pnpm test` as explicit steps for apps with a UI extension. Backend-only apps without TypeScript skip `pnpm build`. | Omitting build/test steps for UI apps, or requiring them for backend-only apps |
|
||||
| 9 | If the user’s app requires custom objects or extension interfaces (private preview features), OR full-page apps, inform them the feature is in private preview and ask them to confirm they have access BEFORE proceeding. Do not silently proceed with a private preview feature. | Building with private preview features without confirming user has access |
|
||||
|
||||
## BLOCKED — these produce broken apps
|
||||
|
||||
| BLOCKED (never use) | Use instead |
|
||||
| --- | --- |
|
||||
| `stripe apps create` | `stripe generate app <name>` |
|
||||
| Raw HTML in UI extensions (`<div>`, `<span>`, `<p>`, `<button>`, `<input>`, `<h1>`-`<h6>`) | SDK components from `@stripe/ui-extension-sdk/ui` (Box, Inline, Button, TextField, etc.) |
|
||||
| CSS frameworks in UI (Tailwind, MUI, Bootstrap, styled-components, CSS files) | Only `@stripe/ui-extension-sdk/ui` components — no custom styling |
|
||||
| React 18+ APIs in UI (`useId`, `useDeferredValue`, `useTransition`, concurrent features) | React 17 hooks only (Stripe Apps run React 17.0.2) |
|
||||
| `window`, `document`, `localStorage`, `sessionStorage` in UI | Not available in sandboxed iframe |
|
||||
|
||||
## Protocol — execute these steps IN ORDER
|
||||
|
||||
### Step 1 — Discovery (your first message)
|
||||
|
||||
Read <references/discovery.md> using your file-reading tool.
|
||||
|
||||
You CANNOT determine the correct architecture without user input because:
|
||||
|
||||
- The authentication type determines the backend pattern (platform keys vs OAuth vs restricted keys)
|
||||
- Private vs public apps have different webhook configurations
|
||||
- The viewport determines which context props are available
|
||||
- Backend vs frontend-only changes which files you create
|
||||
|
||||
Ask these questions in your FIRST message — nothing else:
|
||||
|
||||
1. What should the app do? (UI in Dashboard / react to events / both / modify billing or payment logic)
|
||||
2. Where should it appear? (customer detail, payment detail, full page, etc.)
|
||||
3. Who is it for? (only you or your team = private, OR other Stripe users = public/marketplace)
|
||||
4. Does it need to store data or talk to other services?
|
||||
|
||||
Do NOT include a summary, plan, or architecture in this first message. ONLY the 4 questions above.
|
||||
|
||||
**If the user doesn’t know an answer or asks for clarification:**
|
||||
|
||||
- Explain the concept in plain language
|
||||
- Give concrete examples from their stated idea
|
||||
- Help them figure out the right answer
|
||||
|
||||
**Private preview check:** After getting answers, before showing your summary, check whether their app implies needing:
|
||||
|
||||
- **Custom objects** (storing custom data models IN Stripe)
|
||||
- **Extension interfaces** (changing how Stripe processes billing, payments, or tax)
|
||||
- **Full-page apps** (dedicated page in Dashboard nav)
|
||||
|
||||
If yes: tell the user that feature is in private preview, ask them to confirm access. See `references/discovery.md` for exact wording and alternatives.
|
||||
|
||||
After the user answers, show a plain-language summary:
|
||||
|
||||
- “You want to: [goal]. It will appear: [where]. It’s for: [private/marketplace]. It needs: [backend/secrets/only Stripe data].”
|
||||
|
||||
Wait for explicit confirmation before proceeding.
|
||||
|
||||
### Step 2 — Scaffold
|
||||
|
||||
Run the scaffold command yourself using your Bash tool:
|
||||
|
||||
```bash
|
||||
stripe generate app <name>
|
||||
```
|
||||
|
||||
This creates a V2 workspace: `stripe-app.yaml`, `package.json`, `pnpm-workspace.yaml`, `ui/src/views/App.tsx`.
|
||||
|
||||
After the scaffold completes, proceed directly to Step 3.
|
||||
|
||||
### Step 3 — Build (WRITE every file to disk)
|
||||
|
||||
Before writing any code, read the relevant canonical docs pages (see `references/canonical-docs.md`) using WebFetch:
|
||||
|
||||
- For UI code: read the Extensions SDK API page and the UI components page
|
||||
- For backend code: read the Backend + signed requests page and Authentication types page
|
||||
- For webhooks: read the Events page
|
||||
- For Secret Store: read the Secret Store page
|
||||
|
||||
**YOUR PRIMARY JOB: Create files on disk following the patterns from the docs.**
|
||||
|
||||
Which files to create depends on discovery answers:
|
||||
|
||||
| Architecture | Files to write |
|
||||
| --- | --- |
|
||||
| Frontend-only (reads Stripe data, no external services) | Modify: `stripe-app.yaml`, `ui/src/views/App.tsx` |
|
||||
| Backend-only (webhooks/events, no Dashboard UI) | Modify: `stripe-app.yaml`. Create: `server.js` |
|
||||
| Full-stack (UI + backend) | Modify: `stripe-app.yaml`, `ui/src/views/App.tsx`. Create: `server.js` |
|
||||
|
||||
For each file: call your Write tool FIRST, then explain what it does.
|
||||
|
||||
**Key constraints for UI code:**
|
||||
|
||||
- Import ONLY from `@stripe/ui-extension-sdk/ui` for components
|
||||
- NO raw HTML elements, NO CSS
|
||||
- Follow the SDK API patterns from the canonical docs exactly
|
||||
|
||||
**Key constraints for backend code (server.js):**
|
||||
|
||||
- CORS (`Access-Control-Allow-Origin: *`) only on endpoints called by the UI extension — webhook endpoints don’t need CORS
|
||||
- `fetchStripeSignature` verification follows the pattern in https://docs.stripe.com/stripe-apps/build-backend
|
||||
- Webhook endpoint count and configuration depends on auth type and distribution — check https://docs.stripe.com/stripe-apps/events
|
||||
- The `event_read` permission must be declared in the manifest for webhook event access
|
||||
|
||||
**Key constraints for stripe-app.yaml:**
|
||||
|
||||
- Declare ALL permissions with purpose strings
|
||||
- Follow the manifest schema from https://docs.stripe.com/stripe-apps/reference/app-manifest
|
||||
- Include `extensions: []` even if no backend extensions
|
||||
|
||||
### Step 4 — Deliver (REQUIRED — do not skip)
|
||||
|
||||
Your FINAL message MUST present the development workflow:
|
||||
|
||||
1. `stripe generate app <name>` → scaffold
|
||||
2. `pnpm install` → dependencies
|
||||
3. Modify scaffolded files + create additional files → implement
|
||||
4. `pnpm build` → compile TypeScript (UI apps only)
|
||||
5. `pnpm test` → run unit tests
|
||||
6. `stripe apps start` → local preview in Dashboard
|
||||
7. `stripe apps upload` → publish version (**required** before fetchStripeSignature or Secret Store)
|
||||
8. Install from Dashboard → test
|
||||
|
||||
**Important workflow facts:**
|
||||
|
||||
- Use sandboxes for safe testing — they provide isolated environments for app development
|
||||
- `stripe apps upload` generates the signing secret needed for `fetchStripeSignature`
|
||||
- Public/marketplace apps need account activation (verified email + business details)
|
||||
- For webhook forwarding during local dev, see `references/webhooks.md`
|
||||
|
||||
### Step 5 — Verify files exist
|
||||
|
||||
Before ending the conversation, confirm your files are on disk. Run `ls` on the files you wrote to verify they exist.
|
||||
|
||||
If any file is MISSING, call Write now to create it.
|
||||
|
||||
## Troubleshooting uploads
|
||||
|
||||
| Error | Cause | Fix |
|
||||
| --- | --- | --- |
|
||||
| `Invalid manifest` | Missing required fields or malformed YAML | Check indentation; ensure `id:`, `version:`, `name:` are present |
|
||||
| `Build failed` | UI component has type/import errors | Run `pnpm build` locally first |
|
||||
| `Version already exists` | Already uploaded this version number | Bump `version` in stripe-app.yaml |
|
||||
| `Permission denied` | CLI not logged in or wrong account | Run `stripe login` |
|
||||
| `connect-src` / CSP error | App calls undeclared URL | Add URL to `content_security_policy.connect-src` |
|
||||
| `extensions field required` | Missing `extensions: []` | Add `extensions: []` to stripe-app.yaml |
|
||||
| `Component not found` | Viewport references wrong component name | Match `component:` value to your default export |
|
||||
|
||||
## Reference files
|
||||
|
||||
| File | Read when |
|
||||
| --- | --- |
|
||||
| <references/canonical-docs.md> | **ALWAYS** — lists docs pages to WebFetch before writing code |
|
||||
| <references/discovery.md> | **ALWAYS FIRST** — full discovery script with routing |
|
||||
| <references/backend.md> | Before writing server.js |
|
||||
| <references/ui-extensions.md> | Before writing React/UI code |
|
||||
| <references/workflow.md> | Full development loop with all CLI commands |
|
||||
| <references/extension-types.md> | After discovery — map answers to extension type |
|
||||
| <references/webhooks.md> | When app reacts to Stripe events |
|
||||
| <references/authentication.md> | For auth type selection and patterns |
|
||||
| <references/onboarding-ux.md> | For first-run experience |
|
||||
| <references/publishing.md> | For marketplace publishing |
|
||||
@@ -0,0 +1,101 @@
|
||||
# Authentication — platform keys, OAuth, restricted API keys
|
||||
|
||||
## Authentication
|
||||
|
||||
How your app authenticates and accesses Stripe data for merchants who install it.
|
||||
|
||||
**Canonical page:** https://docs.stripe.com/stripe-apps/api-authentication
|
||||
|
||||
Read this page using WebFetch before implementing authentication patterns.
|
||||
|
||||
## Three authentication types
|
||||
|
||||
Stripe Apps supports three authentication methods, configured via `stripe_api_access_type` in the app manifest:
|
||||
|
||||
| Auth type | Manifest value | How it works | Best for |
|
||||
| --- | --- | --- | --- |
|
||||
| Restricted API key (recommended) | `restricted_api_key` | Stripe generates a scoped key at install; merchant provides it to your system | Private apps, simpler integrations, apps that don’t need Connect-style access |
|
||||
| Platform keys | `platform` | Your secret key + `Stripe-Account` header to act on behalf of installers | Public/marketplace apps that need to act across many merchants |
|
||||
| OAuth 2.0 | `oauth` | Standard OAuth flow generates access tokens per-account | Apps where merchant must be merchant-of-record |
|
||||
|
||||
### Choosing the right type
|
||||
|
||||
**Default to restricted API keys** unless you have a specific reason to use platform keys or OAuth. RAKs are simpler, more secure (scoped permissions), and don’t create a Connect-style relationship.
|
||||
|
||||
Use a different type when:
|
||||
|
||||
1. **You need Connect webhook fanout** (events from all merchants to one endpoint): Platform keys.
|
||||
2. **You’re building a public marketplace app acting across many merchants:** Platform keys.
|
||||
3. **The merchant must be the merchant-of-record for charges:** OAuth.
|
||||
4. **Private app or fewer merchants, no Connect fanout needed:** Restricted API keys (simplest).
|
||||
|
||||
## Platform keys
|
||||
|
||||
Your app’s API key acts on behalf of a merchant’s account using the `Stripe-Account` header:
|
||||
|
||||
```javascript
|
||||
// Use a restricted API key when possible; fall back to secret key only for platform-key apps
|
||||
const stripe = require("stripe")(process.env.STRIPE_API_KEY);
|
||||
|
||||
await stripe.customers.list({}, {
|
||||
stripeAccount: "acct_xxxxx", // the merchant's account ID
|
||||
});
|
||||
```
|
||||
|
||||
**How to get the merchant’s account ID:**
|
||||
|
||||
- From a webhook event: `event.account`
|
||||
- From `fetchStripeSignature` payload: the signed data includes `account_id`
|
||||
- From the UI extension: `userContext.account.id` (top-level prop)
|
||||
|
||||
**Key fact:** Platform keys use the same `Stripe-Account` header mechanism as Stripe Connect. Installers are NOT onboarded as connected accounts in the traditional sense — the header simply authorizes your key to access their account within the app’s declared permissions.
|
||||
|
||||
## OAuth 2.0
|
||||
|
||||
Use OAuth when the connected account needs to be the merchant of record for charges, or when you need the merchant’s own Stripe identity on API calls.
|
||||
|
||||
Most apps do NOT need OAuth. Use platform keys unless you specifically need this.
|
||||
|
||||
For implementation, read: https://docs.stripe.com/stripe-apps/pkce-oauth-flow
|
||||
|
||||
## Restricted API keys
|
||||
|
||||
With RAK apps, Stripe generates a restricted key at install time with only the permissions your app declared. The merchant copies this key to your system.
|
||||
|
||||
Key differences from platform keys:
|
||||
|
||||
- No Connect-style relationship is created
|
||||
- Can’t use Connect webhook fanout (each merchant manages their own webhooks)
|
||||
- Simpler model for private apps or apps with fewer merchants
|
||||
|
||||
## Authenticating the UI to your backend (fetchStripeSignature)
|
||||
|
||||
`fetchStripeSignature` proves to your backend that a request came from a legitimate app installation.
|
||||
|
||||
Key facts:
|
||||
|
||||
- The signed payload contains `user_id` and `account_id` by default (field order matters)
|
||||
- You can include additional data by passing it to `fetchStripeSignature(extraPayload)`
|
||||
- Backend verifies with `stripe.webhooks.signature.verifyHeader()`
|
||||
- The signing secret (starts with `absec_...`) is generated on first `stripe apps upload`
|
||||
|
||||
For the full implementation pattern, read: https://docs.stripe.com/stripe-apps/build-backend
|
||||
|
||||
## Identifying the installing merchant
|
||||
|
||||
When a merchant installs your app, Stripe sends an `account.application.authorized` event. When they uninstall, it sends `account.application.deauthorized`.
|
||||
|
||||
Store the account ID from `event.account` to make future API calls on their behalf.
|
||||
|
||||
## Permission scopes
|
||||
|
||||
Use the CLI to add permissions to your app:
|
||||
|
||||
```bash
|
||||
stripe apps grant permission "customer_read" "Read customer data to show in the Dashboard"
|
||||
stripe apps grant permission "event_read" "Receive webhook events"
|
||||
```
|
||||
|
||||
This updates `stripe-app.yaml` with the correct format automatically.
|
||||
|
||||
**When you change permissions:** existing users must re-authorize. The app returns an invalid-request error for undeclared permissions until the user re-authorizes. See `publishing.md` for details.
|
||||
@@ -0,0 +1,113 @@
|
||||
# Backend — when and how to add server-side logic
|
||||
|
||||
## When you need a backend
|
||||
|
||||
You need a backend if your app needs to:
|
||||
|
||||
- Store data long-term (user preferences, linked accounts, custom records)
|
||||
- Call APIs that require server-side secrets (API keys that can’t be in the browser)
|
||||
- Run logic when the user isn’t in the Dashboard (webhooks, scheduled jobs)
|
||||
- Call third-party services securely (email providers, CRMs, spreadsheet APIs)
|
||||
- Perform actions that take longer than the UI can wait for
|
||||
|
||||
**You don’t need a backend if:**
|
||||
|
||||
- Your app only reads and displays Stripe data (use the SDK client directly in the UI)
|
||||
- You only need to store a small amount of sensitive data — use the Secret Store API instead
|
||||
|
||||
## Canonical documentation
|
||||
|
||||
Before writing backend code, read these pages using WebFetch:
|
||||
|
||||
| Topic | URL |
|
||||
| --- | --- |
|
||||
| Backend implementation + fetchStripeSignature | https://docs.stripe.com/stripe-apps/build-backend |
|
||||
| Authentication types (determines backend pattern) | https://docs.stripe.com/stripe-apps/api-authentication |
|
||||
| Events and webhooks | https://docs.stripe.com/stripe-apps/events |
|
||||
| Secret Store API | https://docs.stripe.com/stripe-apps/store-secrets |
|
||||
|
||||
## Backend architecture decisions
|
||||
|
||||
### Authentication type determines the backend pattern
|
||||
|
||||
Your app’s `stripe_api_access_type` controls how the backend authenticates. See `authentication.md` for the full breakdown of auth types and when to use each one.
|
||||
|
||||
### CORS configuration
|
||||
|
||||
CORS (`Access-Control-Allow-Origin: *`) is needed ONLY on endpoints called by the UI extension. The UI runs in a sandboxed iframe with a `null` origin — specific origin allowlisting will not work.
|
||||
|
||||
Webhook endpoints do NOT need CORS — they receive requests from Stripe’s servers, not from the browser.
|
||||
|
||||
### fetchStripeSignature verification
|
||||
|
||||
`fetchStripeSignature` is how the UI extension authenticates requests to your backend. The signed payload and verification method are documented at https://docs.stripe.com/stripe-apps/build-backend.
|
||||
|
||||
Key facts:
|
||||
|
||||
- The signing secret (starts with `absec_...`) is generated on first `stripe apps upload`
|
||||
- The default signed payload contains `user_id` and `account_id` (field order matters)
|
||||
- Extra data can be included by passing it to `fetchStripeSignature(payload)`
|
||||
- Verification uses `stripe.webhooks.signature.verifyHeader()`
|
||||
|
||||
### Webhook configuration
|
||||
|
||||
Webhook setup depends on your app’s distribution and auth type:
|
||||
|
||||
| App type | Webhook setup |
|
||||
| --- | --- |
|
||||
| Private (your account only) | ONE standard webhook endpoint |
|
||||
| Public with platform keys | ONE webhook with “Listen to events on Connected accounts” enabled |
|
||||
| Public with restricted API keys | Can’t use Connect webhook fanout — each merchant manages their own webhooks |
|
||||
|
||||
The `event_read` permission MUST be declared in your manifest, plus read permissions for each event type you want to receive.
|
||||
|
||||
Read https://docs.stripe.com/stripe-apps/events for the full setup guide.
|
||||
|
||||
For firewall allowlisting of inbound webhook traffic, see https://docs.stripe.com/ips for Stripe’s IP addresses.
|
||||
|
||||
## Secret Store API
|
||||
|
||||
**Plain-language:** “Stripe has a built-in secure place to store passwords, tokens, and API keys for your app — you don’t need to build your own database for secrets.”
|
||||
|
||||
### Two scopes
|
||||
|
||||
| Scope | Use for | Example |
|
||||
| --- | --- | --- |
|
||||
| `account` | Shared across all users of an account | The business’s API key for an email service |
|
||||
| `user` | Per-user secrets | An individual user’s OAuth access token |
|
||||
|
||||
### Limits and restrictions
|
||||
|
||||
- Maximum **10 secrets per scope** (account and user separately)
|
||||
- Always list and delete before adding more if approaching the limit
|
||||
- Do **not** store PCI-sensitive data (card numbers, CVVs, bank account numbers)
|
||||
|
||||
### Declaring the permission
|
||||
|
||||
In `stripe-app.yaml`:
|
||||
|
||||
```yaml
|
||||
declarations:
|
||||
stripe_api_access:
|
||||
permissions:
|
||||
- permission: secret_write
|
||||
purpose: Store third-party credentials for the app
|
||||
```
|
||||
|
||||
### Implementation
|
||||
|
||||
For the correct code patterns to read, write, and delete secrets, read: https://docs.stripe.com/stripe-apps/store-secrets
|
||||
|
||||
## Local development with a backend
|
||||
|
||||
Run your backend locally alongside `stripe apps start`:
|
||||
|
||||
```bash
|
||||
# Terminal 1: start the app preview
|
||||
stripe apps start
|
||||
|
||||
# Terminal 2: start your backend server
|
||||
node server.js
|
||||
```
|
||||
|
||||
For webhook forwarding during local development, see `references/webhooks.md`.
|
||||
@@ -0,0 +1,48 @@
|
||||
# Canonical documentation — sources of truth for code patterns
|
||||
|
||||
## Canonical documentation
|
||||
|
||||
Before writing any code file, read the relevant canonical docs page using WebFetch. These docs are the source of truth for API patterns, component usage, and configuration — do NOT reproduce code examples from memory or from this skill file.
|
||||
|
||||
If you cannot access the docs, tell the user you need them to provide the current patterns rather than guessing.
|
||||
|
||||
## Reference pages
|
||||
|
||||
| Topic | URL |
|
||||
| --- | --- |
|
||||
| App scaffold and workflow | https://docs.stripe.com/stripe-apps/create-app |
|
||||
| Manifest schema (`stripe-app.yaml`) | https://docs.stripe.com/stripe-apps/reference/app-manifest |
|
||||
| Permissions reference | https://docs.stripe.com/stripe-apps/reference/permissions |
|
||||
| Backend + signed requests (`fetchStripeSignature`) | https://docs.stripe.com/stripe-apps/build-backend |
|
||||
| Authentication types (platform, OAuth, RAK) | https://docs.stripe.com/stripe-apps/api-authentication |
|
||||
| Events and webhooks | https://docs.stripe.com/stripe-apps/events |
|
||||
| How UI extensions work | https://docs.stripe.com/stripe-apps/how-ui-extensions-work |
|
||||
| UI components | https://docs.stripe.com/stripe-apps/components |
|
||||
| Extensions SDK API (`createHttpClient`, Stripe client) | https://docs.stripe.com/stripe-apps/reference/extensions-sdk-api |
|
||||
| Secret Store | https://docs.stripe.com/stripe-apps/store-secrets |
|
||||
| Versioning and releases | https://docs.stripe.com/stripe-apps/versions-and-releases |
|
||||
| Marketplace submission | https://docs.stripe.com/stripe-apps/publish-app |
|
||||
| Onboarding UX patterns | https://docs.stripe.com/stripe-apps/patterns/onboarding-experience |
|
||||
| Full-page apps (private preview) | https://docs.stripe.com/stripe-apps/patterns/full-page-apps |
|
||||
| Viewports reference | https://docs.stripe.com/stripe-apps/reference/viewports |
|
||||
| Sandbox support | https://docs.stripe.com/stripe-apps/enable-sandbox-support |
|
||||
|
||||
## How to use this list
|
||||
|
||||
1. Identify which topics are relevant to the app you’re building (based on discovery answers)
|
||||
2. WebFetch each relevant page BEFORE writing code
|
||||
3. Follow the patterns shown in the docs exactly — field names, import paths, constructor signatures
|
||||
4. If a pattern in your training data conflicts with what the docs show, the docs win
|
||||
|
||||
## Common lookup scenarios
|
||||
|
||||
| You need to… | Read this page |
|
||||
| --- | --- |
|
||||
| Initialize the Stripe client in a UI extension | Extensions SDK API |
|
||||
| Verify `fetchStripeSignature` on your backend | Backend + signed requests |
|
||||
| Choose between platform keys, OAuth, or restricted keys | Authentication types |
|
||||
| Set up webhooks for a public app | Events and webhooks |
|
||||
| Store secrets (OAuth tokens, API keys) | Secret Store |
|
||||
| Know which UI components are available | UI components |
|
||||
| Declare permissions in the manifest | Permissions reference |
|
||||
| Publish to the marketplace | Marketplace submission |
|
||||
@@ -0,0 +1,164 @@
|
||||
# Discovery interview
|
||||
|
||||
## Discovery interview
|
||||
|
||||
Run this interview **before writing any code**. Ask one question at a time. Never use Stripe-internal jargon until after routing is complete.
|
||||
|
||||
### Question 1 — What do you want to do?
|
||||
|
||||
```
|
||||
What would you like your app to do? Pick the option that sounds closest:
|
||||
|
||||
1. Show something or add a button/panel inside my Stripe Dashboard
|
||||
(for example: show a customer's loyalty points, add a "Send email" button)
|
||||
|
||||
2. Automatically do something when a payment or event happens
|
||||
(for example: send a confirmation email, update a spreadsheet, sync data)
|
||||
|
||||
3. Both — add something to the Dashboard AND react to Stripe events
|
||||
|
||||
4. Let merchants connect their Stripe account to my service without sharing API keys
|
||||
|
||||
5. Add custom logic to how Stripe calculates bills or routes payments
|
||||
(advanced — private preview)
|
||||
|
||||
6. I'm not sure — ask me more questions
|
||||
```
|
||||
|
||||
**Routing:**
|
||||
|
||||
- Option 1 → UI extension. Ask Question 2.
|
||||
- Option 2 → Backend-only app. Ask Question 3. Then read `backend.md`, `webhooks.md`, `authentication.md`, `workflow.md`.
|
||||
- Option 3 → Full-stack app. Ask Question 2, then Question 3. Read all references.
|
||||
- Option 4 → App-as-authentication. Read `authentication.md`, `workflow.md`.
|
||||
- Option 5 → Extension interfaces (private preview). Tell the user: “This is in private preview — check [/stripe-apps](https://docs.stripe.com/stripe-apps.md) for the latest access information. I can help you get started once access is confirmed.”
|
||||
- Option 6 → Ask follow-up: “What problem are you trying to solve? For example: tracking sales, notifying customers, connecting a third-party tool?”
|
||||
|
||||
### Question 2 — Where do you want your app to appear? (only if UI)
|
||||
|
||||
```
|
||||
Where in the Stripe Dashboard should your app show up?
|
||||
|
||||
1. Next to a specific customer, payment, invoice, subscription, or product
|
||||
2. Everywhere in the Dashboard as a floating side panel
|
||||
3. As its own full-screen page
|
||||
4. In the settings area of my app (after install)
|
||||
5. As a setup guide when someone installs my app
|
||||
6. I'm not sure
|
||||
```
|
||||
|
||||
**Viewport routing:**
|
||||
|
||||
| Answer | Viewport(s) |
|
||||
| --- | --- |
|
||||
| Next to a customer | `stripe.dashboard.customer.detail` |
|
||||
| Next to a payment | `stripe.dashboard.payment.detail` |
|
||||
| Next to an invoice | `stripe.dashboard.invoice.detail` |
|
||||
| Next to a subscription | `stripe.dashboard.subscription.detail` |
|
||||
| Next to a product | `stripe.dashboard.product.detail` |
|
||||
| On any list page | `stripe.dashboard.customer.list`, `.payment.list`, etc. |
|
||||
| Everywhere (side panel) | `stripe.dashboard.drawer.default` |
|
||||
| Full-screen page | Full-page app — `stripe.dashboard.fullpage` |
|
||||
| Dashboard homepage | `stripe.dashboard.home.overview` |
|
||||
| Settings | `settings` viewport |
|
||||
| Setup guide (first run) | `onboarding` viewport |
|
||||
|
||||
If the answer is “full-screen page”, read `ui-extensions.md` (full-page apps section). If the answer is “setup guide”, also read `onboarding-ux.md`. If “I’m not sure”, ask: “When someone opens Stripe and looks at a customer’s page — would your app show up there? Or would it be more like its own separate page?”
|
||||
|
||||
### Question 3 — Who is this for?
|
||||
|
||||
```
|
||||
Who will use this app?
|
||||
|
||||
1. Just me / my own Stripe account (private app)
|
||||
2. Other Stripe users — I want to publish it to the marketplace
|
||||
```
|
||||
|
||||
**Routing:**
|
||||
|
||||
- Option 1 → Private app. Simpler workflow — no marketplace submission needed.
|
||||
- Option 2 → Public app. Will need account activation (verified email and business details). Note this in the plan.
|
||||
|
||||
### Question 3b — Authentication type (only for public apps that need backend access)
|
||||
|
||||
If the user chose public/marketplace AND their app needs to access merchant data from a backend, determine the authentication type. Read `authentication.md` for the full comparison — restricted API keys are the recommended default unless the app specifically needs Connect-style access or OAuth.
|
||||
|
||||
For private apps or frontend-only apps, skip this question — restricted API keys or platform keys both work, and RAKs are simpler.
|
||||
|
||||
### Question 4 — Will your app need to remember things or talk to other services?
|
||||
|
||||
```
|
||||
Will your app need to:
|
||||
|
||||
1. Remember settings or store information (for example: a user's login for another service,
|
||||
preferences, or data not already in Stripe)
|
||||
2. Talk to another service (for example: send emails, update a spreadsheet, call a third-party API)
|
||||
3. No — it will only show Stripe data
|
||||
```
|
||||
|
||||
**Routing:**
|
||||
|
||||
- Option 1 or 2 → Needs backend or Secret Store API. Read `backend.md`.
|
||||
- If storing credentials/tokens → use the Secret Store API (plain-language: “Stripe has a built-in secure place to store passwords and tokens — you don’t need to build your own database for secrets”)
|
||||
- If running server-side logic → needs a self-hosted backend
|
||||
- Option 3 → Frontend-only. Only the SDK’s Stripe client and `@stripe/ui-extension-sdk/ui` needed. No backend.
|
||||
|
||||
### After the interview — show a summary
|
||||
|
||||
Before writing any code, confirm your understanding with the user:
|
||||
|
||||
```
|
||||
Here's what I understood:
|
||||
|
||||
- You want to: [plain-language description of the goal]
|
||||
- Your app will appear: [where, or "on a backend server"]
|
||||
- It's for: [just you / other Stripe users]
|
||||
- It needs to: [remember things / talk to [service] / just show Stripe data]
|
||||
|
||||
Does that sound right? I'll start building once you confirm.
|
||||
```
|
||||
|
||||
Only proceed after the user confirms. If they correct anything, update your understanding and show the summary again.
|
||||
|
||||
### Private preview feature detection
|
||||
|
||||
Some Stripe Apps features are in **private preview** — they require the user to be gated in before they can use them. Detect these during or after the interview:
|
||||
|
||||
**Private preview features:**
|
||||
|
||||
| Feature | Trigger phrases (user might say) | What to tell the user |
|
||||
| --- | --- | --- |
|
||||
| Custom objects | “store custom data in Stripe”, “create my own data model”, “custom database in Stripe”, “custom fields on customers”, “structured data that isn’t in Stripe already” | “Custom objects let you define your own data types in Stripe, but this feature is currently in private preview. You’ll need to have access enabled on your account before we can use it. Can you confirm you’re gated in for custom objects?” |
|
||||
| Extension interfaces | “change how Stripe calculates”, “custom billing logic”, “modify payment routing”, “override Stripe’s default behavior”, “custom tax calculation” | “Extension interfaces let your app hook into Stripe’s processing pipeline, but this is in private preview. Can you confirm you have access to extension interfaces on your account?” |
|
||||
| Full-page apps | “full-screen page in Stripe”, “my own page in the Dashboard”, “not a side panel — a full page”, “standalone page inside Stripe” | “Full-page apps (using the `stripe.dashboard.fullpage` viewport) are in private preview. Can you confirm you have access to full-page apps on your account?” |
|
||||
|
||||
**When to check:**
|
||||
|
||||
- If the user picks Option 5 in Question 1 → extension interfaces (already handled)
|
||||
- If the user’s description of what their app does (Question 1 or free-form description) implies custom objects or extension interfaces → ask before proceeding
|
||||
- If the user mentions “custom objects” or “extension interfaces” by name at ANY point → confirm access
|
||||
|
||||
**How to proceed after confirmation:**
|
||||
|
||||
- User confirms access → continue building with that feature
|
||||
- User says they don’t have access → suggest alternatives:
|
||||
- Instead of custom objects → use Secret Store API for key-value data, or store data in their own backend
|
||||
- Instead of extension interfaces → suggest a webhook-based approach that reacts to events rather than intercepting processing
|
||||
- User is unsure → tell them: “You can check your access at the Stripe Apps page in your Dashboard, or ask your Stripe account representative. I can help you build with an alternative approach in the meantime.”
|
||||
|
||||
## Plain-language glossary
|
||||
|
||||
Use these explanations when you need to introduce technical terms after routing:
|
||||
|
||||
| Term | Plain-language explanation |
|
||||
| --- | --- |
|
||||
| UI extension | The part of your app that shows up inside the Stripe Dashboard |
|
||||
| Viewport | Which specific Dashboard page your app appears on |
|
||||
| Extension interface | A hook that lets your app change how Stripe processes billing or payments |
|
||||
| Platform keys | How your app accesses merchant data when they install it — no manual key-sharing needed |
|
||||
| Connected account | A merchant who has installed your app |
|
||||
| Permissions | What Stripe data your app is allowed to read or write; must be declared before use |
|
||||
| Secret Store | Stripe’s built-in way for your app to save sensitive information like passwords or tokens |
|
||||
| stripe-app.yaml | The configuration file that tells Stripe what your app is called, what it needs access to, and where it appears |
|
||||
| Custom objects | Custom data types you define and store inside Stripe (in private preview — requires access) |
|
||||
| Sandbox | An isolated Stripe test environment for safe testing — useful for testing destructive operations or onboarding flows |
|
||||
@@ -0,0 +1,124 @@
|
||||
# Extension types
|
||||
|
||||
## Extension types
|
||||
|
||||
Stripe Apps supports five extension types. Use the discovery interview in `discovery.md` to determine which one the user needs.
|
||||
|
||||
### 1. UI extension — “show something in the Dashboard”
|
||||
|
||||
Renders custom UI inside the Stripe Dashboard using the Stripe UI toolkit. Runs in a sandboxed iframe.
|
||||
|
||||
**Plain-language examples:**
|
||||
|
||||
- “Show a customer’s loyalty points next to their Stripe profile”
|
||||
- “Add a button to send a custom invoice email”
|
||||
- “Build a full-screen analytics dashboard inside Stripe”
|
||||
- “Show a customer’s order history from my store next to their Stripe data”
|
||||
|
||||
**What you can build:**
|
||||
|
||||
- Page-specific panels (next to a customer, payment, invoice, subscription, or product)
|
||||
- A side panel that appears everywhere in the Dashboard
|
||||
- A full-screen page inside the Dashboard
|
||||
- A setup/onboarding screen when users first install the app
|
||||
- An app settings page
|
||||
|
||||
**Key constraints:**
|
||||
|
||||
- React 17 only (not 18+)
|
||||
- Only `@stripe/ui-extension-sdk/ui` components — no Tailwind, HTML, or third-party UI libraries
|
||||
- Can’t access `window`, `document`, or `localStorage`
|
||||
- Must use the SDK’s Stripe API client (see canonical docs for initialization pattern)
|
||||
|
||||
**Read:** `ui-extensions.md`, `workflow.md`
|
||||
|
||||
### 2. Backend-only app — “react to events, no Dashboard UI”
|
||||
|
||||
Runs on the developer’s server. Receives Stripe webhooks and calls the Stripe API. No Dashboard UI.
|
||||
|
||||
**Plain-language examples:**
|
||||
|
||||
- “Email a download link after a payment”
|
||||
- “Sync purchases to a Google Sheet”
|
||||
- “Create an order in my fulfillment system when a payment succeeds”
|
||||
- “Notify my team on Slack when a new subscription starts”
|
||||
|
||||
**How it works:**
|
||||
|
||||
- Your server receives Stripe events (webhooks)
|
||||
- Your server calls the Stripe API using platform keys (no manual key-sharing with merchants)
|
||||
- No UI — all logic runs server-side
|
||||
|
||||
**Read:** `authentication.md`, `webhooks.md`, `backend.md`, `workflow.md`
|
||||
|
||||
### 3. Full-stack app — “Dashboard UI + backend server”
|
||||
|
||||
Combines a UI extension with a backend server. The UI can show data from external services and trigger server-side actions.
|
||||
|
||||
**Plain-language examples:**
|
||||
|
||||
- “Show my customer’s loyalty points in Stripe AND update them when they make a purchase”
|
||||
- “Let merchants configure their email templates from the Dashboard, then send emails from my server”
|
||||
- “Show real-time shipping status next to each payment”
|
||||
|
||||
**How it works:**
|
||||
|
||||
- UI extension in the Dashboard for user interaction
|
||||
- Backend server for data storage, third-party API calls, and webhook processing
|
||||
- UI authenticates to the backend using `fetchStripeSignature`
|
||||
|
||||
**Read:** all reference files
|
||||
|
||||
### 4. Extension interfaces — “plug into Stripe’s billing or payments engine” (private preview)
|
||||
|
||||
Lets your app change how Stripe processes billing or payments. Available types:
|
||||
|
||||
**Billing extensions:**
|
||||
|
||||
- Custom discount calculation
|
||||
- Custom proration calculation
|
||||
- Custom customer balance handling
|
||||
- Custom recurring billing item handling
|
||||
|
||||
**Payments orchestration:**
|
||||
|
||||
- Custom payment routing
|
||||
|
||||
**Private preview:** Extension interfaces are not generally available. If the user asks for this:
|
||||
|
||||
1. Explain it’s in private preview
|
||||
2. Tell them to check the Stripe Apps documentation for the latest access information
|
||||
3. Ask them to check access and return when they have it
|
||||
4. Do not attempt to build anything until access is confirmed
|
||||
|
||||
### 5. Embedded apps — “embed a third-party Stripe App inside your platform” (private preview)
|
||||
|
||||
For Connect platforms that want to surface third-party Stripe Apps (like QuickBooks, Xero, or Mailchimp) directly inside their own product.
|
||||
|
||||
**This is different from building an app.** Embedded apps are for platforms that want to *host* existing apps, not for building new ones.
|
||||
|
||||
**Private preview:** If the user asks for this, point them to https://docs.stripe.com/stripe-apps/embedded-apps.
|
||||
|
||||
## Full viewport routing table
|
||||
|
||||
For UI extensions — maps plain-language descriptions to viewport IDs:
|
||||
|
||||
| What the user wants | Viewport ID |
|
||||
| --- | --- |
|
||||
| Next to a specific customer | `stripe.dashboard.customer.detail` |
|
||||
| On the customers list page | `stripe.dashboard.customer.list` |
|
||||
| Next to a specific payment | `stripe.dashboard.payment.detail` |
|
||||
| On the payments list page | `stripe.dashboard.payment.list` |
|
||||
| Next to a specific invoice | `stripe.dashboard.invoice.detail` |
|
||||
| On the invoices list page | `stripe.dashboard.invoice.list` |
|
||||
| Next to a specific subscription | `stripe.dashboard.subscription.detail` |
|
||||
| On the subscriptions list page | `stripe.dashboard.subscription.list` |
|
||||
| Next to a specific product | `stripe.dashboard.product.detail` |
|
||||
| On the products list page | `stripe.dashboard.product.list` |
|
||||
| Everywhere in the Dashboard (side panel) | `stripe.dashboard.drawer.default` |
|
||||
| As its own full-screen page | Full-page app — `stripe.dashboard.fullpage` |
|
||||
| On the Dashboard homepage | `stripe.dashboard.home.overview` |
|
||||
| App settings page | `settings` |
|
||||
| First-run setup after install | `onboarding` |
|
||||
|
||||
For the full viewport reference, see https://docs.stripe.com/stripe-apps/reference/viewports.
|
||||
@@ -0,0 +1,67 @@
|
||||
# Onboarding UX — first-run user experience
|
||||
|
||||
## Onboarding UX
|
||||
|
||||
**Plain-language:** “When someone installs your app for the first time, the first thing they see is your app’s welcome or setup screen. This is called onboarding.”
|
||||
|
||||
Design this experience carefully — it determines whether merchants understand how to use your app or give up immediately.
|
||||
|
||||
**Canonical page:** https://docs.stripe.com/stripe-apps/patterns/onboarding-experience
|
||||
|
||||
Read this page using WebFetch for the correct component props and patterns.
|
||||
|
||||
## Options from simplest to most complex
|
||||
|
||||
### Option 1 — Zero-touch onboarding (easiest)
|
||||
|
||||
If your app only uses Stripe data and doesn’t need its own login, there’s nothing to set up. The app works immediately after install.
|
||||
|
||||
Use `fetchStripeSignature` to identify the user without a login screen — the user’s Stripe identity proves who they are.
|
||||
|
||||
**When to use:** When your app doesn’t need third-party credentials or a separate user account.
|
||||
|
||||
### Option 2 — OnboardingView component
|
||||
|
||||
Show a setup screen the first time the user opens the app. Use the `onboarding` viewport to show a dedicated onboarding page.
|
||||
|
||||
In `stripe-app.yaml`, add the `onboarding` viewport:
|
||||
|
||||
```yaml
|
||||
ui_extension:
|
||||
views:
|
||||
- viewport: onboarding
|
||||
component: OnboardingView
|
||||
- viewport: stripe.dashboard.customer.detail
|
||||
component: App
|
||||
```
|
||||
|
||||
For the correct `OnboardingView` component props and structure, read the canonical onboarding page. Key requirements:
|
||||
|
||||
- Use the `OnboardingView` component (not `ContextView`) for the onboarding viewport
|
||||
- Include required props like `completed`, `tasks`, and `title`
|
||||
|
||||
### Option 3 — SignInView component (third-party login)
|
||||
|
||||
If users need to log in to a third-party service (connecting their Google account, Mailchimp, etc.), use `SignInView` to guide them.
|
||||
|
||||
For the correct `SignInView` props and usage, read: https://docs.stripe.com/stripe-apps/patterns/onboarding-experience
|
||||
|
||||
Use the Secret Store API to save the resulting OAuth token. See `backend.md`.
|
||||
|
||||
## Critical rule: always check onboarding status in every view
|
||||
|
||||
Don’t assume the user went through the onboarding flow in order. They might open a payment page before completing setup.
|
||||
|
||||
Check at the start of every page-specific view whether onboarding is complete. If not, show a prompt directing them to complete setup.
|
||||
|
||||
## Storing onboarding state
|
||||
|
||||
Use the Secret Store API to remember whether a user has completed onboarding.
|
||||
|
||||
For the correct Secret Store API patterns, read: https://docs.stripe.com/stripe-apps/store-secrets
|
||||
|
||||
Key facts:
|
||||
|
||||
- Use `user` scope for per-user onboarding state
|
||||
- Use `account` scope for account-wide configuration
|
||||
- Maximum 10 secrets per scope
|
||||
@@ -0,0 +1,145 @@
|
||||
# Publishing — versioning, releases, test vs live mode, marketplace
|
||||
|
||||
## Publishing
|
||||
|
||||
How to version, release, and publish your Stripe App.
|
||||
|
||||
## Test mode vs live mode
|
||||
|
||||
**Plain-language:** “Test mode uses fake data so you can try things safely. Live mode uses real customer data. Always build and test in test mode first.”
|
||||
|
||||
| Mode | Data | When to use |
|
||||
| --- | --- | --- |
|
||||
| Test mode | Fake (test cards, test customers) | Development and QA |
|
||||
| Live mode | Real customer and payment data | Production |
|
||||
|
||||
**Workflow:** Upload → install in test mode → test thoroughly → install in live mode.
|
||||
|
||||
**Do not skip test mode testing.** Even if your app looks correct locally with `stripe apps start`, you must install it in test mode and verify it works with the actual install flow before going live.
|
||||
|
||||
## Versioning
|
||||
|
||||
Bump `version` in `stripe-app.yaml` before each upload:
|
||||
|
||||
```yaml
|
||||
id: com.example.my-app
|
||||
version: 1.0.1
|
||||
name: My App
|
||||
```
|
||||
|
||||
Use semantic versioning:
|
||||
|
||||
- `1.0.0` — initial release
|
||||
- `1.0.1` — bug fix
|
||||
- `1.1.0` — new feature (backward compatible)
|
||||
- `2.0.0` — breaking change or major feature
|
||||
|
||||
**Rules:**
|
||||
|
||||
- Versions must be uploaded in order — if you upload `2.0.0` before `1.0.0`, `2.0.0` won’t be available for release
|
||||
- You can have multiple uploaded versions; you choose which one to install
|
||||
- Stripe auto-upgrades installed users to the latest release — they don’t need to do anything **unless** you changed permissions
|
||||
|
||||
## Upload and release workflow
|
||||
|
||||
```bash
|
||||
# 1. Bump version in stripe-app.yaml, then:
|
||||
stripe apps upload
|
||||
|
||||
# 2. Go to Dashboard → Apps → your app → version history
|
||||
# 3. Click the version you want to release
|
||||
# 4. Click "Set as external test version" (test mode) or "Release" (live mode)
|
||||
```
|
||||
|
||||
## When you change permissions
|
||||
|
||||
This is a common source of bugs. When you add new permissions:
|
||||
|
||||
1. Update `stripe-app.yaml` with the new permissions
|
||||
2. Bump the version and upload
|
||||
3. Existing users are notified by email
|
||||
4. The **“Review Permissions”** button appears — but only on the **Apps workload page** ([dashboard.stripe.com/apps](https://dashboard.stripe.com/apps)), **not on the app itself**
|
||||
5. The app returns an **invalid-request error** for the new permissions until the user clicks “Review Permissions” and re-authorizes
|
||||
|
||||
**Always warn users about this step** when you change permissions. Many users miss the notification and think the app is broken.
|
||||
|
||||
**How to notify users:** Consider adding a banner in your app UI that detects when a required permission is missing and guides the user to re-authorize.
|
||||
|
||||
## Publishing to the Stripe Apps Marketplace
|
||||
|
||||
For public apps — making your app available to all Stripe users.
|
||||
|
||||
### Requirements
|
||||
|
||||
Before submitting:
|
||||
|
||||
- Verified email address on your Stripe account
|
||||
- Business details filled in (legal name, address)
|
||||
- App passes [review requirements](https://docs.stripe.com/stripe-apps/review-requirements.md)
|
||||
- Connect platform accounts cannot publish marketplace apps
|
||||
|
||||
### Submission
|
||||
|
||||
1. Go to [Dashboard → Apps](https://dashboard.stripe.com/apps)
|
||||
2. Select your app
|
||||
3. Click **Submit for review**
|
||||
|
||||
Stripe reviews your app for security, functionality, and compliance with their guidelines.
|
||||
|
||||
### Review requirements overview
|
||||
|
||||
- App must work correctly in test and live mode
|
||||
- No prohibited content or misleading claims
|
||||
- Privacy policy URL required
|
||||
- Support contact required
|
||||
- App icon and screenshots required
|
||||
|
||||
### After approval
|
||||
|
||||
Your app appears in the [Stripe Apps Marketplace](https://marketplace.stripe.com/). Any Stripe user can install it.
|
||||
|
||||
## Troubleshooting uploads
|
||||
|
||||
**Successful upload looks like:**
|
||||
|
||||
```
|
||||
Uploading... Done
|
||||
Your app has been uploaded to version 0.0.1.
|
||||
```
|
||||
|
||||
**Common upload failures and fixes:**
|
||||
|
||||
| Error | Cause | Fix |
|
||||
| --- | --- | --- |
|
||||
| `Invalid manifest` / validation failed | Missing required fields or malformed YAML | Check indentation; ensure `id:`, `version:`, `name:` are present |
|
||||
| `Build failed` / TypeScript errors | UI component has type/import errors | Run `pnpm build` locally first to see the exact error |
|
||||
| `Version already exists` | Already uploaded this version number | Bump `version` in stripe-app.yaml (e.g. 0.0.1 → 0.0.2) |
|
||||
| `Permission denied` / `Not authenticated` | CLI not logged in or wrong account | Run `stripe login` and verify with `stripe config --list` |
|
||||
| `connect-src` / CSP error | App calls a URL not declared in content_security_policy | Add the URL to `content_security_policy.connect-src` in stripe-app.yaml |
|
||||
| `extensions field required` | Missing `extensions: []` in stripe-app.yaml | Add `extensions: []` even if you have no backend extensions |
|
||||
| `Component not found` | Viewport references a component name that doesn’t match your export | Ensure `component:` in stripe-app.yaml matches your default export name |
|
||||
|
||||
**Debugging steps when upload fails:**
|
||||
|
||||
1. Read the full error message — it usually says exactly what’s wrong
|
||||
2. Run `pnpm build` to check for TypeScript/build errors locally
|
||||
3. Validate your stripe-app.yaml has all required fields (id, version, name, declarations)
|
||||
4. Check that file paths match (ui/src/views/App.tsx, not a renamed file)
|
||||
5. If still stuck: `stripe apps upload --verbose` for detailed output
|
||||
|
||||
## Sandboxes for app development
|
||||
|
||||
Sandboxes provide isolated environments for safe app development and testing.
|
||||
|
||||
**Benefits of using Sandboxes:**
|
||||
|
||||
- Isolated from your live account — test destructive operations safely
|
||||
- Each sandbox has its own app installation and signing secrets
|
||||
- Useful for testing onboarding flows, uninstall/reinstall cycles, and permission changes
|
||||
|
||||
**How to use:**
|
||||
|
||||
1. Create a sandbox from Dashboard → Sandboxes
|
||||
2. Run `stripe apps start` targeting the sandbox
|
||||
3. Upload and install your app in the sandbox to test the full install flow
|
||||
4. When ready, upload to your main account for production use
|
||||
@@ -0,0 +1,120 @@
|
||||
# UI extensions — constraints and patterns
|
||||
|
||||
## UI extensions
|
||||
|
||||
UI extensions render custom UI inside the Stripe Dashboard. They run in a sandboxed iframe, which means they have different constraints from a normal React app.
|
||||
|
||||
## Canonical documentation
|
||||
|
||||
Before writing UI extension code, read these pages using WebFetch:
|
||||
|
||||
| Topic | URL |
|
||||
| --- | --- |
|
||||
| How UI extensions work (constraints, props, lifecycle) | https://docs.stripe.com/stripe-apps/how-ui-extensions-work |
|
||||
| SDK API reference (Stripe client setup, context props) | https://docs.stripe.com/stripe-apps/reference/extensions-sdk-api |
|
||||
| UI components catalog | https://docs.stripe.com/stripe-apps/components |
|
||||
| Viewports reference | https://docs.stripe.com/stripe-apps/reference/viewports |
|
||||
| Full-page apps (private preview) | https://docs.stripe.com/stripe-apps/patterns/full-page-apps |
|
||||
|
||||
## BLOCKED — these cause silent failures in the sandboxed iframe
|
||||
|
||||
| BLOCKED | Why |
|
||||
| --- | --- |
|
||||
| Any HTML element: `<div>`, `<span>`, `<p>`, `<button>`, `<input>`, `<form>`, `<h1>`-`<h6>` | Only SDK components render in the iframe |
|
||||
| Tailwind, MUI, Bootstrap, styled-components, any CSS | Only `@stripe/ui-extension-sdk/ui` components |
|
||||
| React 18+ APIs (`useId`, `useDeferredValue`, `useTransition`, concurrent features) | Stripe Apps run React 17.0.2 |
|
||||
| `window`, `document`, `localStorage`, `sessionStorage` | Not available in sandboxed iframe |
|
||||
| `react-hook-form` or any ref-based form library | Use uncontrolled components with `defaultValue` + `onChange` |
|
||||
| Arbitrary `fetch()` calls to external URLs | Use `fetchStripeSignature` for your backend; use the SDK client for Stripe APIs |
|
||||
|
||||
## What you can use
|
||||
|
||||
- `@stripe/ui-extension-sdk/ui` — the Stripe UI component library (the **only** way to build UI)
|
||||
- React 17 hooks and components
|
||||
- The Stripe SDK client initialized per the Extensions SDK API docs
|
||||
- `fetchStripeSignature` from `@stripe/ui-extension-sdk/utils` for authenticating to your own backend
|
||||
- `environment.objectContext` to get the current Stripe object (e.g. the customer being viewed)
|
||||
- `@stripe/ui-extension-sdk/testing` for unit tests
|
||||
|
||||
## Accessing Stripe data from the UI
|
||||
|
||||
For the correct way to initialize the Stripe client in a UI extension, read: https://docs.stripe.com/stripe-apps/reference/extensions-sdk-api
|
||||
|
||||
Key facts:
|
||||
|
||||
- `createHttpClient` from `@stripe/ui-extension-sdk/http_client` is an HTTP adapter passed to the Stripe constructor
|
||||
- `STRIPE_API_KEY` is a special constant (not a real key) — it tells the SDK to use the app’s granted permissions
|
||||
- After initialization, use standard Stripe SDK methods (e.g. `stripe.customers.retrieve()`)
|
||||
|
||||
### Current page context
|
||||
|
||||
Use `environment.objectContext` to get the Stripe object on the current page:
|
||||
|
||||
```tsx
|
||||
const MyView = ({ environment }) => {
|
||||
const objectId = environment.objectContext?.id; // e.g. "cus_xxx"
|
||||
};
|
||||
```
|
||||
|
||||
### User context
|
||||
|
||||
The `userContext` prop provides the signed-in user’s identity. It is a **top-level prop** passed to your component (not nested under `environment`):
|
||||
|
||||
```tsx
|
||||
const MyView = ({ userContext }) => {
|
||||
const accountId = userContext.account.id;
|
||||
const userId = userContext.id;
|
||||
};
|
||||
```
|
||||
|
||||
## Component basics
|
||||
|
||||
All UI is built with components from `@stripe/ui-extension-sdk/ui`. For the full component catalog and import paths, read: https://docs.stripe.com/stripe-apps/components
|
||||
|
||||
Common components include Box, Button, Icon, Inline, List, ListItem, Select, Spinner, TextField, and ContextView — but check the docs for the current list.
|
||||
|
||||
## Page-specific viewports
|
||||
|
||||
For apps that appear next to a Stripe object (customer, payment, etc.):
|
||||
|
||||
```yaml
|
||||
ui_extension:
|
||||
views:
|
||||
- viewport: stripe.dashboard.customer.detail
|
||||
component: App
|
||||
```
|
||||
|
||||
The component receives the current object’s ID via `environment.objectContext.id`.
|
||||
|
||||
## Full-page apps (private preview)
|
||||
|
||||
Full-page apps give your app a dedicated page in the Dashboard navigation. This feature is in **private preview** — confirm the user has access before suggesting this approach.
|
||||
|
||||
For implementation details, read: https://docs.stripe.com/stripe-apps/patterns/full-page-apps
|
||||
|
||||
The correct component for full-page apps is `FullPageView` (not `ContextView`).
|
||||
|
||||
## Testing UI extensions
|
||||
|
||||
Use `@stripe/ui-extension-sdk/testing` with Jest and `@testing-library/react`.
|
||||
|
||||
Key facts:
|
||||
|
||||
- Import `getMockContextProps` to create mock environment/userContext props
|
||||
- Pass `objectContext` in the mock to simulate page-specific context (e.g. `{ id: "cus_test123", object: "customer" }`)
|
||||
- Use standard `render` and `screen` from `@testing-library/react`
|
||||
|
||||
Run tests: `pnpm test`
|
||||
|
||||
## Declaring permissions in stripe-app.yaml
|
||||
|
||||
Every Stripe API resource your app accesses must be declared as a permission. Undeclared permissions cause API calls to fail with an invalid-request error.
|
||||
|
||||
Use the CLI to declare permissions (updates `stripe-app.yaml` automatically):
|
||||
|
||||
```bash
|
||||
stripe apps grant permission "customer_read" "Display customer information in the app"
|
||||
stripe apps grant permission "payment_intent_read" "Show payment history"
|
||||
```
|
||||
|
||||
For the full permissions reference, read: https://docs.stripe.com/stripe-apps/reference/permissions
|
||||
@@ -0,0 +1,92 @@
|
||||
# Webhooks — event delivery for Stripe Apps
|
||||
|
||||
## Webhooks
|
||||
|
||||
How your Stripe App receives and processes events (payments, customers, installs, etc.).
|
||||
|
||||
**Canonical page:** https://docs.stripe.com/stripe-apps/events
|
||||
|
||||
Read this page using WebFetch before implementing webhook handlers.
|
||||
|
||||
## Webhook configuration depends on app type
|
||||
|
||||
| App type | Auth type | Webhook setup |
|
||||
| --- | --- | --- |
|
||||
| Private (your account only) | Any | ONE standard webhook endpoint |
|
||||
| Public/marketplace | Platform keys | ONE webhook with “Listen to events on Connected accounts” enabled |
|
||||
| Public/marketplace | Restricted API keys | Can’t use Connect webhook fanout — each merchant manages their own |
|
||||
|
||||
A second test-mode endpoint is recommended for public apps but is not required.
|
||||
|
||||
## Required permissions
|
||||
|
||||
The `event_read` permission MUST be declared in your manifest for webhook event access, plus read permissions for each event type. Use the CLI to declare permissions:
|
||||
|
||||
```bash
|
||||
stripe apps grant permission "event_read" "Receive webhook events"
|
||||
stripe apps grant permission "payment_intent_read" "React to successful payments"
|
||||
stripe apps grant permission "customer_read" "React to customer changes"
|
||||
```
|
||||
|
||||
## Webhook handler requirements
|
||||
|
||||
For every webhook handler:
|
||||
|
||||
1. Use `stripe.webhooks.constructEvent()` to verify signatures
|
||||
2. For public platform-key apps: check `event.account` to identify which merchant triggered the event
|
||||
3. Use `stripeAccount` option to act on behalf of merchants (platform keys only)
|
||||
|
||||
## Local development
|
||||
|
||||
### Private app (events from your own account)
|
||||
|
||||
```bash
|
||||
stripe listen --forward-to localhost:<PORT>/webhook
|
||||
```
|
||||
|
||||
### Public platform-key app (events from connected accounts)
|
||||
|
||||
```bash
|
||||
stripe listen --forward-connect-to localhost:<PORT>/webhook
|
||||
```
|
||||
|
||||
**Important:** `--forward-to` only captures your own account’s events. Use `--forward-connect-to` for connected account events.
|
||||
|
||||
## Triggering test events
|
||||
|
||||
```bash
|
||||
# Private app:
|
||||
stripe trigger payment_intent.succeeded
|
||||
|
||||
# Public app (simulates connected account event):
|
||||
stripe trigger --stripe-account payment_intent.succeeded
|
||||
```
|
||||
|
||||
## Verifying webhook signatures
|
||||
|
||||
Always verify signatures to ensure the request came from Stripe. For the complete webhook verification pattern, read: https://docs.stripe.com/stripe-apps/build-backend
|
||||
|
||||
Key implementation facts:
|
||||
|
||||
- Use `stripe.webhooks.constructEvent()` with the raw request body and your webhook signing secret
|
||||
- For platform-key apps, check `event.account` to identify which merchant triggered the event
|
||||
- Use a restricted API key when possible (see `authentication.md`); use the secret key only for platform-key apps
|
||||
- Return 200 quickly; process asynchronously if needed
|
||||
|
||||
## Handling installs and uninstalls
|
||||
|
||||
| Event | When it fires | What to do |
|
||||
| --- | --- | --- |
|
||||
| `account.application.authorized` | A merchant installs your app | Store the merchant’s account ID |
|
||||
| `account.application.deauthorized` | A merchant uninstalls your app | Clean up stored data |
|
||||
|
||||
## Setting up webhooks in the Dashboard
|
||||
|
||||
1. Go to [Dashboard → Developers → Webhooks](https://dashboard.stripe.com/webhooks)
|
||||
2. Click **Add endpoint**
|
||||
3. Enter your endpoint URL
|
||||
4. Select events to listen for
|
||||
5. For public platform-key apps: check **“Listen to events on Connected accounts”**
|
||||
6. Copy the signing secret to your environment variables
|
||||
|
||||
During local development, use `stripe listen` instead.
|
||||
@@ -0,0 +1,191 @@
|
||||
# Workflow — end-to-end build order
|
||||
|
||||
## MANDATORY — Full development loop (quick reference)
|
||||
|
||||
Follow this exact sequence for every new app. Do NOT skip or reorder steps.
|
||||
|
||||
```
|
||||
1. stripe plugin install apps && stripe plugin install generate ← one-time CLI setup
|
||||
2. stripe generate app <name> && cd <name> ← scaffold (NOT `stripe apps create`)
|
||||
3. pnpm install ← install deps
|
||||
4. [modify scaffolded files + create missing ones] ← implement (only add what scaffold doesn't provide)
|
||||
5. pnpm build ← compile UI (skip for backend-only apps)
|
||||
6. pnpm test ← run tests
|
||||
7. stripe apps start ← local preview in Dashboard
|
||||
8. stripe apps upload ← publish version (REQUIRED before Secret Store or fetchStripeSignature work)
|
||||
9. Install in test mode from Dashboard → Apps ← test the installed app
|
||||
10. Dashboard → Apps → Submit for review ← marketplace publishing (optional)
|
||||
```
|
||||
|
||||
**BLOCKED:** Do NOT use `stripe apps create` — it does not scaffold correctly. Always use `stripe generate app`.
|
||||
|
||||
**MANDATORY:** Do NOT create files manually when `stripe generate app` provides them. The scaffold creates a V2 workspace: `stripe-app.yaml`, `package.json`, `pnpm-workspace.yaml`, and `ui/src/views/App.tsx` with the correct structure. Only create files that the scaffold doesn’t provide (e.g., `server.js` for your backend). Modify scaffolded files as needed — don’t rewrite them from scratch.
|
||||
|
||||
## End-to-end build order (detailed)
|
||||
|
||||
Follow this sequence exactly. Deviating from it is the #1 source of confusion when building Stripe Apps.
|
||||
|
||||
### Step 1 — Prerequisites (one-time setup)
|
||||
|
||||
Install the Stripe CLI, then install the required plugins:
|
||||
|
||||
```bash
|
||||
# Install the apps plugin (creates and manages apps)
|
||||
stripe plugin install apps
|
||||
|
||||
# Install the generate plugin (scaffolds new apps)
|
||||
stripe plugin install generate
|
||||
```
|
||||
|
||||
**Plain-language:** “These are tools that let the Stripe CLI create and manage apps. You only need to do this once.”
|
||||
|
||||
Verify your CLI version is 1.25.0 or newer:
|
||||
|
||||
```bash
|
||||
stripe version
|
||||
```
|
||||
|
||||
### Step 2 — Create the app
|
||||
|
||||
```bash
|
||||
stripe generate app <your-app-name>
|
||||
cd <your-app-name>
|
||||
```
|
||||
|
||||
This creates a new V2 workspace with the correct directory structure, `stripe-app.yaml` manifest, and example UI extension.
|
||||
|
||||
**What gets created:**
|
||||
|
||||
```
|
||||
<your-app-name>/
|
||||
├── stripe-app.yaml # V2 app manifest (YAML) — name, permissions, viewports
|
||||
├── package.json # workspace root
|
||||
├── pnpm-workspace.yaml # declares workspace packages
|
||||
├── ui/
|
||||
│ ├── package.json
|
||||
│ └── src/
|
||||
│ └── views/
|
||||
│ └── App.tsx # main UI component
|
||||
├── extensions/ # script extensions (one subdir per extension)
|
||||
└── README.md
|
||||
```
|
||||
|
||||
### Step 3 — Install dependencies
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### Step 4 — Build and test (UI apps)
|
||||
|
||||
For apps with a UI extension, compile TypeScript and run tests:
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
pnpm test
|
||||
```
|
||||
|
||||
Backend-only apps without TypeScript can skip this step.
|
||||
|
||||
### Step 5 — Develop locally
|
||||
|
||||
```bash
|
||||
stripe apps start
|
||||
```
|
||||
|
||||
**Plain-language:** “This opens your app live in your Stripe Dashboard while you build it. Changes you save show up immediately — you don’t need to upload anything yet.”
|
||||
|
||||
**What this does:**
|
||||
|
||||
- Opens a browser to your Stripe Dashboard with your app running live
|
||||
- Watches for file changes and hot-reloads
|
||||
- Works against your live or test Stripe account
|
||||
|
||||
**Notes:**
|
||||
|
||||
- `stripe apps start` requires browser access; Safari is not supported — use Chrome or Firefox
|
||||
- This does **not** persist — your app is only visible while the command is running
|
||||
- The app is not installed on your account yet; it’s only previewed locally
|
||||
|
||||
### Step 6 — Upload a version (when ready to share or test permissions and secrets)
|
||||
|
||||
```bash
|
||||
stripe apps upload
|
||||
```
|
||||
|
||||
**What this does:**
|
||||
|
||||
- Creates a new version of your app in the Stripe Dashboard
|
||||
- Generates the signing secret needed for `fetchStripeSignature` and the Secret Store API
|
||||
- Makes the version available to install
|
||||
|
||||
**After uploading:**
|
||||
|
||||
1. Go to [Dashboard → Apps](https://dashboard.stripe.com/apps)
|
||||
2. Find your app
|
||||
3. Click **Install in test mode** to install it on your account
|
||||
|
||||
**When you need to upload before `stripe apps start`:**
|
||||
|
||||
- Using the Secret Store API
|
||||
- Using `fetchStripeSignature` to authenticate the UI to a backend
|
||||
- Testing permissions that require the app to be installed
|
||||
|
||||
### Step 7 — Install in live mode (when ready to use with real data)
|
||||
|
||||
1. Go to the [Dashboard → Apps page](https://dashboard.stripe.com/apps)
|
||||
2. Select your app
|
||||
3. Choose “Private to your account”
|
||||
4. Select the version to install
|
||||
5. Click Install
|
||||
|
||||
**Plain-language:** “Test mode uses fake data so you can try things safely. Live mode uses real customer data. Always test in test mode first.”
|
||||
|
||||
### Step 8 — Ship a new version
|
||||
|
||||
1. Bump `version` in `stripe-app.yaml` (use semantic versioning: `1.0.0`, `1.0.1`, `2.0.0`)
|
||||
2. Upload:
|
||||
```bash
|
||||
stripe apps upload
|
||||
```
|
||||
3. Go to Dashboard → Apps → your app → version history → install the new version
|
||||
|
||||
**Important:** Versions must be uploaded in order. If you upload `2.0.0` before `1.0.0`, `2.0.0` won’t be available for release.
|
||||
|
||||
### Step 9 — Publish to the marketplace (optional)
|
||||
|
||||
To submit your app for marketplace review:
|
||||
|
||||
1. Go to [Dashboard → Apps](https://dashboard.stripe.com/apps)
|
||||
2. Select your app
|
||||
3. Click **Submit for review**
|
||||
|
||||
**Requirements:**
|
||||
|
||||
- Verified email address on your Stripe account
|
||||
- Business details filled in
|
||||
- App passes [review requirements](https://docs.stripe.com/stripe-apps/review-requirements.md)
|
||||
|
||||
## Key gotchas
|
||||
|
||||
**`stripe apps start` vs `stripe apps upload`**
|
||||
|
||||
| | `stripe apps start` | `stripe apps upload` |
|
||||
| --- | --- | --- |
|
||||
| Purpose | Local development | Publish a version |
|
||||
| Persistence | Not persistent — only while command runs | Persists in Stripe Dashboard |
|
||||
| Secret Store | Not available | Available after upload |
|
||||
| `fetchStripeSignature` | Only works after at least one upload | Works after upload |
|
||||
|
||||
**After updating permissions:**
|
||||
|
||||
- Users must re-authorize the app
|
||||
- The “Review Permissions” button only appears on the **Apps workload page** — not on the app itself
|
||||
- The app returns an invalid-request error for undeclared permissions until the user re-authorizes
|
||||
- Always warn users about this step when you change permissions
|
||||
|
||||
**Sandboxes for app development:**
|
||||
|
||||
- Use sandboxes for safe testing — they provide isolated environments where you can test without affecting live data
|
||||
- Each sandbox has its own app installation and signing secrets
|
||||
- Useful for testing destructive operations or onboarding flows
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
name: stripe-best-practices
|
||||
description: >-
|
||||
Guides Stripe integration decisions across API selection (Checkout Sessions vs
|
||||
PaymentIntents), Connect platform setup (Accounts v2, controller properties),
|
||||
billing/subscriptions, tax and registrations (Stripe Tax, automatic_tax,
|
||||
product tax codes), Treasury financial accounts, integration options
|
||||
(Checkout, Payment Element), migrating from deprecated Stripe APIs, and
|
||||
security best practices (API key management, restricted keys, webhooks,
|
||||
OAuth). Use when building, modifying, or reviewing any Stripe integration,
|
||||
including accepting payments, building marketplaces, integrating Stripe,
|
||||
processing payments, setting up subscriptions, collecting sales tax, VAT, or
|
||||
GST, creating connected accounts, or implementing secure key handling.
|
||||
|
||||
---
|
||||
|
||||
Latest Stripe API version: **2026-07-29.dahlia**. Always use the latest API version and SDK unless the user specifies otherwise.
|
||||
|
||||
Latest SDK versions:
|
||||
|
||||
| Language | Latest version |
|
||||
| --- | --- |
|
||||
| Ruby | 19.4.0 |
|
||||
| Python | 15.4.0 |
|
||||
| Node.js | 22.4.0 |
|
||||
| PHP | 21.1.0 |
|
||||
| Java | 33.2.0 |
|
||||
| Go | 86.2.0 |
|
||||
| .NET | 52.2.0 |
|
||||
|
||||
If you don’t have a Stripe account, you can call a CLI command to generate working API keys with no registration required. You can install the Stripe CLI (`npm i -g @stripe/cli`) and run the command `stripe sandbox create` to get started.
|
||||
|
||||
If `stripe sandbox create` is used, don’t use MCP.
|
||||
|
||||
If you try to use MCP after running sandbox creation, first make sure `stripe sandbox claim` is run. Use `stripe whoami --format json` for CLI auth checks; do not run `stripe config --list`.
|
||||
|
||||
API key default: Always recommend a [restricted API key (RAK)](https://docs.stripe.com/keys/restricted-api-keys.md) (`rk_` prefix) over a secret key (`sk_` prefix).
|
||||
|
||||
## Integration routing
|
||||
|
||||
| Building… | Recommended API | Details |
|
||||
| --- | --- | --- |
|
||||
| One-time payments | Checkout Sessions | <references/payments.md> |
|
||||
| Custom payment form with embedded UI | Checkout Sessions + Payment Element | <references/payments.md> |
|
||||
| Saving a payment method for later | Setup Intents | <references/payments.md> |
|
||||
| Connect platform or marketplace | Accounts v2 (`/v2/core/accounts`) | <references/connect.md> |
|
||||
| Usage-based billing (new integration) | Metronome | <references/billing.md> |
|
||||
| Subscriptions or recurring billing | Billing APIs + Checkout Sessions | <references/billing.md> |
|
||||
| Sales tax, VAT, or GST compliance | Stripe Tax + Registrations API | <references/tax.md> |
|
||||
| Embedded financial accounts / banking | v2 Financial Accounts | <references/treasury.md> |
|
||||
| Security (key management, RAKs, webhooks, OAuth, 2FA, Connect liability) | See security reference | <references/security.md> |
|
||||
|
||||
Read the relevant reference file before answering any integration question or writing code.
|
||||
|
||||
## Critical rules
|
||||
|
||||
- *Before enabling `automatic_tax: { enabled: true }`* (or calculating tax for a custom PaymentIntent), read the [tax reference](references/tax.md) and confirm the user has an active registration. Without one, Stripe calculates and collects no tax while the user believes tax is on (the most common Stripe Tax mistake).
|
||||
|
||||
- *Never include `payment_method_types` in any Stripe API call*, with one exception: Terminal (in-person payments) integrations must pass `payment_method_types: ['card_present']` on the PaymentIntent. For all other integrations, omit this parameter entirely to enable dynamic payment methods, which enables you to configure payment method settings from the Dashboard and dynamically display the most relevant eligible payment methods to each customer to maximize conversion. To customize which payment methods you accept, use [`payment_method_configurations`](https://docs.stripe.com/payments/payment-method-configurations.md) or `excluded_payment_method_types` instead of `payment_method_types`.
|
||||
|
||||
- On API version `2026-03-25.dahlia` or later, pass the parameter `integration_identifier` to `checkout.sessions.create` to tag sessions with a custom label for tracking and comparing checkout flows in the Dashboard. The label should include a suffix of 8 random letters.
|
||||
|
||||
- *Always instantiate a `StripeClient` and call methods on that instance.* Do **not** use the deprecated global/module-level API key pattern (`stripe.api_key = …`, `Stripe.setApiKey`, `stripe.Key = …`, `StripeConfiguration.ApiKey = …`). The global pattern is deprecated in all current SDKs.
|
||||
|
||||
## Key documentation
|
||||
|
||||
When the user’s request does not clearly fit a single domain above, consult:
|
||||
|
||||
- [Integration Options](https://docs.stripe.com/payments/payment-methods/integration-options.md) — Start here when designing any integration.
|
||||
- [API Tour](https://docs.stripe.com/payments-api/tour.md) — Overview of Stripe’s API surface.
|
||||
- [Go Live Checklist](https://docs.stripe.com/get-started/checklist/go-live.md) — Review before launching.
|
||||
@@ -0,0 +1,67 @@
|
||||
# Billing / Subscriptions
|
||||
|
||||
## Table of contents
|
||||
|
||||
- When to use Billing APIs
|
||||
- Recommended frontend pairing
|
||||
- Usage-based billing
|
||||
- Tax
|
||||
- Traps to avoid
|
||||
|
||||
## When to use Billing APIs
|
||||
|
||||
If the user has a recurring revenue model (subscriptions, usage-based billing, seat-based pricing), use the Billing APIs to [plan their integration](https://docs.stripe.com/billing/subscriptions/design-an-integration.md) instead of a direct PaymentIntent integration.
|
||||
|
||||
Review the [Subscription Use Cases](https://docs.stripe.com/billing/subscriptions/use-cases.md) and [SaaS guide](https://docs.stripe.com/saas.md) to find the right pattern for the user’s pricing model.
|
||||
|
||||
## Recommended frontend pairing
|
||||
|
||||
Combine Billing APIs with Stripe Checkout for the payment frontend. Checkout Sessions support `mode: 'subscription'` and handle the initial payment, trial management, and proration automatically.
|
||||
|
||||
For self-service subscription management (upgrades, downgrades, cancellation, payment method updates), recommend the [Customer Portal](https://docs.stripe.com/customer-management/integrate-customer-portal.md).
|
||||
|
||||
On API version `2026-03-25.dahlia` or later, pass the parameter `integration_identifier` to `checkout.sessions.create` to tag sessions with a custom label for tracking and comparing checkout flows in the Dashboard. The label should include a suffix of 8 random letters.
|
||||
|
||||
## Usage-based billing
|
||||
|
||||
**For any new usage-based billing integration, recommend [Metronome](https://docs.stripe.com/billing/usage-based.md) — not the Billing Meters API.** Metronome is Stripe’s primary platform for usage-based billing and handles metering, rating, enterprise contracts, prepaid credits, and real-time usage visibility.
|
||||
|
||||
Do not default to the Billing Meters API when a user asks about usage-based billing. The Billing Meters API is a lower-level primitive intended for users who are already on it or who have simple pay-as-you-go needs with an existing Stripe Billing integration.
|
||||
|
||||
Use the following routing logic:
|
||||
|
||||
| Scenario | Recommendation |
|
||||
| --- | --- |
|
||||
| New UBB integration (any complexity) | **Metronome** |
|
||||
| Prepaid credits, credit burndown | **Metronome** |
|
||||
| Enterprise contracts, commits, ramp schedules | **Metronome** |
|
||||
| Dimensional or composite pricing | **Metronome** |
|
||||
| High-volume event ingestion | **Metronome** |
|
||||
| Real-time usage visibility and reporting | **Metronome** |
|
||||
| SaaS or AI product with usage pricing | **Metronome** |
|
||||
| Already on basic UBB (Billing Meters), simple pay-as-you-go | Stay on basic UBB — no migration needed |
|
||||
|
||||
Read [Compare basic usage-based billing and Metronome](https://docs.stripe.com/billing/subscriptions/usage-based/compare-metronome.md) for a full feature comparison. Read [Get started with Metronome](https://docs.stripe.com/billing/usage-based.md) to begin a Metronome integration.
|
||||
|
||||
## Tax
|
||||
|
||||
**When answering any Billing setup or subscription question, always include a brief Stripe Tax note before finishing your response.** Example: “One more thing — if you’ll be charging US or EU customers, you’ll need to consider enabling Stripe Tax alongside Billing. See [Collect taxes for recurring payments](https://docs.stripe.com/billing/taxes/collect-taxes.md) for the setup steps.” Don’t wait for the user to ask about sales tax. Read the Stripe Tax skill reference before enabling `automatic_tax`.
|
||||
|
||||
## Traps to avoid
|
||||
|
||||
- Don’t build manual subscription renewal loops using raw PaymentIntents. Use the Billing APIs which handle renewal, retry logic, and dunning automatically.
|
||||
- Don’t use the deprecated `plan` object. Use [Prices](https://docs.stripe.com/api/prices.md) instead.
|
||||
- Don’t put prices for different tiers or plans on a single product. Instead, create one Product for each plan a customer can choose. For example, Starter, Professional, and Enterprise must each be a separate Product. Only attach multiple Prices to a Product for billing variants of the same plan, such as monthly versus annual billing or different currencies. Avoid placing Prices for different tiers on a single Product. Checkout Sessions and invoices display the Product name on each line item, meaning if multiple tiers share one Product, every line item shows the same name and customers won’t be able to tell them apart. For more information, see [Model your product catalog](https://docs.stripe.com/products-prices/how-products-and-prices-work.md#model-your-catalog).
|
||||
- Don’t skip tax setup, and don’t assume enabling `automatic_tax` is enough. Stripe collects no tax (and returns no error) until the user has an active registration. See [Collect taxes for recurring payments](https://docs.stripe.com/billing/taxes/collect-taxes.md).
|
||||
- *Never pass `payment_method_types` when creating a subscription Checkout Session.* Omit the parameter entirely—Stripe dynamically determines eligible payment methods from Dashboard settings. Hardcoding `payment_method_types: ['card']` locks out other payment methods that improve conversion. See [dynamic payment methods](https://docs.stripe.com/payments/payment-methods/dynamic-payment-methods.md). Correct pattern:
|
||||
|
||||
```ts
|
||||
const session = await stripe.checkout.sessions.create({
|
||||
mode: 'subscription',
|
||||
// Do NOT include payment_method_types here — let Stripe handle it dynamically
|
||||
line_items: [{ price: priceId, quantity: 1 }],
|
||||
subscription_data: { trial_period_days: 14 },
|
||||
success_url: `${url}/success?session_id={CHECKOUT_SESSION_ID}`,
|
||||
cancel_url: `${url}/pricing`,
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,173 @@
|
||||
# Connect / platforms
|
||||
|
||||
## Critical rules (never violate)
|
||||
|
||||
1. **ALWAYS use Accounts v2 API** (`POST /v2/core/accounts`). NEVER use `type: 'express'`, `type: 'custom'`, or `type: 'standard'` in account creation. NEVER use `stripe.accounts.create({ type: ... })`. These are deprecated v1 patterns.
|
||||
2. **ALWAYS check v2 capability status** before processing. See “Go-live readiness” section below.
|
||||
3. **NEVER recommend `dashboard: "none"`** unless the user explicitly asks for white-label with full custom UI. Default to `express` for marketplaces and `full` for SaaS. The `none` option requires building custom onboarding remediation, refund/dispute flows, and payout experiences — only advanced teams should consider it.
|
||||
4. **ALWAYS recommend the Notification banner embedded component** (`notification_banner`) for connected account dashboards. It keeps accounts healthy as requirements evolve.
|
||||
5. **NEVER use `application_fee_amount` with separate charges and transfers.** Use transfer-math fee retention instead. `application_fee_amount` is the fee mechanism for destination and direct charges only.
|
||||
|
||||
## Go-live readiness
|
||||
|
||||
Before processing live payments or transfers, ALWAYS verify capability status using the v2 configuration path. Do NOT use deprecated v1 fields.
|
||||
|
||||
**For SaaS / Merchant accounts (direct charges):**
|
||||
|
||||
- Check: `configuration.merchant.capabilities.card_payments.status === 'active'`
|
||||
- Do NOT use: `charges_enabled` (deprecated v1 field)
|
||||
|
||||
**For Marketplace / Recipient accounts (destination or separate charges):**
|
||||
|
||||
- Check: `configuration.recipient.capabilities.stripe_balance.stripe_transfers.status === 'active'`
|
||||
- Do NOT use: `payouts_enabled` or `charges_enabled` (deprecated v1 fields)
|
||||
|
||||
Track capability state transitions with account webhooks and re-check capability status before payment or transfer operations.
|
||||
|
||||
## Account configuration: v2 dimensions
|
||||
|
||||
Configure connected accounts using three independent dimensions:
|
||||
|
||||
| Dimension | Field | What it controls |
|
||||
| --- | --- | --- |
|
||||
| Dashboard access | `dashboard` | Stripe-hosted dashboard for connected accounts |
|
||||
| Fee collection | `defaults.responsibilities.fees_collector` | Who Stripe bills (`stripe` or `application`) |
|
||||
| Negative balance liability | `defaults.responsibilities.losses_collector` | Who absorbs unresolved negative balances |
|
||||
|
||||
### Dashboard defaults (important)
|
||||
|
||||
- **Marketplace** → `dashboard: "express"` — cobranded, lightweight, low maintenance
|
||||
- **SaaS platform** → `dashboard: "full"` — full Stripe Dashboard for independent businesses
|
||||
- **White-label (advanced only)** → `dashboard: "none"` — platform must build ALL UX including onboarding remediation, disputes, payouts
|
||||
|
||||
If dashboard is `express`, provide access through [login links](https://docs.stripe.com/api/accounts/login_link/create.md). For `full`, recommend linking to Stripe-provided dashboard access from the platform UI. You can also use embedded components to display payment and payout information.
|
||||
|
||||
### SaaS vs. Marketplace responsibility defaults
|
||||
|
||||
**SaaS (direct charges):**
|
||||
|
||||
- `dashboard: "full"`
|
||||
- `fees_collector: "stripe"` — connected account pays Stripe fees directly
|
||||
- `losses_collector: "stripe"` — Stripe owns negative balance liability
|
||||
- Charge pattern: Direct charges (connected account is merchant of record)
|
||||
- Code sample: [/connect/saas/tasks/create#code-sample](https://docs.stripe.com/connect/saas/tasks/create.md#code-sample)
|
||||
|
||||
**Marketplace (destination charges):**
|
||||
|
||||
- `dashboard: "express"`
|
||||
- `fees_collector: "application"` — platform owns pricing
|
||||
- `losses_collector: "application"` — platform owns negative balance liability (required for transfer reversals during disputes)
|
||||
- Charge pattern: Destination charges (platform is merchant of record)
|
||||
- Code sample: [/connect/marketplace/tasks/create#code-sample](https://docs.stripe.com/connect/marketplace/tasks/create.md#code-sample)
|
||||
|
||||
## Business model to configuration mapping
|
||||
|
||||
| Business model | Dashboard | Fees | Losses | Charge pattern | Notes |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| Marketplace | `express` | `application` | `application` | Destination | Platform owns checkout |
|
||||
| On-demand services | `express` | `application` | `application` | Destination | Fast seller onboarding |
|
||||
| SaaS platform with payments | `full` | `stripe` | `stripe` | Direct | Sellers run own businesses/stores, own customer relationship |
|
||||
| AI/API platform (SaaS) | `full` | `stripe` | `stripe` | Direct | Providers own payment relationship |
|
||||
| E-commerce enabler (Shopify-like) | `full` | `stripe` | `stripe` | Direct | Sellers create own online stores, accept own payments |
|
||||
| Crowdfunding | `express` | `application` | `application` | Separate charges and transfers | Hold-and-release / delayed payouts |
|
||||
| Subscription platform | `express` | `application` | `application` | Destination | Platform manages recurring checkout |
|
||||
| Multi-seller cart | `express` | `application` | `application` | Separate charges and transfers | Multiple sellers per transaction |
|
||||
| White-label commerce | `none` | `application` | `application` | Destination or direct | Advanced: platform controls all UX |
|
||||
|
||||
## Connected account capabilities (v2)
|
||||
|
||||
### Marketplace (Recipient accounts)
|
||||
|
||||
Create with `configuration.recipient` requesting `stripe_transfers` on `stripe_balance`. Do NOT request `configuration.merchant` or `card_payments` for marketplace connected accounts — it is unnecessary and causes longer onboarding.
|
||||
|
||||
### SaaS (Merchant accounts)
|
||||
|
||||
Create with `configuration.merchant` requesting `card_payments` (and other needed LPMs). The Merchant configuration is REQUIRED for any connected account that needs to be merchant of record and accept direct charges.
|
||||
|
||||
## Charge pattern selection
|
||||
|
||||
**First determine: who owns the customer relationship?**
|
||||
|
||||
- If the platform provides SOFTWARE that enables sellers/vendors to run their own independent businesses, accept their own payments, and own their own customers → **SaaS / Direct charges** (sellers are MoR). Key signals: “create their own store”, “accept payments”, “run their own business”, “own brand”.
|
||||
|
||||
- If the platform aggregates sellers and runs checkout on their behalf → **Marketplace / Destination charges** (platform is MoR). Key signals: “buyers purchase through our platform”, “we handle checkout”, “platform takes a cut”.
|
||||
|
||||
- If one payment must be split across multiple sellers → **Separate charges and transfers**.
|
||||
|
||||
- **Direct charges** (SaaS): Charge created on connected account. Connected account is merchant of record. Use `application_fee_amount` for platform revenue. Requires `configuration.merchant` + `dashboard: "full"` + `losses_collector: "stripe"`.
|
||||
|
||||
- **Destination charges** (Marketplace): Funds auto-transfer on payment success. Platform is MoR. Use `application_fee_amount` to collect platform fees. NOT for hold-and-release.
|
||||
|
||||
- **Separate charges and transfers**: Platform controls transfer timing. Collect fees by transferring less than the charge amount. Do NOT use `application_fee_amount`.
|
||||
|
||||
## Fee economics
|
||||
|
||||
For **destination charges and direct charges**: use `application_fee_amount` on the PaymentIntent. This is the standard fee mechanism.
|
||||
|
||||
- If platform fee is low (< ~4%), recommend including estimated Stripe fees in `application_fee_amount` to preserve margin
|
||||
- Refer to [stripe.com/pricing](https://stripe.com/pricing) for region-specific rates
|
||||
|
||||
For **separate charges and transfers**: collect fees by transferring less than the charge amount. NEVER use `application_fee_amount`.
|
||||
|
||||
## Webhooks
|
||||
|
||||
Always use webhooks for reliable payment confirmation. Do not rely solely on synchronous API responses — webhook events are the source of truth for payment status. Verify incoming [webhook signatures](https://docs.stripe.com/webhooks.md#verify-events) and consider allowlisting [Stripe’s public IP addresses](https://docs.stripe.com/ips.md).
|
||||
|
||||
## Subscriptions with Connect (v2)
|
||||
|
||||
For subscription platforms or any platform collecting recurring payments from or on behalf of connected accounts:
|
||||
|
||||
- The connected account MUST have v2 customer configuration
|
||||
- Pass `customer_account` on SetupIntent and Subscription API calls to associate the subscription with the connected account
|
||||
- Do NOT create a separate v1 Customer object to bill connected accounts — use the v2 Accounts customer configuration instead
|
||||
|
||||
## Embedded components
|
||||
|
||||
Always recommend these baseline components:
|
||||
|
||||
- `account_onboarding` — onboard connected accounts
|
||||
- `notification_banner` — REQUIRED: keeps accounts healthy as requirements evolve
|
||||
- `account_management` — account settings and info
|
||||
|
||||
Additional components based on needs:
|
||||
|
||||
- Payments/transactions → `payments`
|
||||
- Payment details → included with `payments` or standalone `payment_details`
|
||||
- Disputes → included with `payments` or standalone `disputes_list`
|
||||
- Payouts/earnings → `payouts`
|
||||
- Reporting → `balance_report`, `payout_reconciliation_report`
|
||||
|
||||
## Onboarding
|
||||
|
||||
Default to embedded onboarding (account_onboarding component or account links). Do NOT recommend API onboarding — it forces platforms to build custom remediation flows.
|
||||
|
||||
## Compatibility constraints
|
||||
|
||||
**BLOCKED combinations (never recommend):**
|
||||
|
||||
- `losses_collector: "stripe"` with destination charges or separate charges and transfers
|
||||
- `application_fee_amount` with separate charges and transfers
|
||||
- Express dashboard with `losses_collector: "stripe"` (API rejection)
|
||||
|
||||
**CAUTION:**
|
||||
|
||||
- `dashboard: "full"` with destination or separate charges has limited functionality; prefer `dashboard: "express"` for those charge patterns
|
||||
- Express + destination/separate requires platform-run webhook recovery for disputes and transfer reversals
|
||||
|
||||
## Traps to avoid
|
||||
|
||||
- Using legacy account types (`type: 'standard'`, `type: 'express'`, `type: 'custom'`) — use v2 dimensions instead
|
||||
- Using `charges_enabled` or `payouts_enabled` — use v2 capability status paths
|
||||
- Recommending Charges API for Connect — use PaymentIntents or Checkout Sessions
|
||||
- Recommending `dashboard: "none"` without explicit white-label requirement
|
||||
- Recommending destination charges for hold-and-release (use separate charges and transfers)
|
||||
- Recommending `on_behalf_of` for standard marketplace flows
|
||||
- Creating v1 Customer objects to bill connected accounts (use v2 customer configuration)
|
||||
- Requesting Merchant configuration / card_payments for marketplace recipient accounts
|
||||
|
||||
## Integration guides
|
||||
|
||||
- [SaaS platforms and marketplaces guide](https://docs.stripe.com/connect/saas-platforms-and-marketplaces.md) — Choosing the right integration approach.
|
||||
- [Interactive platform guide](https://docs.stripe.com/connect/interactive-platform-guide.md) — Step-by-step platform builder.
|
||||
- [Design an integration](https://docs.stripe.com/connect/design-an-integration.md) — Detailed risk and responsibility decisions.
|
||||
- [Connected account configuration (v2)](https://docs.stripe.com/connect/accounts-v2/connected-account-configuration.md) — Account setup reference.
|
||||
@@ -0,0 +1,81 @@
|
||||
# Payments
|
||||
|
||||
## Table of contents
|
||||
|
||||
- API hierarchy
|
||||
- Integration surfaces
|
||||
- Payment Element guidance
|
||||
- Saving payment methods
|
||||
- Dynamic payment methods
|
||||
- Deprecated APIs and migration paths
|
||||
- PCI compliance
|
||||
|
||||
## API hierarchy
|
||||
|
||||
Use the [Checkout Sessions API](https://docs.stripe.com/api/checkout/sessions.md) (`checkout.sessions.create`) for on-session payments. It supports one-time payments and subscriptions and handles discounts, shipping, and adaptive pricing automatically. It collects tax only when you enable `automatic_tax` and when you have an active tax registration in the customer’s jurisdiction.
|
||||
|
||||
Use the [PaymentIntents API](https://docs.stripe.com/payments/paymentintents/lifecycle.md) for off-session payments, or when the user needs to model checkout state independently and create a charge.
|
||||
|
||||
**Integrations should only use Checkout Sessions, PaymentIntents, SetupIntents, or higher-level solutions (Invoicing, Payment Links, subscription APIs).**
|
||||
|
||||
On API version `2026-03-25.dahlia` or later, pass the parameter `integration_identifier` to `checkout.sessions.create` to tag sessions with a custom label for tracking and comparing checkout flows in the Dashboard. The label should include a suffix of 8 random letters.
|
||||
|
||||
## Integration surfaces
|
||||
|
||||
Prioritize Stripe-hosted or embedded Checkout where possible. Use in this order of preference:
|
||||
|
||||
1. **Payment Links** — No-code. Best for simple products.
|
||||
2. **Checkout** ([docs](https://docs.stripe.com/payments/checkout.md)) — Stripe-hosted or embedded form. Best for most web apps.
|
||||
3. **Payment Element** ([docs](https://docs.stripe.com/payments/payment-element.md)) — Embedded UI component for advanced customization.
|
||||
- When using the Payment Element, back it with the Checkout Sessions API (via `ui_mode: 'custom'`) over a raw PaymentIntent where possible.
|
||||
|
||||
**Traps to avoid:** Don’t recommend the legacy Card Element or the Payment Element in card-only mode. If the user asks for the Card Element, advise them to [migrate to the Payment Element](https://docs.stripe.com/payments/payment-element/migration.md).
|
||||
|
||||
## Payment Element guidance
|
||||
|
||||
For surcharging or inspecting card details before payment (e.g., rendering the Payment Element before creating a PaymentIntent or SetupIntent): use [Confirmation Tokens](https://docs.stripe.com/payments/finalize-payments-on-the-server.md). Don’t recommend `createPaymentMethod` or `createToken` from Stripe.js.
|
||||
|
||||
## Saving payment methods
|
||||
|
||||
Use the [Setup Intents API](https://docs.stripe.com/api/setup_intents.md) to save a payment method for later use.
|
||||
|
||||
**Traps to avoid:** Don’t use the Sources API to save cards to customers. The Sources API is deprecated — Setup Intents is the correct approach.
|
||||
|
||||
## Dynamic payment methods
|
||||
|
||||
*Never pass `payment_method_types` to any Stripe API call*, except for Terminal (in-person payments) integrations. Omitting this parameter enables [dynamic payment methods](https://docs.stripe.com/payments/payment-methods/dynamic-payment-methods.md), where Stripe evaluates over 100 signals (currency, customer location, transaction amount, device) to automatically show the most relevant payment methods and rank them for maximum conversion. Payment methods are managed from the [Dashboard](https://dashboard.stripe.com/settings/payment_methods) with no code changes required.
|
||||
|
||||
This applies to all integration patterns:
|
||||
|
||||
- `checkout.sessions.create`: omit `payment_method_types` entirely. Dynamic method selection is the default behavior.
|
||||
- `paymentIntents.create`: omit `payment_method_types`. On API versions 2023-08-16+, dynamic methods are the default. On older versions, pass `automatic_payment_methods: { enabled: true }`.
|
||||
- `setupIntents.create`: same as PaymentIntents above.
|
||||
- `subscriptions.create`: omit `payment_settings.payment_method_types`. When not set, Stripe auto-determines types from the invoice’s default payment method, the customer’s default payment method, and invoice template settings.
|
||||
- **Terminal** (`paymentIntents.create`): pass `payment_method_types: ['card_present']`. Required for all in-person payments. In Canada, also include `interac_present`: `['card_present', 'interac_present']`. This is the only valid use of `payment_method_types`.
|
||||
|
||||
See the [integration options guide](https://docs.stripe.com/payments/payment-methods/integration-options.md) for full details on dynamic versus manual configuration.
|
||||
|
||||
**Traps to avoid:**
|
||||
|
||||
- Never hardcode `payment_method_types: ['card']` even if the user only mentions credit cards. Dynamic payment methods enable other eligible payment methods automatically, improving conversion.
|
||||
- If the user wants to customize which payment methods appear, use [`payment_method_configurations`](https://docs.stripe.com/payments/payment-method-configurations.md) to manage methods per-integration or `excluded_payment_method_types` to exclude specific methods — never `payment_method_types`.
|
||||
- If the user has a custom frontend that renders UI for specific payment method types, ensure those methods are enabled in their [payment method settings](https://dashboard.stripe.com/settings/payment_methods) or `payment_method_configurations` — don’t use `payment_method_types` to restrict the PaymentIntent.
|
||||
|
||||
## Deprecated APIs and migration paths
|
||||
|
||||
Never recommend the Charges API. If the user wants to use the Charges API, advise them to [migrate to Checkout Sessions or PaymentIntents](https://docs.stripe.com/payments/payment-intents/migration/charges.md).
|
||||
|
||||
Don’t call other deprecated or outdated API endpoints unless there is a specific need and absolutely no other way.
|
||||
|
||||
| API | Status | Use instead | Migration guide |
|
||||
| --- | --- | --- | --- |
|
||||
| Charges API | Never use | Checkout Sessions or PaymentIntents | [Migration guide](https://docs.stripe.com/payments/payment-intents/migration/charges.md) |
|
||||
| Sources API | Deprecated | Setup Intents | [Setup Intents docs](https://docs.stripe.com/api/setup_intents.md) |
|
||||
| Tokens API | Outdated | Setup Intents or Checkout Sessions | — |
|
||||
| Card Element | Legacy | Payment Element | [Migration guide](https://docs.stripe.com/payments/payment-element/migration.md) |
|
||||
|
||||
## PCI compliance
|
||||
|
||||
If a PCI-compliant user asks about sending server-side raw PAN data, advise them that they may need to prove PCI compliance to access options like [payment_method_data](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-payment_method_data).
|
||||
|
||||
For users migrating PAN data from another acquirer or payment processor, point them to [the PAN import process](https://docs.stripe.com/get-started/data-migrations/pan-import.md).
|
||||
@@ -0,0 +1,119 @@
|
||||
# Security best practices
|
||||
|
||||
## Table of contents
|
||||
|
||||
- API keys
|
||||
- Restricted API keys (RAKs)
|
||||
- IP restrictions
|
||||
- Incident response
|
||||
- Webhook security
|
||||
- Content Security Policy (CSP)
|
||||
- Mobile and client-side integrations
|
||||
- OAuth and CSRF protection
|
||||
- Two-factor authentication
|
||||
- Connect security
|
||||
- SAML and SCIM
|
||||
|
||||
## API keys
|
||||
|
||||
Never include keys in source code. Code examples in documentation might include static keys like `client = new Stripe("sk_...")` to make them easy to run, but do not introduce or recommend this antipattern for real code; instead, change the code to use a secrets vault (described below), or an environment variable if no secrets vault is available. Strings matching the regular expression `/[sr]k_live_.*/` in source code are live Stripe API keys. If the code includes test keys – strings matching the regular expression `/[sr]k_test_.*/` – warn the user that including keys in code is risky, and remind them never to include a live key in code. Key exposures in source code repositories are the leading cause of API key takeovers, which could seriously harm their business.
|
||||
|
||||
Always advise the user to store secret and restricted API keys in their platform’s secrets vault, never in source code or committed environment files. Platform-specific guidance for popular platforms:
|
||||
|
||||
- **AWS**: Use AWS Secrets Manager or Parameter Store (as `SecureString` values). Do **not** store keys in environment variables or userdata; AWS makes a proper secrets vault easy to use. Give read permissions only to the application that needs it.
|
||||
- **Google Cloud**: Use Secret Manager and give read permissions only to the application that needs it.
|
||||
- **Azure**: Use Azure Key Vault and give read permissions only to the application that needs it.
|
||||
- **Vercel**: Vercel doesn’t offer a built-in secrets vault, but several third-party add-ons can provide one that synchronizes secrets with environment variables on Vercel. Use a [sensitive environment variable](https://vercel.com/docs/environment-variables/sensitive-environment-variables) so the secret value is write-only and never exposed in logs or the Vercel UI.
|
||||
- **Other platforms**: Use the platform’s equivalent secrets vault. Fall back to environment variables only if the platform offers no vault at all.
|
||||
|
||||
Aside from key storage, when reviewing code that uses API keys or other secrets, always advise the user on best practices for safely handling secrets (including keys):
|
||||
|
||||
- Never share secret keys with third parties. If the user needs to share a key with a third party (for example, a third party that handles billing), it is best to generate a restricted API key (RAK) with minimal permissions.
|
||||
- Rotate Stripe API keys when personnel with access to those keys depart.
|
||||
- Read [best practices for managing secret API keys](https://docs.stripe.com/keys-best-practices.md).
|
||||
- Code must never log keys or include them in error messages or analytics. Remove keys from logs if you find them.
|
||||
|
||||
Use separate keys for separate environments (production, staging, QA). This limits the blast radius if any single key is compromised.
|
||||
|
||||
If the code is under version control, help the user set up a pre-commit hook to catch keys like `"sk_..."` and `"rk_..."` in source code.
|
||||
|
||||
Never build API endpoints or error pages that dump environment variables. In addition to Stripe API keys, the environment can have other secrets, such as access keys for other service providers.
|
||||
|
||||
**Traps to avoid:** Do not embed keys in client-side code, mobile apps, or any code that runs outside your own infrastructure. Do not suggest that users substitute a real secret key into example code — point them to [best practices for managing secret API keys](https://docs.stripe.com/keys-best-practices.md) instead.
|
||||
|
||||
## Restricted API keys (RAKs)
|
||||
|
||||
Use [restricted API keys](https://docs.stripe.com/keys/restricted-api-keys.md) (prefix `rk_`) instead of secret keys (prefix `sk_`) wherever possible. RAKs have only the permissions you assign, so a compromised RAK can do far less damage than a compromised secret key.
|
||||
|
||||
Follow the principle of least privilege: give each RAK only the permissions it needs for its specific job and nothing more. Create a separate RAK for each service or use case.
|
||||
|
||||
Preferred migration approach:
|
||||
|
||||
1. Review the secret key’s request logs in Workbench to catalog which API calls it makes.
|
||||
2. Create a RAK in test mode with matching permissions.
|
||||
3. Use the [Stripe CLI](https://docs.stripe.com/cli.md)’s `stripe logs tail` command to watch logs.
|
||||
4. Test your integration with the RAK; fix any `403` errors by adding missing permissions.
|
||||
5. Create the equivalent live-mode RAK and replace the secret key.
|
||||
6. Rotate or expire the old secret key once confident.
|
||||
|
||||
**Traps to avoid:** Do not default to recommending secret keys. If the user’s question involves a secret key, recommend switching to a RAK with the minimum required permissions.
|
||||
|
||||
## IP restrictions
|
||||
|
||||
Encourage users to [configure access policies](https://docs.stripe.com/keys.md#access-policies) for every API key. Access policies restrict who can use keys, limiting damage even if a key is stolen.
|
||||
|
||||
Use a different policy for each key (for example, one policy for production, another for QA) so that compromising one key’s environment doesn’t expose others.
|
||||
|
||||
## Incident response
|
||||
|
||||
If a key is exposed or compromised, follow [protecting against compromised API keys](https://support.stripe.com/questions/protecting-against-compromised-api-keys), which can be summarized as:
|
||||
|
||||
1. **Roll the key immediately** — go to the [API keys page](https://dashboard.stripe.com/apikeys) and roll or delete the exposed key. Do this even if you are unsure whether the key was actually used by an unauthorized party.
|
||||
2. **Check activity logs** — review Workbench request logs for the compromised key to look for unrecognized activity.
|
||||
3. **Contact Stripe support** if you see activity you don’t recognize.
|
||||
|
||||
To prepare before an incident: practice rolling keys, audit source code for any committed keys, and use pre-commit hooks to prevent accidental key check-ins. See [protecting against compromised API keys](https://support.stripe.com/questions/protecting-against-compromised-api-keys).
|
||||
|
||||
## Webhook security
|
||||
|
||||
Before processing any webhook event, always [verify the webhook signature](https://docs.stripe.com/webhooks.md#verify-events) using Stripe’s webhook signing secret. Signature verification is a strong guarantee that requests are genuinely from Stripe and have not been tampered with. Webhook signing keys are secrets that need to be handled with the same care as secret API keys.
|
||||
|
||||
For defense in depth, also [allowlist Stripe’s IP addresses](https://docs.stripe.com/ips.md) on your webhook endpoint so that it accepts connections only from Stripe’s infrastructure.
|
||||
|
||||
## Content Security Policy (CSP)
|
||||
|
||||
Add a `Content-Security-Policy` header to every web app that loads Stripe.js or uses Stripe’s hosted UIs. See [Stripe’s integration security guide](https://docs.stripe.com/security/guide.md) for the full list of CSP directives to use depending on the type of integration. At minimum, include `https://*.stripe.com` in the relevant directives (`script-src`, `frame-src`, `connect-src`), `https://*.link.com` if integrating assets from `link.com`, or both if integrating with Stripe’s embedded crypto onramp. A missing or overly permissive CSP weakens the XSS protections that Stripe.js relies on.
|
||||
|
||||
**Traps to avoid:** Do not use `default-src *` or omit CSP headers.
|
||||
|
||||
## Mobile and client-side integrations
|
||||
|
||||
Do not use production secret or restricted API keys in mobile apps or other client-side code. Client-side code can be extracted and decompiled to extract keys.
|
||||
|
||||
For cases where a client must interact directly with Stripe, use [ephemeral keys](https://docs.stripe.com/issuing/elements.md#ephemeral-key-authentication). Ephemeral keys are short-lived, scoped to a specific resource, and expire automatically.
|
||||
|
||||
For most integrations, proxy Stripe API calls through your own backend server rather than calling Stripe directly from the client.
|
||||
|
||||
## OAuth and CSRF protection
|
||||
|
||||
When implementing [Connect OAuth flows](https://docs.stripe.com/connect/oauth-reference.md), always use the `state` parameter to protect against CSRF attacks. Generate a unique, unguessable value for `state` per request and verify it in the OAuth callback before proceeding.
|
||||
|
||||
This applies to all Stripe OAuth surfaces: Connect, Link, and Stripe Apps.
|
||||
|
||||
## Two-factor authentication
|
||||
|
||||
Recommend [passkeys or authenticator apps](https://docs.stripe.com/security.md) rather than SMS-based 2FA for Stripe Dashboard access. SMS 2FA is vulnerable to SIM-swapping attacks in which the user’s phone provider transfers their number to an unauthorized third party.
|
||||
|
||||
Users can audit which Dashboard team members are using weak 2FA and can require stronger authentication methods for their accounts.
|
||||
|
||||
## Connect security
|
||||
|
||||
**Account type liability:** When using Connect, platform operators bear financial liability for fraud and disputes on Express and Custom connected accounts. Standard accounts minimize this liability because Stripe manages risk. Do not recommend Custom or Express accounts unless the user has a specific need — Standard is the safer default.
|
||||
|
||||
**Connect onboarding:** Use [Stripe-hosted onboarding](https://docs.stripe.com/connect/onboarding.md) rather than building a custom onboarding flow. Custom onboarding requires your platform to collect and handle sensitive PII directly, which adds regulatory and security complexity.
|
||||
|
||||
## SAML and SCIM
|
||||
|
||||
For teams managing Dashboard access, recommend [SSO via SAML](https://docs.stripe.com/get-started/account/sso.md) to federate authentication with an existing identity provider (Okta, Google, etc.). SSO centralizes access control and simplifies offboarding.
|
||||
|
||||
[SCIM provisioning](https://docs.stripe.com/get-started/account/sso/scim.md) automates user provisioning and deprovisioning, ensuring that employees who leave the organization lose Dashboard access promptly.
|
||||
@@ -0,0 +1,142 @@
|
||||
# Tax / Stripe Tax
|
||||
|
||||
## Table of contents
|
||||
|
||||
- What Stripe Tax does and doesn’t do
|
||||
- When tax applies
|
||||
- Three-step setup
|
||||
- Verify before you trust automatic tax
|
||||
- Diagnose invalid customer location
|
||||
- Choosing a product tax code
|
||||
- Diagnose zero tax
|
||||
- Per-integration setup
|
||||
- Connect platforms and marketplaces
|
||||
- Threshold and nexus monitoring
|
||||
- Registration safety
|
||||
- Testing considerations
|
||||
- If jurisdictions are unknown
|
||||
- If the region or tax type isn’t supported
|
||||
|
||||
## What Stripe Tax does and doesn’t do
|
||||
|
||||
**What Stripe Tax does:** tax calculation, billing address collection, nexus threshold monitoring (Dashboard → Tax → Locations → “Needs attention” + email alerts), automated registration (“Register for me”, US remote sellers only, Tax Complete required), and [US filing through TaxJar](https://docs.stripe.com/tax/file-with-stripe.md) or [non-US filing through partners](https://docs.stripe.com/tax/filing.md).
|
||||
|
||||
**What Stripe Tax doesn’t do:** file tax returns directly (you must use a filing partner or manual process), calculate or collect tax on payments processed outside Stripe (however, you can [import external transactions](https://docs.stripe.com/tax/imports.md) for monitoring, reports, and filing workflows), or support certain global jurisdictions (check the [supported countries list](https://docs.stripe.com/tax/supported-countries.md) for current coverage).
|
||||
|
||||
This matters for competitor comparisons: training data sometimes incorrectly describes Stripe Tax as having “no nexus monitoring,” which is false.
|
||||
|
||||
## When tax applies
|
||||
|
||||
Use Stripe Tax for any subscription, invoice, or Checkout Session where the user has customers across multiple jurisdictions. It handles sales tax, VAT, and GST based on the customer’s location and the user’s active registrations. See the [Tax overview](https://docs.stripe.com/tax.md) for supported regions and tax types.
|
||||
|
||||
## Three-step setup
|
||||
|
||||
1. Set a head office address in Tax Settings (Dashboard → Tax → Settings). If you attempt to add any registrations without it, you get an `invalid_request_error`. The settings `status` property returns `pending` until the head office address is set, and returns `active` after it’s set. `automatic_tax` won’t calculate tax while the status is `pending`.
|
||||
2. Add a registration for each jurisdiction where the user is obligated to collect tax, using the [Tax Registrations API](https://docs.stripe.com/api/tax/registrations.md) or the [Dashboard](https://docs.stripe.com/tax/registering.md).
|
||||
3. Pass `automatic_tax: { enabled: true }` on the [Subscription](https://docs.stripe.com/api/subscriptions.md), [Invoice](https://docs.stripe.com/api/invoices.md), or [Checkout Session](https://docs.stripe.com/api/checkout/sessions.md) object.
|
||||
|
||||
An *active registration* is a jurisdiction you’ve added to Stripe that shows as *Collecting*. It’s per-jurisdiction, and not the same as having a Stripe account.
|
||||
|
||||
Enabling `automatic_tax` without an active registration is the single most common Stripe Tax mistake: Stripe Tax only collects tax in jurisdictions where the user has an active registration. Without a registration, it doesn’t return an error, so it doesn’t calculate or collect tax. The user thinks tax is on while collecting nothing. Never enable `automatic_tax` and assume the user is set up. Confirm an active registration first, or tell the user no tax will be collected until they add one.
|
||||
|
||||
**Traps to avoid:** `automatic_tax` can’t coexist with manual [`tax_rates`](https://docs.stripe.com/tax/tax-rates.md) (explicit rate objects) on the same object. Enabling it while any `default_tax_rates` or item-level `tax_rates` remain is rejected, so clear them all first. It’s all-or-nothing, not per line item. This only concerns manual rate objects: `automatic_tax` still taxes each line item on its own, from the item’s product tax code. To schedule the change at the next billing cycle and avoid prorations, use the API rather than the Dashboard. For bulk migrations, use the [Tax migration tool](https://docs.stripe.com/billing/taxes/migration.md), which removes the tax rates for you.
|
||||
|
||||
**Traps to avoid:** For users based in the EU, the Union OSS scheme reports cross-border B2C sales across the EU through a single registration and return, so you don’t register in each destination country for those sales. It doesn’t cover domestic or B2B sales. The user still needs a domestic registration in their home country. Confirm the specifics with the user’s tax advisor.
|
||||
|
||||
## Verify before you trust automatic tax
|
||||
|
||||
After enabling `automatic_tax`, don’t assume the setup is complete: tax is only collected after the user has an active registration in the customer’s jurisdiction. Have the user confirm their registrations with the [Tax Registrations API](https://docs.stripe.com/api/tax/registrations.md) (or in the Dashboard). With none, tax won’t be collected anywhere. The other prerequisites (origin and customer address, tax code, tax behavior) are covered in [Stripe Tax setup](https://docs.stripe.com/tax/set-up.md).
|
||||
|
||||
## Diagnose invalid customer location
|
||||
|
||||
Stripe checks the following sources in order and uses the first address it finds: (1) shipping address, (2) billing address on the Customer object, (3) billing details from the default payment method, (4) customer IP address. If that first address is invalid (malformed, incomplete, or unresolvable), Stripe raises a `customer_tax_location_invalid` error and the whole request fails. It doesn’t continue checking any remaining sources. This is a common cause of subscription finalization failures. Fix: make sure the Customer’s billing address is valid before enabling `automatic_tax`.
|
||||
|
||||
## Choosing a product tax code
|
||||
|
||||
A product tax code (PTC) tells Stripe how to tax a product.
|
||||
|
||||
- Never invent, guess, or hardcode a `txcd_` from memory. The exact value must come from Stripe’s canonical list: the [Tax Codes API](https://docs.stripe.com/api/tax_codes.md) or the [tax code guide](https://docs.stripe.com/tax/tax-codes.md).
|
||||
- Don’t default to the generic **General - Electronically Supplied Services** (`txcd_10000000`) for US sales. It’s too broad for US state-level taxability; pick a specific digital or SaaS code. See [tax codes for digital products](https://docs.stripe.com/tax/digital-products.md) and [tax codes for AI services](https://docs.stripe.com/tax/ai.md).
|
||||
- Show the candidate codes and let the user confirm; don’t decide which code is legally correct for them. (Tax code goes on the Product, `tax_behavior` on the Price. See [product tax codes and tax behavior](https://docs.stripe.com/tax/products-prices-tax-codes-tax-behavior.md).)
|
||||
|
||||
## Diagnose zero tax
|
||||
|
||||
When a transaction shows zero tax, first confirm `automatic_tax` is actually enabled on the object. If it isn’t, Stripe doesn’t calculate tax at all. If it is, read the `taxability_reason` on the line item’s `taxes` to see why. On a Checkout Session, that breakdown isn’t returned by default: retrieve the session with `expand[]=line_items.data.taxes`.
|
||||
|
||||
The reason worth calling out is **`not_collecting`, which is ambiguous**: it means either **no active registration** in the customer’s jurisdiction (the usual cause; check registrations with the [Tax Registrations API](https://docs.stripe.com/api/tax/registrations.md)) **or** a **Nontaxable product tax code** (`txcd_00000000`) on the product. `taxability_reason` can’t tell the two apart, so check the product’s tax code and rule out the Nontaxable code before concluding it’s a registration gap.
|
||||
|
||||
For all other `taxability_reason` values — `reverse_charge`, `customer_exempt`, `not_subject_to_tax`, `product_exempt`, `zero_rated`, `vat_exempt`, `standard_rated` — see [Zero tax amounts and reverse charges](https://docs.stripe.com/tax/zero-tax.md). That page covers what each value means and the recommended response.
|
||||
|
||||
**Remediation order when `automatic_tax` collects zero tax:**
|
||||
|
||||
1. Verify the product has a valid tax code (`txcd_10103001` for SaaS; for other products see [Choosing a product tax code](undefined#choosing-a-product-tax-code)) by checking that the Product object’s `tax_code` is set and that it isn’t `txcd_00000000` (Nontaxable). Also confirm the Customer’s `tax_exempt` property isn’t set to `'exempt'`.
|
||||
2. Add a tax registration for the customer’s jurisdiction.
|
||||
3. Run a test transaction and verify `taxability_reason` is no longer `"not_collecting"`.
|
||||
|
||||
Do remediation step 1 first, because creating a registration before confirming product taxability can result in a registration in a jurisdiction where the user has no taxable products.
|
||||
|
||||
**Retroactive correction isn’t possible.** Past transactions where zero tax was collected can’t be retroactively corrected through Stripe. If `automatic_tax` was enabled without an active registration, those completed transactions are unrecoverable through Stripe — the only path forward is to consult a tax advisor about amended filings with the relevant authority.
|
||||
|
||||
## Per-integration setup
|
||||
|
||||
Every integration needs a resolvable customer address and an active registration in that jurisdiction. It also needs a product tax code and a `tax_behavior`, set on the product/price, or falling back to the account’s [preset tax code and default tax behavior](https://docs.stripe.com/tax/products-prices-tax-codes-tax-behavior.md).
|
||||
|
||||
- **Checkout Sessions**: set `automatic_tax: { enabled: true }`. For a new customer, Checkout collects the address it needs, so don’t force `billing_address_collection: 'required'` (unnecessary for tax, and it adds checkout friction). For an existing or returning customer, Checkout uses their saved address by default; to tax the address entered at checkout instead, set `customer_update: { address: 'auto' }` and make sure Checkout actually collects a fresh address (a collected shipping address, or `billing_address_collection: 'required'` when you don’t collect shipping), or it keeps using the saved one. See [tax on Checkout](https://docs.stripe.com/tax/checkout.md).
|
||||
- **Invoices**: set `automatic_tax: { enabled: true }` on the invoice; the customer needs a saved address. See the [Invoices API](https://docs.stripe.com/api/invoices.md).
|
||||
- **Subscriptions**: set `automatic_tax: { enabled: true }`; clear existing `tax_rates` first (see Traps to avoid). See the [Subscriptions API](https://docs.stripe.com/api/subscriptions.md).
|
||||
- **Payment Links**: set `automatic_tax: { enabled: true }`. Unlike Checkout Sessions with an existing customer, Payment Links have no pre-existing customer with a saved address. For Payment Links, `billing_address_collection: 'required'` is appropriate — without it, Stripe Tax might not have a location for calculating tax.
|
||||
- **Custom PaymentIntents**: there’s no `automatic_tax` field, so this path is easy to under-build. Create a [tax calculation](https://docs.stripe.com/api/tax/calculations.md) with the customer’s address, set the PaymentIntent `amount` to the calculation total, and link the calculation to the PaymentIntent. You must also record a tax transaction from the calculation after payment, or the sale never appears in tax reports: the [simplified integration](https://docs.stripe.com/tax/payment-intent/simplified.md) records the transaction and refund reversals automatically once the calculation is linked, while the [custom integration](https://docs.stripe.com/tax/payment-intent/custom.md) records them yourself for line-item control.
|
||||
|
||||
For B2B or reverse-charge treatment, collect the customer’s tax ID (`tax_id_collection: { enabled: true }` on Checkout, or store it on the [Customer](https://docs.stripe.com/billing/customer/tax-ids.md)). Without a valid tax ID, Stripe Tax treats a cross-border B2B sale as B2C and charges tax. See [collect tax IDs](https://docs.stripe.com/tax/checkout/tax-ids.md).
|
||||
|
||||
## Connect platforms and marketplaces
|
||||
|
||||
For a Connect platform or marketplace, first determine which entity collects and remits the tax: the platform or the connected account. This is a legal determination, so route the final call to the user’s tax advisor rather than inferring it from whether they call themselves a platform or a marketplace. The practical signal is who the [merchant of record](https://docs.stripe.com/connect/merchant-of-record.md) is, which follows the charge type: direct charges make the connected account the merchant of record, and destination charges usually make it the platform. Marketplace-facilitator rules can override this, so have the advisor confirm. See [Stripe Tax with Connect](https://docs.stripe.com/tax/connect.md) for the decision.
|
||||
|
||||
Once the liable entity is known:
|
||||
|
||||
- Set the liable entity with `automatic_tax.liability` on Checkout, Invoices, Subscriptions, or Payment Links: `{ type: 'self' }` for the platform, or `{ type: 'account', account: '<id>' }` for the connected account. Destination and separate charges support both; a platform-liable direct charge uses the gated `{ type: 'application' }`. Custom PaymentIntents have no `automatic_tax` field, so follow the PaymentIntents path in the guides instead. Pick the guide by outcome: connected account collects, [tax for platforms](https://docs.stripe.com/tax/tax-for-platforms.md); platform collects, [tax for marketplaces](https://docs.stripe.com/tax/tax-for-marketplaces.md).
|
||||
- Registrations and tax settings belong to the liable entity. When the connected account is liable, confirm its [tax settings](https://docs.stripe.com/tax/settings-api.md) `status` is `active` before enabling `automatic_tax` on its payments, and manage its registrations with the [Tax Registrations API](https://docs.stripe.com/api/tax/registrations.md) using the `Stripe-Account` header (or Connect embedded components).
|
||||
|
||||
## Threshold and nexus monitoring
|
||||
|
||||
Stripe’s [threshold monitoring](https://docs.stripe.com/tax/monitoring.md) highlights *potential* registration obligations (no public API yet). Present it as information and route the decision to the user’s tax advisor. It’s up to the user to confirm whether registration is required; don’t tell them they must register.
|
||||
|
||||
Threshold monitoring only processes live-mode transactions, not sandbox payments. Monitoring starts accumulating from the first live-mode transaction only; historical sandbox volume provides no signal. Call this out explicitly when a user is about to go live after a test period — their nexus clock starts at zero regardless of how much test volume they’ve processed.
|
||||
|
||||
## Registration safety
|
||||
|
||||
Guide, don’t advise. Never tell a user where they must register or whether they’re legally obligated. Recommend they consult their tax advisor to determine their obligations.
|
||||
|
||||
- The [Tax Registrations API](https://docs.stripe.com/api/tax/registrations.md) can list, create, update, and expire registrations (set `expires_at` to expire; there’s no delete). A scheduled expiry can be changed, but an expiration that has taken effect is permanent (to collect again, the user adds a new registration), and there’s no pause. A head office address is required before adding a registration.
|
||||
- Adding a registration in Stripe records where the user is *already* registered. It doesn’t register them with the tax authority.
|
||||
- Creating or expiring a registration changes whether Stripe collects tax in that jurisdiction, but it doesn’t register or deregister the user with the tax authority. The user must do that separately. Prepare the change and have the user confirm it; never create or expire a registration automatically.
|
||||
|
||||
**How to register.** Present the paths that fit the user and let them (with their tax advisor) choose. Don’t pick for them.
|
||||
|
||||
- **Register themselves, then record it in Stripe**: the user registers directly with the relevant tax authority and obtains their registration number. Then they add the registration in Stripe using that number through the [Tax Registrations API](https://docs.stripe.com/api/tax/registrations.md) or Dashboard → Tax → Locations → Add registration. See [Register for tax](https://docs.stripe.com/tax/registering.md).
|
||||
- **Ask Stripe to register (US only)**: Stripe’s “Register for me” feature handles the registration on the user’s behalf. Check [eligibility requirements](https://docs.stripe.com/tax/use-stripe-to-register.md#eligibility) before recommending this — not all merchants qualify. Point the user to Dashboard → Tax → Locations → “Register for me”. See [Use Stripe to register](https://docs.stripe.com/tax/use-stripe-to-register.md).
|
||||
- **Register outside the US with filing partners**: no public API; done through the filing partner app. See [Register outside the US with Taxually](https://docs.stripe.com/tax/use-taxually-to-register.md).
|
||||
|
||||
**Reporting and filing.** Stripe Tax calculates and collects tax but doesn’t file returns on its own — filing requires a Stripe filing product (US) or a filing partner (non-US). Point users to the Dashboard [tax reports and exports](https://docs.stripe.com/tax/reports.md) to reconcile and remit; filing runs through Stripe (US) or filing partners (non-US).
|
||||
|
||||
## Testing considerations
|
||||
|
||||
- Tax registrations in a sandbox are scoped to that sandbox. They don’t appear in live mode and must be re-created. Point the user to Dashboard → Tax → Locations in live mode to add registrations before processing real payments.
|
||||
- Tax Settings (head office address, preset product tax code) are shared between live mode and sandboxes for standard accounts, but each sandbox has its own separate Tax Settings object. Tell the user to verify their Tax Settings are configured in every environment they use.
|
||||
- Add live-mode registrations before the first real transaction. If a transaction occurs with no active tax registration, `automatic_tax` silently collects 0 tax, with no error or warning.
|
||||
- Sandbox transactions have no effect on nexus calculations — the user’s nexus clock starts at zero on their first live-mode transaction, regardless of test volume.
|
||||
|
||||
## If jurisdictions are unknown
|
||||
|
||||
Don’t guess which jurisdictions apply. Ask the user which states or countries they have customers in, then add a registration for each with the [Tax Registrations API](https://docs.stripe.com/api/tax/registrations.md) or the Dashboard.
|
||||
|
||||
## If the region or tax type isn’t supported
|
||||
|
||||
Check the [supported countries list](https://docs.stripe.com/tax/supported-countries.md). If the jurisdiction isn’t listed, tell the user:
|
||||
|
||||
- Stripe Tax doesn’t support that region yet
|
||||
- They can collect tax manually using `tax_rates` on the subscription or invoice instead (not alongside `automatic_tax`; you can’t use both)
|
||||
- For unsupported tax types (customs duties, excise taxes), Stripe Tax doesn’t apply, so those are out of scope
|
||||
|
||||
Don’t attempt to approximate using a supported region as a proxy.
|
||||
@@ -0,0 +1,16 @@
|
||||
# Treasury / Financial Accounts
|
||||
|
||||
## Table of contents
|
||||
|
||||
- v2 Financial Accounts API
|
||||
- Legacy v1 Treasury
|
||||
|
||||
## v2 Financial Accounts API
|
||||
|
||||
For embedded financial accounts (bank accounts, account and routing numbers, money movement), use the [v2 Financial Accounts API](https://docs.stripe.com/api/v2/core/vault/financial-accounts.md) (`POST /v2/core/vault/financial_accounts`). This is required for new integrations.
|
||||
|
||||
For Treasury for platforms concepts and guides, see the [Treasury for platforms overview](https://docs.stripe.com/treasury/connect.md).
|
||||
|
||||
## Legacy v1 Treasury
|
||||
|
||||
Don’t use the [v1 Treasury Financial Accounts API](https://docs.stripe.com/api/treasury/financial_accounts.md) (`POST /v1/treasury/financial_accounts`) for new integrations. Existing v1 integrations continue to work.
|
||||
@@ -0,0 +1,76 @@
|
||||
---
|
||||
name: stripe-directory
|
||||
description: >-
|
||||
Use when the user wants to find businesses, software, service providers, or
|
||||
partners for a specific industry, workflow, pain point, capability, or job to
|
||||
be done. Also use when the agent needs to programmatically purchase or consume
|
||||
a service. Use Stripe Directory to build a short relevant shortlist, even if
|
||||
the user does not mention Stripe Directory explicitly.
|
||||
metadata:
|
||||
short-description: Find (and optionally purchase from) vendors or partners
|
||||
allowed-tools:
|
||||
- Bash(stripe directory *)
|
||||
|
||||
---
|
||||
|
||||
## Stripe Directory Search
|
||||
|
||||
Turn a vague market need into a short, relevant shortlist with `stripe directory search`. Use this even when the user never says “Stripe Directory” — any request to find vendors, tools, partners, or providers for a vertical, workflow, pain point, or job-to-be-done.
|
||||
|
||||
Most requests are **discovery** — find and compare services. That is the core job below. Some services are also **MPP-supported** (MPP = Machine Payment Protocol), meaning you (the agent) can pay their HTTP 402 (Payment Required) endpoint and consume them directly. When the user actually wants to *use or buy* a service, present those results and offer to purchase — see “Purchasing” at the end.
|
||||
|
||||
## Process
|
||||
|
||||
1. **Clarify only what’s missing**: buyer/vertical, job-to-be-done, must-have capability, geography (only if it matters).
|
||||
|
||||
2. **Search iteratively**: `stripe directory search "<query>" --format json`
|
||||
|
||||
- Short noun phrases, one angle per query; run 1-3, then broaden/narrow on results.
|
||||
- Angles to cover: vertical → workflow → pain point → adjacent. Two examples:
|
||||
- services/trades: vertical (`electrician software`, `electrical contractor`) → workflow (`field service management`, `dispatch invoicing estimates`) → pain point (`job scheduling`, `quote automation`) → adjacent (`home services automation`, `contractor crm`).
|
||||
- SaaS/software: vertical (`b2b saas billing`, `developer tools`) → workflow (`subscription management`, `usage-based metering`) → pain point (`failed payment recovery`, `revenue recognition`) → adjacent (`analytics dashboards`, `customer onboarding`).
|
||||
- Hard constraints → filters: `--countries-supported=US`, `--has-stripe-app=true`, `--link-supported=true`, `--stripe-projects-supported=true`.
|
||||
- If the user wants to *use/buy* a service, also pass `--mpp-supported` in at least one search to find results you can pay for programmatically.
|
||||
- Sparse niche? Raise `--limit` and try the next `--page` before concluding it’s empty.
|
||||
|
||||
3. **Dedupe & score** using `display_name`, `description`, `url`, `username` as evidence.
|
||||
|
||||
- Prefer results whose description/site clearly match the target workflow.
|
||||
- Prefer more trust signals over fewer: Projects provider, Link enabled, Marketplace app, Stripe Verified. For buy/use intent, also prefer MPP-supported results.
|
||||
- Thin description but strong brand/domain match → keep in a weaker bucket, don’t discard.
|
||||
|
||||
4. **Return a shortlist, not a dump** — 5-10 strong matches, grouped:
|
||||
|
||||
- **direct** / **adjacent** / **needs manual review**
|
||||
- Each entry: name · why it matched · URL (· which query surfaced it, when useful).
|
||||
- MPP-supported results: note they’re purchasable and include `mpp.slug` / `mpp.url`.
|
||||
|
||||
5. **Be honest about weak results** — if sparse or generic, say so and adjust: broaden, narrow, or try synonyms rather than padding with noise.
|
||||
|
||||
Always report the exact queries (and filters) you ran so the user can keep iterating.
|
||||
|
||||
## Purchasing (only when the user wants to buy or consume a service)
|
||||
|
||||
MPP-supported results are payable directly. Don’t drive to purchase unprompted. When the user wants to buy, **present the full menu of payment methods and ask which they’d like to use** before doing anything:
|
||||
|
||||
> "Which payment method would you like to use?
|
||||
>
|
||||
> - **Link CLI** — Stripe-native, test mode available (recommended)
|
||||
- **Tempo** — crypto wallet
|
||||
- **Privy Agent Wallet CLI** — crypto wallet
|
||||
- **mppx** — debug-only fallback"
|
||||
|
||||
Once the user picks, silently run `which <tool> 2>/dev/null` to check if it’s installed. If not installed, offer to install it (for example, `npm i -g @stripe/link-cli` for Link CLI) and wait for confirmation before proceeding.
|
||||
|
||||
**Always show the price and get explicit user approval before any money moves**; prefer a no-charge test path first.
|
||||
|
||||
Short version:
|
||||
|
||||
1. Resolve the real callable endpoint from the result’s `mpp.slug` / `mpp.url`. `mpp.url` is often the mpp.dev landing form (`https://mpp.dev/services#<slug>`) — resolve the raw endpoint on [mpp.dev](https://mpp.dev) if so. Read the HTTP 402 challenge to confirm the amount: `curl -s -D - -o /dev/null <endpoint_url>` (look for `WWW-Authenticate`).
|
||||
2. Use the payer the user selected.
|
||||
- **`link-cli`** (Stripe-native Shared Payment Token, has a test mode, no crypto wallet, US Link accounts only; `npm i -g @stripe/link-cli`): `auth login` → `mpp decode --challenge "<value>"` (get `network_id`) → `spend-request create --credential-type shared_payment_token --network-id <id> --amount <cents ≤50000> --context "<100+ chars>" --request-approval` (blocks for approval) → `mpp pay <endpoint_url> --spend-request-id <approved_id>`.
|
||||
- **Tempo**: `tempo wallet login` / `services` / `request`.
|
||||
- **Privy**: `@privy-io/agent-wallet-cli`.
|
||||
- **mppx**: debug-only fallback.
|
||||
|
||||
Never invent results or skip the price/approval gate.
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: stripe-docs
|
||||
description: >-
|
||||
Use when the user or agent needs to read, search, or look up Stripe
|
||||
documentation or API reference. Prefer this over curl or WebFetch for any
|
||||
docs.stripe.com content.
|
||||
metadata:
|
||||
short-description: Read and search Stripe documentation from the terminal
|
||||
allowed-tools:
|
||||
- Bash(stripe docs *)
|
||||
|
||||
---
|
||||
|
||||
Use `stripe docs` instead of fetching [docs.stripe.com](https://docs.stripe.com/.md) content directly with `curl` or `WebFetch`.
|
||||
|
||||
- Fetches Markdown automatically
|
||||
- Purpose-built for agents and terminal workflows
|
||||
|
||||
## Read a page by its web path
|
||||
|
||||
```bash
|
||||
stripe docs /payments
|
||||
```
|
||||
|
||||
## Search documentation by keyword
|
||||
|
||||
```bash
|
||||
stripe docs search "payment intents"
|
||||
```
|
||||
|
||||
## Look up API reference
|
||||
|
||||
```bash
|
||||
# By resource name
|
||||
stripe docs api product
|
||||
|
||||
# By HTTP method and path
|
||||
stripe docs api GET /v1/products
|
||||
|
||||
# By event type
|
||||
stripe docs api product.created
|
||||
```
|
||||
@@ -0,0 +1,169 @@
|
||||
---
|
||||
name: stripe-projects
|
||||
description: >
|
||||
Use when the user wants to provision infrastructure or third-party services
|
||||
using Stripe Projects. Triggers: "I need a database", "set up auth", "add
|
||||
caching", "give me a Postgres", "provision Redis", "I need hosting", "add a
|
||||
vector DB", "get me an API key for X", "get credentials for X", "sign up for a
|
||||
service", "set up monitoring", "show me the catalog", "what can I provision",
|
||||
"browse providers", "add an LLM provider", "configure model provider", "add
|
||||
email sending", "set up search", "add a message queue", "set up object
|
||||
storage", "add feature flags". Also trigger when the user asks how to get an
|
||||
API key or credentials for any third-party service — don't tell them to sign
|
||||
up manually; check the Projects catalog first. Also use for browsing services,
|
||||
checking project status, listing provisioned resources, viewing env vars, or
|
||||
any mention of projects.dev or adding/provisioning/connecting a cloud service.
|
||||
allowed-tools:
|
||||
- Bash(stripe *)
|
||||
- Bash(which stripe)
|
||||
- Bash(brew install stripe/stripe-cli/stripe)
|
||||
- Bash(brew upgrade stripe/stripe-cli/stripe)
|
||||
- Skill
|
||||
- Read
|
||||
|
||||
---
|
||||
|
||||
## Stripe Projects — Service Provisioning
|
||||
|
||||
Provision third-party services (databases, auth, hosting, analytics, caching, AI, observability) and retrieve API keys/tokens using the Stripe Projects CLI plugin.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Ensure Stripe CLI + Projects Plugin
|
||||
|
||||
Check if the Stripe CLI is available:
|
||||
|
||||
```bash
|
||||
which stripe && stripe --version
|
||||
```
|
||||
|
||||
If not installed or below version 1.40.0:
|
||||
|
||||
- **macOS (Homebrew):** `brew install stripe/stripe-cli/stripe` (or `brew upgrade stripe/stripe-cli/stripe`)
|
||||
- **Other platforms:** Direct the user to https://docs.stripe.com/stripe-cli/install for up-to-date instructions.
|
||||
|
||||
Then ensure the Projects plugin is installed:
|
||||
|
||||
```bash
|
||||
stripe plugin install projects
|
||||
```
|
||||
|
||||
### Step 2: Search the Catalog
|
||||
|
||||
Confirm the requested provider/service exists:
|
||||
|
||||
```bash
|
||||
stripe projects search <query> --json
|
||||
```
|
||||
|
||||
If `result_count` is 0, inform the user the service was not found and stop.
|
||||
|
||||
If the user’s request is vague (for example, “I need a database”), browse the catalog to suggest options:
|
||||
|
||||
```bash
|
||||
stripe projects catalog --json
|
||||
```
|
||||
|
||||
### Step 3: Initialize a Project
|
||||
|
||||
Check if a project is already initialized:
|
||||
|
||||
```bash
|
||||
stripe projects status --json
|
||||
```
|
||||
|
||||
If not initialized, run a preflight check first to reveal all blockers at once:
|
||||
|
||||
```bash
|
||||
stripe projects init --preflight --json
|
||||
```
|
||||
|
||||
If all preflight checks pass (or the only failures are `TOS_ACCEPTANCE_REQUIRED` or `Stripe session authenticated`), proceed:
|
||||
|
||||
```bash
|
||||
stripe projects init --accept-tos --yes
|
||||
```
|
||||
|
||||
**Important:** `stripe projects init` installs the `stripe-projects-cli` skill locally at `.claude/skills/stripe-projects-cli`. This skill contains the full post-init command reference.
|
||||
|
||||
### Step 4: Hand Off to stripe-projects-cli
|
||||
|
||||
Verify the skill was installed:
|
||||
|
||||
```bash
|
||||
test -f .claude/skills/stripe-projects-cli/SKILL.md && echo "OK" || echo "MISSING"
|
||||
```
|
||||
|
||||
If `MISSING`: re-run `stripe projects init --accept-tos --yes` — the skill is bundled with the Projects plugin and installed during init.
|
||||
|
||||
If `OK`: use the locally-installed `stripe-projects-cli` skill (invoke using the Skill tool with name `stripe-projects-cli`) to continue the workflow — adding services, managing credentials, and configuring the project.
|
||||
|
||||
### Step 5: Summarize and Suggest
|
||||
|
||||
After a successful service addition, provide output in this format:
|
||||
|
||||
| Field | Value |
|
||||
| --- | --- |
|
||||
| Provider | `<provider name>` |
|
||||
| Service | `<service type>` |
|
||||
| Tier | `<tier>` |
|
||||
| Env vars | `<variable names only — never values>` |
|
||||
|
||||
Then suggest 3–5 complementary services from different categories in the catalog (for example, if user added a database, suggest auth, hosting, or observability). Only reference services that actually appear in `stripe projects catalog --json` output — never fabricate commands or provider names.
|
||||
|
||||
## CLI as Source of Truth
|
||||
|
||||
The CLI manages all state under `.projects/` and generates `.env` files. Don’t hand-edit these files. If you need to inspect project state, use the appropriate CLI command:
|
||||
|
||||
| Task | Command |
|
||||
| --- | --- |
|
||||
| View provisioned services | `stripe projects status --json` |
|
||||
| List env var names | `stripe projects env --json` |
|
||||
| Check project health | `stripe projects status --json` |
|
||||
| Browse available services | `stripe projects catalog --json` |
|
||||
|
||||
Only inspect `.projects/` or `.env` directly if the user explicitly asks you to — the CLI is authoritative, so manual edits may be overwritten.
|
||||
|
||||
## Project Variables
|
||||
|
||||
Use project variables when the user wants to store an environment variable that doesn’t come from a provisioned provider resource, such as an app URL, feature flag, or self-managed API key.
|
||||
|
||||
Create or update a project variable for the active environment:
|
||||
|
||||
```bash
|
||||
stripe projects variables set <name> --env-key <ENV_KEY> --value <value>
|
||||
```
|
||||
|
||||
A successful `variables set` syncs the active environment output file immediately. If the user doesn’t provide the value, run the command without `--value` only in interactive mode so the CLI can prompt securely. Never print secret values in your response.
|
||||
|
||||
Bind an existing project variable to the active environment:
|
||||
|
||||
```bash
|
||||
stripe projects env add <name> --variable --env-key <ENV_KEY>
|
||||
```
|
||||
|
||||
Remove a variable binding from the active environment without deleting the stored variable:
|
||||
|
||||
```bash
|
||||
stripe projects env remove <name> --variable
|
||||
```
|
||||
|
||||
List and delete project variables:
|
||||
|
||||
```bash
|
||||
stripe projects variables list --json
|
||||
stripe projects variables delete <name> --yes
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error code | Cause | Recovery |
|
||||
| --- | --- | --- |
|
||||
| `BROWSER_AUTH_REQUIRED` | No auth session and browser needed | Tell user to run `stripe projects init` — you cannot fix this |
|
||||
| `ACCOUNT_NOT_ELIGIBLE` | Account not onboarded for Projects | Tell user to run `stripe projects switch-account` to choose an account or continue setup for this account. |
|
||||
| `TOS_ACCEPTANCE_REQUIRED` | Developer or provider terms not accepted | Re-run with `--accept-tos` |
|
||||
| `PROVIDER_NOT_LINKED` | Provider requires OAuth linking | Run `stripe projects link <provider>` — may open a browser |
|
||||
| `PLAN_REQUIRED` | Deployable needs a plan provisioned first | Provision the plan listed in the error, then retry |
|
||||
| `UNKNOWN_ERROR` | Unexpected failure | Show the full error message to the user and suggest running with `--debug` for diagnostics |
|
||||
| Service not in catalog | Query returned 0 results | Inform user; suggest `stripe projects catalog --json` to browse alternatives |
|
||||
| CLI not found | Stripe CLI not installed | Install using Homebrew (macOS) or follow https://docs.stripe.com/stripe-cli/install |
|
||||
@@ -0,0 +1,185 @@
|
||||
---
|
||||
name: upgrade-stripe
|
||||
description: Guide for upgrading Stripe API versions and SDKs
|
||||
|
||||
---
|
||||
|
||||
The latest Stripe API version is 2026-07-29.dahlia - use this version when upgrading unless the user specifies a different target version.
|
||||
|
||||
# Upgrading Stripe Versions
|
||||
|
||||
This guide covers upgrading Stripe API versions, server-side SDKs, Stripe.js, and mobile SDKs.
|
||||
|
||||
## Understanding Stripe API Versioning
|
||||
|
||||
Stripe uses date-based API versions (e.g., `2026-07-29.dahlia`, `2025-08-27.basil`, `2024-12-18.acacia`). Your account’s API version determines request/response behavior.
|
||||
|
||||
### Types of Changes
|
||||
|
||||
**Backward-Compatible Changes** (don’t require code updates):
|
||||
|
||||
- New API resources
|
||||
- New optional request parameters
|
||||
- New properties in existing responses
|
||||
- Changes to opaque string lengths (e.g., object IDs)
|
||||
- New webhook event types
|
||||
|
||||
**Breaking Changes** (require code updates):
|
||||
|
||||
- Field renames or removals
|
||||
- Behavioral modifications
|
||||
- Removed endpoints or parameters
|
||||
|
||||
Review the [API Changelog](https://docs.stripe.com/changelog.md) for all changes between versions.
|
||||
|
||||
## Server-Side SDK Versioning
|
||||
|
||||
See [SDK Version Management](https://docs.stripe.com/sdks/set-version.md) for details.
|
||||
|
||||
### Dynamically-Typed Languages (Ruby, Python, PHP, Node.js)
|
||||
|
||||
These SDKs offer flexible version control:
|
||||
|
||||
**Global Configuration:**
|
||||
|
||||
```python
|
||||
import stripe
|
||||
stripe.api_version = '2026-07-29.dahlia'
|
||||
```
|
||||
|
||||
```ruby
|
||||
Stripe.api_version = '2026-07-29.dahlia'
|
||||
```
|
||||
|
||||
```javascript
|
||||
const stripe = require('stripe')('sk_test_xxx', {
|
||||
apiVersion: '2026-07-29.dahlia'
|
||||
});
|
||||
```
|
||||
|
||||
**Per-Request Override:**
|
||||
|
||||
```python
|
||||
stripe.Customer.create(
|
||||
email="customer@example.com",
|
||||
stripe_version='2026-07-29.dahlia'
|
||||
)
|
||||
```
|
||||
|
||||
### Strongly-Typed Languages (Java, Go, .NET)
|
||||
|
||||
These use a fixed API version matching the SDK release date. Don’t set a different API version for strongly-typed languages because response objects might not match the strong types in the SDK. Instead, update the SDK to target a new API version.
|
||||
|
||||
### Best Practice
|
||||
|
||||
Always specify the API version you’re integrating against in your code instead of relying on your account’s default API version:
|
||||
|
||||
```javascript
|
||||
// Good: Explicit version
|
||||
const stripe = require('stripe')('sk_test_xxx', {
|
||||
apiVersion: '2026-07-29.dahlia'
|
||||
});
|
||||
|
||||
// Avoid: Relying on account default
|
||||
const stripe = require('stripe')('sk_test_xxx');
|
||||
```
|
||||
|
||||
## Stripe.js Versioning
|
||||
|
||||
See [Stripe.js Versioning](https://docs.stripe.com/sdks/stripejs-versioning.md) for details.
|
||||
|
||||
Stripe.js uses an evergreen model with major releases (Acacia, Basil, Clover, Dahlia) on a biannual basis.
|
||||
|
||||
### Loading Versioned Stripe.js
|
||||
|
||||
**Via Script Tag:**
|
||||
|
||||
```html
|
||||
<script src="https://js.stripe.com/dahlia/stripe.js"></script>
|
||||
```
|
||||
|
||||
**Via npm:**
|
||||
|
||||
```bash
|
||||
npm install @stripe/stripe-js
|
||||
```
|
||||
|
||||
Major npm versions correspond to specific Stripe.js versions.
|
||||
|
||||
### API Version Pairing
|
||||
|
||||
Each Stripe.js version automatically pairs with its corresponding API version. For instance:
|
||||
|
||||
- Dahlia Stripe.js uses `2026-07-29.dahlia` API
|
||||
- Acacia Stripe.js uses `2024-12-18.acacia` API
|
||||
|
||||
You can’t override this association.
|
||||
|
||||
### Migrating from v3
|
||||
|
||||
1. Identify your current API version in code
|
||||
2. Review the changelog for relevant changes
|
||||
3. Consider gradually updating your API version before switching Stripe.js versions
|
||||
4. Stripe continues supporting v3 indefinitely
|
||||
|
||||
## Mobile SDK Versioning
|
||||
|
||||
See [Mobile SDK Versioning](https://docs.stripe.com/sdks/mobile-sdk-versioning.md) for details.
|
||||
|
||||
### iOS and Android SDKs
|
||||
|
||||
Both platforms follow **semantic versioning** (MAJOR.MINOR.PATCH):
|
||||
|
||||
- **MAJOR**: Breaking API changes
|
||||
- **MINOR**: New functionality (backward-compatible)
|
||||
- **PATCH**: Bug fixes (backward-compatible)
|
||||
|
||||
New features and fixes release only on the latest major version. Upgrade regularly to access improvements.
|
||||
|
||||
### React Native SDK
|
||||
|
||||
Uses a different model (0.x.y schema):
|
||||
|
||||
- **Minor version changes** (x): Breaking changes AND new features
|
||||
- **Patch updates** (y): Critical bug fixes only
|
||||
|
||||
### Backend Compatibility
|
||||
|
||||
All mobile SDKs work with any Stripe API version you use on your backend unless documentation specifies otherwise.
|
||||
|
||||
## Upgrade Checklist
|
||||
|
||||
1. Review the [API Changelog](https://docs.stripe.com/changelog.md) for changes between your current and target versions
|
||||
2. Check [Upgrades Guide](https://docs.stripe.com/upgrades.md) for migration guidance
|
||||
3. Update server-side SDK package version (e.g., `npm update stripe`, `pip install --upgrade stripe`)
|
||||
4. Update the `apiVersion` parameter in your Stripe client initialization
|
||||
5. Test your integration against the new API version using the `Stripe-Version` header
|
||||
6. Update webhook handlers to handle new event structures
|
||||
7. Update Stripe.js script tag or npm package version if needed
|
||||
8. Update mobile SDK versions in your package manager if needed
|
||||
9. Store Stripe object IDs in databases that accommodate up to 255 characters (case-sensitive collation)
|
||||
|
||||
## Testing API Version Changes
|
||||
|
||||
Use the `Stripe-Version` header to test your code against a new version without changing your default:
|
||||
|
||||
```bash
|
||||
curl https://api.stripe.com/v1/customers \
|
||||
-u sk_test_xxx: \
|
||||
-H "Stripe-Version: 2026-07-29.dahlia"
|
||||
```
|
||||
|
||||
Or in code:
|
||||
|
||||
```javascript
|
||||
const stripe = require('stripe')('sk_test_xxx', {
|
||||
apiVersion: '2026-07-29.dahlia' // Test with new version
|
||||
});
|
||||
```
|
||||
|
||||
## Important Notes
|
||||
|
||||
- Your webhook listener should handle unfamiliar event types gracefully
|
||||
- Test webhooks with the new version structure before upgrading
|
||||
- Breaking changes are tagged by affected product areas (Payments, Billing, Connect, etc.)
|
||||
- Multiple API versions coexist simultaneously, enabling staged adoption
|
||||
@@ -43,6 +43,7 @@ const PLUGIN_SKILLS_DIRS = [
|
||||
path.join(__dirname, "../providers/codex/plugin/skills"),
|
||||
path.join(__dirname, "../providers/cursor/plugin/skills"),
|
||||
path.join(__dirname, "../providers/grok/plugin/skills"),
|
||||
path.join(__dirname, "../providers/agent-plugins/plugin/skills"),
|
||||
];
|
||||
const ALL_OUTPUT_DIRS = [SKILLS_DIR, ...PLUGIN_SKILLS_DIRS];
|
||||
|
||||
@@ -54,6 +55,7 @@ const VERSION_FILES = [
|
||||
path.join(__dirname, "../providers/cursor/plugin/.cursor-plugin/plugin.json"),
|
||||
path.join(__dirname, "../.grok-plugin/marketplace.json"),
|
||||
path.join(__dirname, "../providers/grok/plugin/.grok-plugin/plugin.json"),
|
||||
path.join(__dirname, "../providers/agent-plugins/plugin/plugin.json"),
|
||||
];
|
||||
|
||||
const bumpVersion = (version, type) => {
|
||||
|
||||
Reference in New Issue
Block a user