claudient 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +42 -0
- package/CONTEXT.md +58 -0
- package/README.md +165 -0
- package/agents/build-resolvers/de/python-resolver.md +64 -0
- package/agents/build-resolvers/de/typescript-resolver.md +65 -0
- package/agents/build-resolvers/es/python-resolver.md +64 -0
- package/agents/build-resolvers/es/typescript-resolver.md +65 -0
- package/agents/build-resolvers/fr/python-resolver.md +64 -0
- package/agents/build-resolvers/fr/typescript-resolver.md +65 -0
- package/agents/build-resolvers/nl/python-resolver.md +64 -0
- package/agents/build-resolvers/nl/typescript-resolver.md +65 -0
- package/agents/build-resolvers/python-resolver.md +62 -0
- package/agents/build-resolvers/typescript-resolver.md +63 -0
- package/agents/core/architect.md +64 -0
- package/agents/core/code-reviewer.md +78 -0
- package/agents/core/de/architect.md +66 -0
- package/agents/core/de/code-reviewer.md +80 -0
- package/agents/core/de/planner.md +63 -0
- package/agents/core/de/security-reviewer.md +93 -0
- package/agents/core/es/architect.md +66 -0
- package/agents/core/es/code-reviewer.md +80 -0
- package/agents/core/es/planner.md +63 -0
- package/agents/core/es/security-reviewer.md +93 -0
- package/agents/core/fr/architect.md +66 -0
- package/agents/core/fr/code-reviewer.md +80 -0
- package/agents/core/fr/planner.md +63 -0
- package/agents/core/fr/security-reviewer.md +93 -0
- package/agents/core/nl/architect.md +66 -0
- package/agents/core/nl/code-reviewer.md +80 -0
- package/agents/core/nl/planner.md +63 -0
- package/agents/core/nl/security-reviewer.md +93 -0
- package/agents/core/planner.md +61 -0
- package/agents/core/security-reviewer.md +91 -0
- package/guides/agent-orchestration.md +231 -0
- package/guides/de/agent-orchestration.md +174 -0
- package/guides/de/getting-started.md +164 -0
- package/guides/de/hooks-cookbook.md +160 -0
- package/guides/de/memory-management.md +153 -0
- package/guides/de/security.md +180 -0
- package/guides/de/skill-authoring.md +214 -0
- package/guides/de/token-optimization.md +156 -0
- package/guides/es/agent-orchestration.md +174 -0
- package/guides/es/getting-started.md +164 -0
- package/guides/es/hooks-cookbook.md +160 -0
- package/guides/es/memory-management.md +153 -0
- package/guides/es/security.md +180 -0
- package/guides/es/skill-authoring.md +214 -0
- package/guides/es/token-optimization.md +156 -0
- package/guides/fr/agent-orchestration.md +174 -0
- package/guides/fr/getting-started.md +164 -0
- package/guides/fr/hooks-cookbook.md +227 -0
- package/guides/fr/memory-management.md +169 -0
- package/guides/fr/security.md +180 -0
- package/guides/fr/skill-authoring.md +214 -0
- package/guides/fr/token-optimization.md +158 -0
- package/guides/getting-started.md +164 -0
- package/guides/hooks-cookbook.md +423 -0
- package/guides/memory-management.md +192 -0
- package/guides/nl/agent-orchestration.md +174 -0
- package/guides/nl/getting-started.md +164 -0
- package/guides/nl/hooks-cookbook.md +160 -0
- package/guides/nl/memory-management.md +153 -0
- package/guides/nl/security.md +180 -0
- package/guides/nl/skill-authoring.md +214 -0
- package/guides/nl/token-optimization.md +156 -0
- package/guides/security.md +229 -0
- package/guides/skill-authoring.md +226 -0
- package/guides/token-optimization.md +169 -0
- package/hooks/lifecycle/cost-tracker.md +49 -0
- package/hooks/lifecycle/cost-tracker.sh +59 -0
- package/hooks/lifecycle/pre-compact-save.md +56 -0
- package/hooks/lifecycle/pre-compact-save.sh +37 -0
- package/hooks/lifecycle/session-start.md +50 -0
- package/hooks/lifecycle/session-start.sh +47 -0
- package/hooks/post-tool-use/audit-log.md +53 -0
- package/hooks/post-tool-use/audit-log.sh +53 -0
- package/hooks/post-tool-use/prettier.md +53 -0
- package/hooks/post-tool-use/prettier.sh +49 -0
- package/hooks/pre-tool-use/block-dangerous.md +48 -0
- package/hooks/pre-tool-use/block-dangerous.sh +76 -0
- package/hooks/pre-tool-use/git-push-confirm.md +46 -0
- package/hooks/pre-tool-use/git-push-confirm.sh +36 -0
- package/mcp/configs/github.json +11 -0
- package/mcp/configs/postgres.json +11 -0
- package/mcp/de/recommended-servers.md +170 -0
- package/mcp/es/recommended-servers.md +170 -0
- package/mcp/fr/recommended-servers.md +170 -0
- package/mcp/nl/recommended-servers.md +170 -0
- package/mcp/recommended-servers.md +168 -0
- package/package.json +45 -0
- package/prompts/project-starters/de/fastapi-project.md +62 -0
- package/prompts/project-starters/de/nextjs-project.md +82 -0
- package/prompts/project-starters/es/fastapi-project.md +62 -0
- package/prompts/project-starters/es/nextjs-project.md +82 -0
- package/prompts/project-starters/fastapi-project.md +60 -0
- package/prompts/project-starters/fr/fastapi-project.md +62 -0
- package/prompts/project-starters/fr/nextjs-project.md +82 -0
- package/prompts/project-starters/nextjs-project.md +80 -0
- package/prompts/project-starters/nl/fastapi-project.md +62 -0
- package/prompts/project-starters/nl/nextjs-project.md +82 -0
- package/prompts/system-prompts/ai-product.md +80 -0
- package/prompts/system-prompts/data-pipeline.md +76 -0
- package/prompts/system-prompts/de/ai-product.md +82 -0
- package/prompts/system-prompts/de/data-pipeline.md +78 -0
- package/prompts/system-prompts/de/saas-backend.md +71 -0
- package/prompts/system-prompts/es/ai-product.md +82 -0
- package/prompts/system-prompts/es/data-pipeline.md +78 -0
- package/prompts/system-prompts/es/saas-backend.md +71 -0
- package/prompts/system-prompts/fr/ai-product.md +82 -0
- package/prompts/system-prompts/fr/data-pipeline.md +78 -0
- package/prompts/system-prompts/fr/saas-backend.md +71 -0
- package/prompts/system-prompts/nl/ai-product.md +82 -0
- package/prompts/system-prompts/nl/data-pipeline.md +78 -0
- package/prompts/system-prompts/nl/saas-backend.md +71 -0
- package/prompts/system-prompts/saas-backend.md +69 -0
- package/prompts/task-specific/changelog.md +81 -0
- package/prompts/task-specific/de/changelog.md +83 -0
- package/prompts/task-specific/de/debugging.md +78 -0
- package/prompts/task-specific/de/pr-description.md +69 -0
- package/prompts/task-specific/debugging.md +76 -0
- package/prompts/task-specific/es/changelog.md +83 -0
- package/prompts/task-specific/es/debugging.md +78 -0
- package/prompts/task-specific/es/pr-description.md +69 -0
- package/prompts/task-specific/fr/changelog.md +83 -0
- package/prompts/task-specific/fr/debugging.md +78 -0
- package/prompts/task-specific/fr/pr-description.md +69 -0
- package/prompts/task-specific/nl/changelog.md +83 -0
- package/prompts/task-specific/nl/debugging.md +78 -0
- package/prompts/task-specific/nl/pr-description.md +69 -0
- package/prompts/task-specific/pr-description.md +67 -0
- package/rules/common/coding-style.md +45 -0
- package/rules/common/de/coding-style.md +47 -0
- package/rules/common/de/git.md +48 -0
- package/rules/common/de/performance.md +40 -0
- package/rules/common/de/security.md +45 -0
- package/rules/common/de/testing.md +45 -0
- package/rules/common/es/coding-style.md +47 -0
- package/rules/common/es/git.md +48 -0
- package/rules/common/es/performance.md +40 -0
- package/rules/common/es/security.md +45 -0
- package/rules/common/es/testing.md +45 -0
- package/rules/common/fr/coding-style.md +47 -0
- package/rules/common/fr/git.md +48 -0
- package/rules/common/fr/performance.md +40 -0
- package/rules/common/fr/security.md +45 -0
- package/rules/common/fr/testing.md +45 -0
- package/rules/common/git.md +46 -0
- package/rules/common/nl/coding-style.md +47 -0
- package/rules/common/nl/git.md +48 -0
- package/rules/common/nl/performance.md +40 -0
- package/rules/common/nl/security.md +45 -0
- package/rules/common/nl/testing.md +45 -0
- package/rules/common/performance.md +38 -0
- package/rules/common/security.md +43 -0
- package/rules/common/testing.md +43 -0
- package/rules/language-specific/de/go.md +48 -0
- package/rules/language-specific/de/python.md +38 -0
- package/rules/language-specific/de/typescript.md +51 -0
- package/rules/language-specific/es/go.md +48 -0
- package/rules/language-specific/es/python.md +38 -0
- package/rules/language-specific/es/typescript.md +51 -0
- package/rules/language-specific/fr/go.md +48 -0
- package/rules/language-specific/fr/python.md +38 -0
- package/rules/language-specific/fr/typescript.md +51 -0
- package/rules/language-specific/go.md +46 -0
- package/rules/language-specific/nl/go.md +48 -0
- package/rules/language-specific/nl/python.md +38 -0
- package/rules/language-specific/nl/typescript.md +51 -0
- package/rules/language-specific/python.md +36 -0
- package/rules/language-specific/typescript.md +49 -0
- package/scripts/cli.js +161 -0
- package/scripts/link-skills.sh +35 -0
- package/scripts/list-skills.sh +34 -0
- package/skills/ai-engineering/agent-construction.md +285 -0
- package/skills/ai-engineering/claude-api.md +248 -0
- package/skills/ai-engineering/de/agent-construction.md +287 -0
- package/skills/ai-engineering/de/claude-api.md +250 -0
- package/skills/ai-engineering/es/agent-construction.md +287 -0
- package/skills/ai-engineering/es/claude-api.md +250 -0
- package/skills/ai-engineering/fr/agent-construction.md +287 -0
- package/skills/ai-engineering/fr/claude-api.md +250 -0
- package/skills/ai-engineering/nl/agent-construction.md +287 -0
- package/skills/ai-engineering/nl/claude-api.md +250 -0
- package/skills/backend/dotnet/csharp.md +304 -0
- package/skills/backend/dotnet/de/csharp.md +306 -0
- package/skills/backend/dotnet/es/csharp.md +306 -0
- package/skills/backend/dotnet/fr/csharp.md +306 -0
- package/skills/backend/dotnet/nl/csharp.md +306 -0
- package/skills/backend/go/de/go.md +307 -0
- package/skills/backend/go/es/go.md +307 -0
- package/skills/backend/go/fr/go.md +307 -0
- package/skills/backend/go/go.md +305 -0
- package/skills/backend/go/nl/go.md +307 -0
- package/skills/backend/nodejs/de/nestjs.md +274 -0
- package/skills/backend/nodejs/de/nextjs.md +222 -0
- package/skills/backend/nodejs/es/nestjs.md +274 -0
- package/skills/backend/nodejs/es/nextjs.md +222 -0
- package/skills/backend/nodejs/fr/nestjs.md +274 -0
- package/skills/backend/nodejs/fr/nextjs.md +222 -0
- package/skills/backend/nodejs/nestjs.md +272 -0
- package/skills/backend/nodejs/nextjs.md +220 -0
- package/skills/backend/nodejs/nl/nestjs.md +274 -0
- package/skills/backend/nodejs/nl/nextjs.md +222 -0
- package/skills/backend/python/de/django.md +285 -0
- package/skills/backend/python/de/fastapi.md +244 -0
- package/skills/backend/python/django.md +283 -0
- package/skills/backend/python/es/django.md +285 -0
- package/skills/backend/python/es/fastapi.md +244 -0
- package/skills/backend/python/fastapi.md +242 -0
- package/skills/backend/python/fr/django.md +285 -0
- package/skills/backend/python/fr/fastapi.md +244 -0
- package/skills/backend/python/nl/django.md +285 -0
- package/skills/backend/python/nl/fastapi.md +244 -0
- package/skills/data-ml/dbt-data-pipelines.md +155 -0
- package/skills/data-ml/de/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/de/pandas-polars.md +147 -0
- package/skills/data-ml/de/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/es/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/es/pandas-polars.md +147 -0
- package/skills/data-ml/es/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/fr/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/fr/pandas-polars.md +147 -0
- package/skills/data-ml/fr/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/nl/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/nl/pandas-polars.md +147 -0
- package/skills/data-ml/nl/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/pandas-polars.md +145 -0
- package/skills/data-ml/pytorch-tensorflow.md +169 -0
- package/skills/database/de/graphql.md +181 -0
- package/skills/database/es/graphql.md +181 -0
- package/skills/database/fr/graphql.md +181 -0
- package/skills/database/graphql.md +179 -0
- package/skills/database/nl/graphql.md +181 -0
- package/skills/devops-infra/de/docker.md +133 -0
- package/skills/devops-infra/de/github-actions.md +179 -0
- package/skills/devops-infra/de/kubernetes.md +129 -0
- package/skills/devops-infra/de/terraform.md +130 -0
- package/skills/devops-infra/docker.md +131 -0
- package/skills/devops-infra/es/docker.md +133 -0
- package/skills/devops-infra/es/github-actions.md +179 -0
- package/skills/devops-infra/es/kubernetes.md +129 -0
- package/skills/devops-infra/es/terraform.md +130 -0
- package/skills/devops-infra/fr/docker.md +133 -0
- package/skills/devops-infra/fr/github-actions.md +179 -0
- package/skills/devops-infra/fr/kubernetes.md +129 -0
- package/skills/devops-infra/fr/terraform.md +130 -0
- package/skills/devops-infra/github-actions.md +177 -0
- package/skills/devops-infra/kubernetes.md +127 -0
- package/skills/devops-infra/nl/docker.md +133 -0
- package/skills/devops-infra/nl/github-actions.md +179 -0
- package/skills/devops-infra/nl/kubernetes.md +129 -0
- package/skills/devops-infra/nl/terraform.md +130 -0
- package/skills/devops-infra/terraform.md +128 -0
- package/skills/finance-payments/de/stripe.md +187 -0
- package/skills/finance-payments/es/stripe.md +187 -0
- package/skills/finance-payments/fr/stripe.md +187 -0
- package/skills/finance-payments/nl/stripe.md +187 -0
- package/skills/finance-payments/stripe.md +185 -0
- package/workflows/code-review.md +151 -0
- package/workflows/de/code-review.md +153 -0
- package/workflows/de/debugging-session.md +146 -0
- package/workflows/de/feature-development.md +155 -0
- package/workflows/de/new-project-bootstrap.md +175 -0
- package/workflows/de/refactor-safely.md +150 -0
- package/workflows/debugging-session.md +144 -0
- package/workflows/es/code-review.md +153 -0
- package/workflows/es/debugging-session.md +146 -0
- package/workflows/es/feature-development.md +155 -0
- package/workflows/es/new-project-bootstrap.md +175 -0
- package/workflows/es/refactor-safely.md +150 -0
- package/workflows/feature-development.md +153 -0
- package/workflows/fr/code-review.md +153 -0
- package/workflows/fr/debugging-session.md +146 -0
- package/workflows/fr/feature-development.md +155 -0
- package/workflows/fr/new-project-bootstrap.md +175 -0
- package/workflows/fr/refactor-safely.md +150 -0
- package/workflows/new-project-bootstrap.md +173 -0
- package/workflows/nl/code-review.md +153 -0
- package/workflows/nl/debugging-session.md +146 -0
- package/workflows/nl/feature-development.md +155 -0
- package/workflows/nl/new-project-bootstrap.md +175 -0
- package/workflows/nl/refactor-safely.md +150 -0
- package/workflows/refactor-safely.md +148 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
> 🇩🇪 Dies ist die deutsche Übersetzung. [Englische Version](../stripe.md).
|
|
2
|
+
|
|
3
|
+
# Stripe Integration Skill
|
|
4
|
+
|
|
5
|
+
## Wann aktivieren
|
|
6
|
+
- Stripe Checkout oder Payment Intents für Einmalzahlungen implementieren
|
|
7
|
+
- Stripe-Abonnements und Abrechnungszyklen einrichten
|
|
8
|
+
- Stripe-Webhooks für Zahlungsereignisse verarbeiten
|
|
9
|
+
- Rückerstattungen, Streitigkeiten oder Zahlungsabbrüche implementieren
|
|
10
|
+
- Stripe Connect für Marktplatz-/Plattformzahlungen einrichten
|
|
11
|
+
- Fehlgeschlagene Zahlungen, abgelehnte Karten oder Webhook-Zustellprobleme debuggen
|
|
12
|
+
- SCA (Starke Kundenauthentifizierung) Compliance implementieren
|
|
13
|
+
|
|
14
|
+
## Wann NICHT verwenden
|
|
15
|
+
- PayPal, Braintree, Adyen, Mollie — andere Zahlungsanbieter mit anderen SDKs
|
|
16
|
+
- Krypto-Zahlungen
|
|
17
|
+
- Interne Buchhaltungs- oder Rechnungssysteme ohne Zahlungs-Gateway
|
|
18
|
+
- Banküberweisung / nur ACH-Flows (anderes Stripe-Produkt — Payment Elements gelten noch, aber Flow unterscheidet sich)
|
|
19
|
+
|
|
20
|
+
## Anweisungen
|
|
21
|
+
|
|
22
|
+
### Niemals Zahlungsdaten hardcoden oder protokollieren
|
|
23
|
+
```typescript
|
|
24
|
+
// NIEMALS Kartendetails, vollständige Payment-Intent-Objekte oder Kunden-PII protokollieren
|
|
25
|
+
// SCHLECHT:
|
|
26
|
+
console.log('Payment intent:', paymentIntent); // Kann sensible Daten enthalten
|
|
27
|
+
|
|
28
|
+
// GUT:
|
|
29
|
+
console.log('Payment intent created:', paymentIntent.id, paymentIntent.status);
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Payment Intents — serverseitige Erstellung
|
|
33
|
+
```typescript
|
|
34
|
+
import Stripe from 'stripe';
|
|
35
|
+
|
|
36
|
+
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
|
|
37
|
+
apiVersion: '2024-12-18.acacia', // API-Version immer pinnen
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Payment Intent serverseitig erstellen — niemals clientseitig
|
|
41
|
+
export async function createPaymentIntent(
|
|
42
|
+
amount: number, // Immer in kleinster Währungseinheit (Cents für USD)
|
|
43
|
+
currency: string,
|
|
44
|
+
customerId: string,
|
|
45
|
+
metadata: Record<string, string>
|
|
46
|
+
): Promise<{ clientSecret: string; paymentIntentId: string }> {
|
|
47
|
+
const paymentIntent = await stripe.paymentIntents.create({
|
|
48
|
+
amount, // z.B. 2999 = $29.99
|
|
49
|
+
currency, // z.B. 'usd', 'eur'
|
|
50
|
+
customer: customerId,
|
|
51
|
+
automatic_payment_methods: { enabled: true },
|
|
52
|
+
metadata, // Interne IDs hier speichern
|
|
53
|
+
idempotency_key: `pi_${customerId}_${Date.now()}`, // Doppelte Abbuchungen verhindern
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
clientSecret: paymentIntent.client_secret!,
|
|
58
|
+
paymentIntentId: paymentIntent.id,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Webhook-Verarbeitung — immer Signatur verifizieren
|
|
64
|
+
```typescript
|
|
65
|
+
import { headers } from 'next/headers';
|
|
66
|
+
|
|
67
|
+
export async function POST(request: Request) {
|
|
68
|
+
const body = await request.text(); // Roher Body — kein geparsten JSON
|
|
69
|
+
const signature = headers().get('stripe-signature')!;
|
|
70
|
+
|
|
71
|
+
let event: Stripe.Event;
|
|
72
|
+
try {
|
|
73
|
+
event = stripe.webhooks.constructEvent(
|
|
74
|
+
body,
|
|
75
|
+
signature,
|
|
76
|
+
process.env.STRIPE_WEBHOOK_SECRET!
|
|
77
|
+
);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
// Ungültige Signatur — sofort ablehnen
|
|
80
|
+
return new Response('Invalid signature', { status: 400 });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Idempotent verarbeiten — Webhooks können mehr als einmal zugestellt werden
|
|
84
|
+
switch (event.type) {
|
|
85
|
+
case 'payment_intent.succeeded':
|
|
86
|
+
await handlePaymentSucceeded(event.data.object as Stripe.PaymentIntent);
|
|
87
|
+
break;
|
|
88
|
+
case 'payment_intent.payment_failed':
|
|
89
|
+
await handlePaymentFailed(event.data.object as Stripe.PaymentIntent);
|
|
90
|
+
break;
|
|
91
|
+
case 'customer.subscription.deleted':
|
|
92
|
+
await handleSubscriptionCancelled(event.data.object as Stripe.Subscription);
|
|
93
|
+
break;
|
|
94
|
+
default:
|
|
95
|
+
// Protokollieren, aber kein Fehler bei unbekannten Ereignissen — Stripe fügt neue Ereignisse im Laufe der Zeit hinzu
|
|
96
|
+
console.log(`Unhandled event type: ${event.type}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return new Response(null, { status: 200 });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Immer prüfen, ob bereits verarbeitet, bevor DB-Änderungen vorgenommen werden
|
|
103
|
+
async function handlePaymentSucceeded(paymentIntent: Stripe.PaymentIntent) {
|
|
104
|
+
const orderId = paymentIntent.metadata.order_id;
|
|
105
|
+
|
|
106
|
+
// Idempotenzprüfung
|
|
107
|
+
const existing = await db.order.findUnique({ where: { id: orderId } });
|
|
108
|
+
if (existing?.status === 'paid') return; // Bereits verarbeitet
|
|
109
|
+
|
|
110
|
+
await db.order.update({
|
|
111
|
+
where: { id: orderId },
|
|
112
|
+
data: { status: 'paid', stripePaymentIntentId: paymentIntent.id }
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Abonnements
|
|
118
|
+
```typescript
|
|
119
|
+
// Abonnement erstellen
|
|
120
|
+
const subscription = await stripe.subscriptions.create({
|
|
121
|
+
customer: customerId,
|
|
122
|
+
items: [{ price: priceId }],
|
|
123
|
+
payment_behavior: 'default_incomplete', // Nicht aktivieren bis Zahlung bestätigt
|
|
124
|
+
expand: ['latest_invoice.payment_intent'],
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Wichtige Webhook-Ereignisse für Abonnements:
|
|
128
|
+
// customer.subscription.created → Zugang bereitstellen
|
|
129
|
+
// customer.subscription.updated → Planänderungen verarbeiten
|
|
130
|
+
// customer.subscription.deleted → Zugang widerrufen
|
|
131
|
+
// invoice.payment_succeeded → Zugangsperiode verlängern
|
|
132
|
+
// invoice.payment_failed → Mahnung senden, dann nach Nachfrist sperren
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Testmodus-Disziplin
|
|
136
|
+
```typescript
|
|
137
|
+
// Testkartennummern in der Entwicklung verwenden:
|
|
138
|
+
// Erfolg: 4242 4242 4242 4242
|
|
139
|
+
// Ablehnung: 4000 0000 0000 0002
|
|
140
|
+
// Auth erforderlich: 4000 0025 0000 3155
|
|
141
|
+
// Test-Webhook-CLI verwenden: stripe listen --forward-to localhost:3000/api/webhooks/stripe
|
|
142
|
+
|
|
143
|
+
// Niemals Testschlüssel in der Produktion, niemals Live-Schlüssel in der Entwicklung verwenden
|
|
144
|
+
const isLiveMode = process.env.STRIPE_SECRET_KEY!.startsWith('sk_live_');
|
|
145
|
+
if (isLiveMode && process.env.NODE_ENV !== 'production') {
|
|
146
|
+
throw new Error('Live Stripe keys must not be used outside production');
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Fehlerbehandlung
|
|
151
|
+
```typescript
|
|
152
|
+
try {
|
|
153
|
+
const charge = await stripe.charges.create({ ... });
|
|
154
|
+
} catch (err) {
|
|
155
|
+
if (err instanceof Stripe.errors.StripeCardError) {
|
|
156
|
+
// Karte abgelehnt — benutzerfreundliche Meldung anzeigen
|
|
157
|
+
return { error: err.message, code: err.code };
|
|
158
|
+
}
|
|
159
|
+
if (err instanceof Stripe.errors.StripeRateLimitError) {
|
|
160
|
+
// Mit exponentiellem Backoff wiederholen
|
|
161
|
+
throw err;
|
|
162
|
+
}
|
|
163
|
+
if (err instanceof Stripe.errors.StripeInvalidRequestError) {
|
|
164
|
+
// Fehlerhafter API-Aufruf — protokollieren und Code korrigieren
|
|
165
|
+
console.error('Invalid Stripe request:', err.message);
|
|
166
|
+
throw err;
|
|
167
|
+
}
|
|
168
|
+
// Andere Fehler: StripeAPIError, StripeConnectionError, StripeAuthenticationError
|
|
169
|
+
throw err;
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Beispiel
|
|
174
|
+
|
|
175
|
+
**Benutzer:** Einen Checkout-Flow für ein SaaS-Produkt implementieren: serverseitig einen Payment Intent erstellen, beim Erfolg den Webhook verarbeiten, um das Abonnement zu aktivieren, und fehlgeschlagene Zahlungen behandeln.
|
|
176
|
+
|
|
177
|
+
**Erwartete Ausgabe:**
|
|
178
|
+
- `POST /api/checkout` — erstellt PaymentIntent, gibt `clientSecret` zurück
|
|
179
|
+
- `POST /api/webhooks/stripe` — verifiziert Signatur, verarbeitet `payment_intent.succeeded` (idempotentes DB-Update), `payment_intent.payment_failed` (protokollieren + benachrichtigen)
|
|
180
|
+
- Metadaten auf PaymentIntent: `user_id`, `plan_id`, `order_id`
|
|
181
|
+
- Alle Beträge in Cents, Währung explizit
|
|
182
|
+
- API-Version gepinnt
|
|
183
|
+
- Keine Protokollierung sensibler Daten
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
> **Mit uns arbeiten:** Claudient wird von [Uitbreiden](https://uitbreiden.com/) unterstützt — wir bauen KI-Produkte und B2B-Lösungen mit Entwickler-Communities. Zahlungsflows aufbauen oder KI-Produkte monetarisieren? [uitbreiden.com](https://uitbreiden.com/)
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
> 🇪🇸 Esta es la traducción en español. [Versión en inglés](../stripe.md).
|
|
2
|
+
|
|
3
|
+
# Skill de Integración con Stripe
|
|
4
|
+
|
|
5
|
+
## Cuándo activar
|
|
6
|
+
- Implementar Stripe Checkout o Payment Intents para pagos únicos
|
|
7
|
+
- Configurar suscripciones y ciclos de facturación en Stripe
|
|
8
|
+
- Manejar webhooks de Stripe para eventos de pago
|
|
9
|
+
- Implementar reembolsos, disputas o cancelaciones de pagos
|
|
10
|
+
- Configurar Stripe Connect para pagos en marketplace/plataforma
|
|
11
|
+
- Depurar pagos fallidos, tarjetas rechazadas o problemas de entrega de webhooks
|
|
12
|
+
- Implementar cumplimiento de SCA (Strong Customer Authentication)
|
|
13
|
+
|
|
14
|
+
## Cuándo NO usar
|
|
15
|
+
- PayPal, Braintree, Adyen, Mollie — proveedores de pago diferentes con SDKs diferentes
|
|
16
|
+
- Pagos con criptomonedas
|
|
17
|
+
- Sistemas internos de contabilidad o facturación sin pasarela de pago
|
|
18
|
+
- Flujos solo de transferencia bancaria/ACH (producto diferente de Stripe — Payment Elements aún aplica, pero el flujo difiere)
|
|
19
|
+
|
|
20
|
+
## Instrucciones
|
|
21
|
+
|
|
22
|
+
### Nunca hardcodees ni registres datos de pago
|
|
23
|
+
```typescript
|
|
24
|
+
// NUNCA registres detalles de tarjeta, objetos completos de payment intent o PII del cliente
|
|
25
|
+
// MALO:
|
|
26
|
+
console.log('Payment intent:', paymentIntent); // Puede contener datos sensibles
|
|
27
|
+
|
|
28
|
+
// BUENO:
|
|
29
|
+
console.log('Payment intent created:', paymentIntent.id, paymentIntent.status);
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Payment Intents — creación en el servidor
|
|
33
|
+
```typescript
|
|
34
|
+
import Stripe from 'stripe';
|
|
35
|
+
|
|
36
|
+
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
|
|
37
|
+
apiVersion: '2024-12-18.acacia', // Siempre fija la versión de la API
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Crear payment intent en el servidor — nunca en el cliente
|
|
41
|
+
export async function createPaymentIntent(
|
|
42
|
+
amount: number, // Siempre en la unidad de moneda más pequeña (centavos para USD)
|
|
43
|
+
currency: string,
|
|
44
|
+
customerId: string,
|
|
45
|
+
metadata: Record<string, string>
|
|
46
|
+
): Promise<{ clientSecret: string; paymentIntentId: string }> {
|
|
47
|
+
const paymentIntent = await stripe.paymentIntents.create({
|
|
48
|
+
amount, // p.ej., 2999 = $29.99
|
|
49
|
+
currency, // p.ej., 'usd', 'eur'
|
|
50
|
+
customer: customerId,
|
|
51
|
+
automatic_payment_methods: { enabled: true },
|
|
52
|
+
metadata, // Almacena tus IDs internos aquí
|
|
53
|
+
idempotency_key: `pi_${customerId}_${Date.now()}`, // Prevenir cargos duplicados
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
clientSecret: paymentIntent.client_secret!,
|
|
58
|
+
paymentIntentId: paymentIntent.id,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Manejo de webhooks — siempre verifica la firma
|
|
64
|
+
```typescript
|
|
65
|
+
import { headers } from 'next/headers';
|
|
66
|
+
|
|
67
|
+
export async function POST(request: Request) {
|
|
68
|
+
const body = await request.text(); // Cuerpo sin procesar — no JSON parseado
|
|
69
|
+
const signature = headers().get('stripe-signature')!;
|
|
70
|
+
|
|
71
|
+
let event: Stripe.Event;
|
|
72
|
+
try {
|
|
73
|
+
event = stripe.webhooks.constructEvent(
|
|
74
|
+
body,
|
|
75
|
+
signature,
|
|
76
|
+
process.env.STRIPE_WEBHOOK_SECRET!
|
|
77
|
+
);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
// Firma inválida — rechazar inmediatamente
|
|
80
|
+
return new Response('Invalid signature', { status: 400 });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Procesar idempotentemente — los webhooks pueden entregarse más de una vez
|
|
84
|
+
switch (event.type) {
|
|
85
|
+
case 'payment_intent.succeeded':
|
|
86
|
+
await handlePaymentSucceeded(event.data.object as Stripe.PaymentIntent);
|
|
87
|
+
break;
|
|
88
|
+
case 'payment_intent.payment_failed':
|
|
89
|
+
await handlePaymentFailed(event.data.object as Stripe.PaymentIntent);
|
|
90
|
+
break;
|
|
91
|
+
case 'customer.subscription.deleted':
|
|
92
|
+
await handleSubscriptionCancelled(event.data.object as Stripe.Subscription);
|
|
93
|
+
break;
|
|
94
|
+
default:
|
|
95
|
+
// Registrar pero no dar error en eventos desconocidos — Stripe agrega nuevos eventos con el tiempo
|
|
96
|
+
console.log(`Unhandled event type: ${event.type}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return new Response(null, { status: 200 });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Siempre verifica si ya fue procesado antes de hacer cambios en la BD
|
|
103
|
+
async function handlePaymentSucceeded(paymentIntent: Stripe.PaymentIntent) {
|
|
104
|
+
const orderId = paymentIntent.metadata.order_id;
|
|
105
|
+
|
|
106
|
+
// Verificación de idempotencia
|
|
107
|
+
const existing = await db.order.findUnique({ where: { id: orderId } });
|
|
108
|
+
if (existing?.status === 'paid') return; // Ya procesado
|
|
109
|
+
|
|
110
|
+
await db.order.update({
|
|
111
|
+
where: { id: orderId },
|
|
112
|
+
data: { status: 'paid', stripePaymentIntentId: paymentIntent.id }
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Suscripciones
|
|
118
|
+
```typescript
|
|
119
|
+
// Crear suscripción
|
|
120
|
+
const subscription = await stripe.subscriptions.create({
|
|
121
|
+
customer: customerId,
|
|
122
|
+
items: [{ price: priceId }],
|
|
123
|
+
payment_behavior: 'default_incomplete', // No activar hasta que se confirme el pago
|
|
124
|
+
expand: ['latest_invoice.payment_intent'],
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Eventos webhook clave para suscripciones:
|
|
128
|
+
// customer.subscription.created → aprovisionar acceso
|
|
129
|
+
// customer.subscription.updated → manejar cambios de plan
|
|
130
|
+
// customer.subscription.deleted → revocar acceso
|
|
131
|
+
// invoice.payment_succeeded → extender período de acceso
|
|
132
|
+
// invoice.payment_failed → enviar correo de dunning, luego suspender después del período de gracia
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Disciplina en modo de prueba
|
|
136
|
+
```typescript
|
|
137
|
+
// Usar números de tarjeta de prueba en desarrollo:
|
|
138
|
+
// Éxito: 4242 4242 4242 4242
|
|
139
|
+
// Rechazada: 4000 0000 0000 0002
|
|
140
|
+
// Requiere autenticación: 4000 0025 0000 3155
|
|
141
|
+
// Usar webhook CLI de prueba: stripe listen --forward-to localhost:3000/api/webhooks/stripe
|
|
142
|
+
|
|
143
|
+
// Nunca usar claves de prueba en producción, nunca usar claves en vivo en desarrollo
|
|
144
|
+
const isLiveMode = process.env.STRIPE_SECRET_KEY!.startsWith('sk_live_');
|
|
145
|
+
if (isLiveMode && process.env.NODE_ENV !== 'production') {
|
|
146
|
+
throw new Error('Live Stripe keys must not be used outside production');
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Manejo de errores
|
|
151
|
+
```typescript
|
|
152
|
+
try {
|
|
153
|
+
const charge = await stripe.charges.create({ ... });
|
|
154
|
+
} catch (err) {
|
|
155
|
+
if (err instanceof Stripe.errors.StripeCardError) {
|
|
156
|
+
// Tarjeta rechazada — mostrar mensaje amigable al usuario
|
|
157
|
+
return { error: err.message, code: err.code };
|
|
158
|
+
}
|
|
159
|
+
if (err instanceof Stripe.errors.StripeRateLimitError) {
|
|
160
|
+
// Reintentar con backoff exponencial
|
|
161
|
+
throw err;
|
|
162
|
+
}
|
|
163
|
+
if (err instanceof Stripe.errors.StripeInvalidRequestError) {
|
|
164
|
+
// Llamada a la API incorrecta — registrar y corregir el código
|
|
165
|
+
console.error('Invalid Stripe request:', err.message);
|
|
166
|
+
throw err;
|
|
167
|
+
}
|
|
168
|
+
// Otros errores: StripeAPIError, StripeConnectionError, StripeAuthenticationError
|
|
169
|
+
throw err;
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Ejemplo
|
|
174
|
+
|
|
175
|
+
**Usuario:** Implementar un flujo de checkout para un producto SaaS: crear un payment intent en el servidor, manejar el webhook al tener éxito para activar la suscripción y manejar pagos fallidos.
|
|
176
|
+
|
|
177
|
+
**Salida esperada:**
|
|
178
|
+
- `POST /api/checkout` — crea PaymentIntent, devuelve `clientSecret`
|
|
179
|
+
- `POST /api/webhooks/stripe` — verifica firma, maneja `payment_intent.succeeded` (actualización idempotente en BD), `payment_intent.payment_failed` (registrar + notificar)
|
|
180
|
+
- Metadata en PaymentIntent: `user_id`, `plan_id`, `order_id`
|
|
181
|
+
- Todos los importes en centavos, moneda explícita
|
|
182
|
+
- Versión de la API fijada
|
|
183
|
+
- Sin registro de datos sensibles
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
> **Trabaja con nosotros:** Claudient está respaldado por [Uitbreiden](https://uitbreiden.com/) — construimos productos de IA y soluciones B2B con comunidades de desarrolladores. ¿Construyendo flujos de pago o monetizando productos de IA? [uitbreiden.com](https://uitbreiden.com/)
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
> 🇫🇷 This is the French translation. [English version](../stripe.md).
|
|
2
|
+
|
|
3
|
+
# Compétence Intégration Stripe
|
|
4
|
+
|
|
5
|
+
## Quand activer
|
|
6
|
+
- Implémenter Stripe Checkout ou Payment Intents pour les paiements uniques
|
|
7
|
+
- Configurer les abonnements Stripe et les cycles de facturation
|
|
8
|
+
- Gérer les webhooks Stripe pour les événements de paiement
|
|
9
|
+
- Implémenter des remboursements, litiges ou annulations de paiement
|
|
10
|
+
- Configurer Stripe Connect pour les paiements marketplace/plateforme
|
|
11
|
+
- Déboguer des paiements échoués, des cartes refusées ou des problèmes de livraison de webhooks
|
|
12
|
+
- Implémenter la conformité SCA (Strong Customer Authentication)
|
|
13
|
+
|
|
14
|
+
## Quand NE PAS utiliser
|
|
15
|
+
- PayPal, Braintree, Adyen, Mollie — fournisseurs de paiement différents avec des SDKs différents
|
|
16
|
+
- Paiements crypto
|
|
17
|
+
- Systèmes comptables internes ou de facturation sans passerelle de paiement
|
|
18
|
+
- Flux uniquement par virement bancaire / ACH (produit Stripe différent — Payment Elements s'applique toujours, mais le flux diffère)
|
|
19
|
+
|
|
20
|
+
## Instructions
|
|
21
|
+
|
|
22
|
+
### Ne jamais coder en dur ou logger des données de paiement
|
|
23
|
+
```typescript
|
|
24
|
+
// NE JAMAIS logger les détails de carte, les objets payment intent complets, ou les PII clients
|
|
25
|
+
// MAUVAIS :
|
|
26
|
+
console.log('Payment intent:', paymentIntent); // Peut contenir des données sensibles
|
|
27
|
+
|
|
28
|
+
// BON :
|
|
29
|
+
console.log('Payment intent created:', paymentIntent.id, paymentIntent.status);
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Payment Intents — création côté serveur
|
|
33
|
+
```typescript
|
|
34
|
+
import Stripe from 'stripe';
|
|
35
|
+
|
|
36
|
+
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
|
|
37
|
+
apiVersion: '2024-12-18.acacia', // Toujours épingler la version de l'API
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Créer le payment intent côté serveur — jamais côté client
|
|
41
|
+
export async function createPaymentIntent(
|
|
42
|
+
amount: number, // Toujours dans la plus petite unité monétaire (centimes pour USD)
|
|
43
|
+
currency: string,
|
|
44
|
+
customerId: string,
|
|
45
|
+
metadata: Record<string, string>
|
|
46
|
+
): Promise<{ clientSecret: string; paymentIntentId: string }> {
|
|
47
|
+
const paymentIntent = await stripe.paymentIntents.create({
|
|
48
|
+
amount, // ex: 2999 = 29,99€
|
|
49
|
+
currency, // ex: 'usd', 'eur'
|
|
50
|
+
customer: customerId,
|
|
51
|
+
automatic_payment_methods: { enabled: true },
|
|
52
|
+
metadata, // Stocker vos IDs internes ici
|
|
53
|
+
idempotency_key: `pi_${customerId}_${Date.now()}`, // Prévenir les charges en doublon
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
clientSecret: paymentIntent.client_secret!,
|
|
58
|
+
paymentIntentId: paymentIntent.id,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Gestion des webhooks — toujours vérifier la signature
|
|
64
|
+
```typescript
|
|
65
|
+
import { headers } from 'next/headers';
|
|
66
|
+
|
|
67
|
+
export async function POST(request: Request) {
|
|
68
|
+
const body = await request.text(); // Corps brut — pas de JSON parsé
|
|
69
|
+
const signature = headers().get('stripe-signature')!;
|
|
70
|
+
|
|
71
|
+
let event: Stripe.Event;
|
|
72
|
+
try {
|
|
73
|
+
event = stripe.webhooks.constructEvent(
|
|
74
|
+
body,
|
|
75
|
+
signature,
|
|
76
|
+
process.env.STRIPE_WEBHOOK_SECRET!
|
|
77
|
+
);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
// Signature invalide — rejeter immédiatement
|
|
80
|
+
return new Response('Invalid signature', { status: 400 });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Traiter de manière idempotente — les webhooks peuvent être livrés plus d'une fois
|
|
84
|
+
switch (event.type) {
|
|
85
|
+
case 'payment_intent.succeeded':
|
|
86
|
+
await handlePaymentSucceeded(event.data.object as Stripe.PaymentIntent);
|
|
87
|
+
break;
|
|
88
|
+
case 'payment_intent.payment_failed':
|
|
89
|
+
await handlePaymentFailed(event.data.object as Stripe.PaymentIntent);
|
|
90
|
+
break;
|
|
91
|
+
case 'customer.subscription.deleted':
|
|
92
|
+
await handleSubscriptionCancelled(event.data.object as Stripe.Subscription);
|
|
93
|
+
break;
|
|
94
|
+
default:
|
|
95
|
+
// Logger mais ne pas lever d'erreur sur les événements inconnus — Stripe en ajoute de nouveaux au fil du temps
|
|
96
|
+
console.log(`Unhandled event type: ${event.type}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return new Response(null, { status: 200 });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Toujours vérifier si déjà traité avant de modifier la DB
|
|
103
|
+
async function handlePaymentSucceeded(paymentIntent: Stripe.PaymentIntent) {
|
|
104
|
+
const orderId = paymentIntent.metadata.order_id;
|
|
105
|
+
|
|
106
|
+
// Vérification d'idempotence
|
|
107
|
+
const existing = await db.order.findUnique({ where: { id: orderId } });
|
|
108
|
+
if (existing?.status === 'paid') return; // Déjà traité
|
|
109
|
+
|
|
110
|
+
await db.order.update({
|
|
111
|
+
where: { id: orderId },
|
|
112
|
+
data: { status: 'paid', stripePaymentIntentId: paymentIntent.id }
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Abonnements
|
|
118
|
+
```typescript
|
|
119
|
+
// Créer un abonnement
|
|
120
|
+
const subscription = await stripe.subscriptions.create({
|
|
121
|
+
customer: customerId,
|
|
122
|
+
items: [{ price: priceId }],
|
|
123
|
+
payment_behavior: 'default_incomplete', // Ne pas activer avant confirmation du paiement
|
|
124
|
+
expand: ['latest_invoice.payment_intent'],
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Événements webhook clés pour les abonnements :
|
|
128
|
+
// customer.subscription.created → provisionner l'accès
|
|
129
|
+
// customer.subscription.updated → gérer les changements de plan
|
|
130
|
+
// customer.subscription.deleted → révoquer l'accès
|
|
131
|
+
// invoice.payment_succeeded → étendre la période d'accès
|
|
132
|
+
// invoice.payment_failed → envoyer un email de relance, puis suspendre après la période de grâce
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Discipline du mode test
|
|
136
|
+
```typescript
|
|
137
|
+
// Utiliser des numéros de carte de test en développement :
|
|
138
|
+
// Succès : 4242 4242 4242 4242
|
|
139
|
+
// Refus : 4000 0000 0000 0002
|
|
140
|
+
// Auth requise : 4000 0025 0000 3155
|
|
141
|
+
// Utiliser la CLI webhook de test : stripe listen --forward-to localhost:3000/api/webhooks/stripe
|
|
142
|
+
|
|
143
|
+
// Ne jamais utiliser des clés de test en production, ne jamais utiliser des clés live en développement
|
|
144
|
+
const isLiveMode = process.env.STRIPE_SECRET_KEY!.startsWith('sk_live_');
|
|
145
|
+
if (isLiveMode && process.env.NODE_ENV !== 'production') {
|
|
146
|
+
throw new Error('Live Stripe keys must not be used outside production');
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Gestion des erreurs
|
|
151
|
+
```typescript
|
|
152
|
+
try {
|
|
153
|
+
const charge = await stripe.charges.create({ ... });
|
|
154
|
+
} catch (err) {
|
|
155
|
+
if (err instanceof Stripe.errors.StripeCardError) {
|
|
156
|
+
// Carte refusée — afficher un message convivial
|
|
157
|
+
return { error: err.message, code: err.code };
|
|
158
|
+
}
|
|
159
|
+
if (err instanceof Stripe.errors.StripeRateLimitError) {
|
|
160
|
+
// Réessayer avec backoff exponentiel
|
|
161
|
+
throw err;
|
|
162
|
+
}
|
|
163
|
+
if (err instanceof Stripe.errors.StripeInvalidRequestError) {
|
|
164
|
+
// Mauvais appel API — logger et corriger le code
|
|
165
|
+
console.error('Invalid Stripe request:', err.message);
|
|
166
|
+
throw err;
|
|
167
|
+
}
|
|
168
|
+
// Autres erreurs : StripeAPIError, StripeConnectionError, StripeAuthenticationError
|
|
169
|
+
throw err;
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Exemple
|
|
174
|
+
|
|
175
|
+
**Utilisateur :** Implémenter un flux de checkout pour un produit SaaS : créer un payment intent côté serveur, gérer le webhook au succès pour activer l'abonnement et gérer les paiements échoués.
|
|
176
|
+
|
|
177
|
+
**Sortie attendue :**
|
|
178
|
+
- `POST /api/checkout` — crée le PaymentIntent, retourne `clientSecret`
|
|
179
|
+
- `POST /api/webhooks/stripe` — vérifie la signature, gère `payment_intent.succeeded` (mise à jour DB idempotente), `payment_intent.payment_failed` (log + notification)
|
|
180
|
+
- Métadonnées sur le PaymentIntent : `user_id`, `plan_id`, `order_id`
|
|
181
|
+
- Tous les montants en centimes, devise explicite
|
|
182
|
+
- Version de l'API épinglée
|
|
183
|
+
- Pas de logging de données sensibles
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
> **Travaillez avec nous :** Claudient est soutenu par [Uitbreiden](https://uitbreiden.com/) — nous construisons des produits IA et des solutions B2B avec des communautés de développeurs. Vous construisez des flux de paiement ou monétisez des produits IA ? [uitbreiden.com](https://uitbreiden.com/)
|