vibes-plug 2.11.0 → 3.9.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.
Files changed (181) hide show
  1. package/.claude/rules/vibes-plug-core.md +5 -0
  2. package/.cursor/rules/vibes-plug-core.mdc +8 -3
  3. package/.cursorrules +9 -3
  4. package/AGENTS.md +25 -4
  5. package/CHANGELOG.md +151 -0
  6. package/CLAUDE.md +15 -8
  7. package/README.md +216 -641
  8. package/bin/vibes.mjs +1104 -0
  9. package/index.js +1 -1
  10. package/package.json +11 -3
  11. package/plugin.json +4 -3
  12. package/scripts/check-anti-slop.js +53 -0
  13. package/scripts/check-anti-slop.mjs +53 -0
  14. package/scripts/generate_swarm_gif.py +2 -2
  15. package/scripts/install.js +3 -1
  16. package/scripts/update_skills.js +1 -1
  17. package/scripts/update_skills.mjs +86 -0
  18. package/scripts/validate-skills.mjs +111 -0
  19. package/skills/accessibility-testing-expert/SKILL.md +117 -116
  20. package/skills/affective-computing-emotion-ai/SKILL.md +83 -0
  21. package/skills/agentic-coding-workflow-expert/SKILL.md +297 -0
  22. package/skills/agentic-memory-architect/SKILL.md +52 -0
  23. package/skills/agentic-micro-economy-architect/SKILL.md +92 -0
  24. package/skills/ai-llm-integration-expert/SKILL.md +330 -187
  25. package/skills/ai-media-generation-expert/SKILL.md +173 -172
  26. package/skills/ai-prompt-engineering-expert/SKILL.md +170 -50
  27. package/skills/ai-safety-governance-expert/SKILL.md +223 -0
  28. package/skills/angular-expert/SKILL.md +149 -148
  29. package/skills/anti-slop/SKILL.md +134 -0
  30. package/skills/api-design-expert/SKILL.md +4 -3
  31. package/skills/api-gateway-proxy-expert/SKILL.md +3 -2
  32. package/skills/app-analyzer-optimizer/SKILL.md +4 -3
  33. package/skills/apple-ecosystem-expert/SKILL.md +6 -5
  34. package/skills/astro-framework-expert/SKILL.md +201 -200
  35. package/skills/async-queue-temporal-expert/SKILL.md +218 -240
  36. package/skills/authentication-identity-expert/SKILL.md +79 -184
  37. package/skills/autonomous-red-teamer/SKILL.md +338 -203
  38. package/skills/autonomous-tdd-debugger/SKILL.md +6 -5
  39. package/skills/biome-linter-formatter-expert/SKILL.md +90 -89
  40. package/skills/blockchain-web3-expert/SKILL.md +116 -115
  41. package/skills/brainstorming/SKILL.md +392 -377
  42. package/skills/browser-automation-expert/SKILL.md +260 -222
  43. package/skills/bun-runtime-expert/SKILL.md +5 -4
  44. package/skills/chatbot-messaging-expert/SKILL.md +115 -114
  45. package/skills/ci-cd-devops-architect/SKILL.md +3 -2
  46. package/skills/cloud-hosting-expert/SKILL.md +5 -4
  47. package/skills/coderabbit/SKILL.md +5 -4
  48. package/skills/compliance-gdpr-privacy-expert/SKILL.md +3 -2
  49. package/skills/composable-mach-architect/SKILL.md +338 -0
  50. package/skills/cron-scheduler-expert/SKILL.md +5 -4
  51. package/skills/data-pipeline-etl-expert/SKILL.md +3 -2
  52. package/skills/data-telemetry-expert/SKILL.md +5 -4
  53. package/skills/data-visualization-expert/SKILL.md +155 -154
  54. package/skills/database-orm-expert/SKILL.md +102 -240
  55. package/skills/deep-research-analyst/SKILL.md +182 -0
  56. package/skills/dependency-upgrade-migrator/SKILL.md +11 -10
  57. package/skills/design-system-architect/SKILL.md +34 -3
  58. package/skills/desktop-electron-expert/SKILL.md +129 -128
  59. package/skills/documentation-site-expert/SKILL.md +60 -59
  60. package/skills/doku-mcp-server/SKILL.md +5 -4
  61. package/skills/doku-payment-gateway/SKILL.md +250 -232
  62. package/skills/domain-driven-design-expert/SKILL.md +3 -2
  63. package/skills/e2e-testing-expert/SKILL.md +5 -4
  64. package/skills/ecommerce-expert/SKILL.md +88 -87
  65. package/skills/email-notification-expert/SKILL.md +35 -7
  66. package/skills/ephemeral-generative-ui-architect/SKILL.md +88 -0
  67. package/skills/error-resilience-expert/SKILL.md +26 -4
  68. package/skills/event-driven-architect/SKILL.md +5 -4
  69. package/skills/feature-flag-analytics-expert/SKILL.md +3 -2
  70. package/skills/file-upload-media-expert/SKILL.md +5 -4
  71. package/skills/firebase-security-expert/SKILL.md +5 -4
  72. package/skills/form-validation-expert/SKILL.md +7 -6
  73. package/skills/frontier-ai-models-expert/SKILL.md +116 -0
  74. package/skills/fullstack-expert/SKILL.md +68 -144
  75. package/skills/gemini-agent-booster/SKILL.md +248 -172
  76. package/skills/geospatial-maps-expert/SKILL.md +81 -80
  77. package/skills/global-a11y-i18n-expert/SKILL.md +5 -4
  78. package/skills/glsl-shader-expert/SKILL.md +155 -71
  79. package/skills/go-programming-expert/SKILL.md +5 -4
  80. package/skills/graph-rag-knowledge-expert/SKILL.md +201 -159
  81. package/skills/graphql-apollo-expert/SKILL.md +5 -4
  82. package/skills/headless-cms-expert/SKILL.md +182 -181
  83. package/skills/hig/SKILL.md +5 -4
  84. package/skills/js-backend-expert/SKILL.md +219 -218
  85. package/skills/legacy-code-translator/SKILL.md +6 -5
  86. package/skills/llm-finops-router/SKILL.md +52 -0
  87. package/skills/local-slm-edge-ai-expert/SKILL.md +168 -167
  88. package/skills/logging-error-tracking-expert/SKILL.md +5 -4
  89. package/skills/mcp-server-architect/SKILL.md +316 -294
  90. package/skills/micro-frontend-architect/SKILL.md +5 -4
  91. package/skills/mobile-expo-expert/SKILL.md +5 -4
  92. package/skills/modern-css-native-expert/SKILL.md +190 -189
  93. package/skills/monorepo-architect/SKILL.md +5 -4
  94. package/skills/mpa-orchestrator/SKILL.md +41 -4
  95. package/skills/multi-agent-orchestration/SKILL.md +388 -254
  96. package/skills/mvc-expert/SKILL.md +5 -4
  97. package/skills/n8n-automation-expert/SKILL.md +90 -89
  98. package/skills/nextjs-app-router-expert/SKILL.md +3 -2
  99. package/skills/openapi-swagger-codegen-expert/SKILL.md +4 -3
  100. package/skills/payment-gateway-expert/SKILL.md +131 -128
  101. package/skills/pdf-document-generation-expert/SKILL.md +92 -91
  102. package/skills/performance-web-vitals/SKILL.md +5 -4
  103. package/skills/post-quantum-crypto-migrator/SKILL.md +3 -2
  104. package/skills/prd-architect/SKILL.md +85 -109
  105. package/skills/proactive-background-watcher/SKILL.md +5 -4
  106. package/skills/production-ready-hardener/SKILL.md +25 -27
  107. package/skills/pwa-offline-first-expert/SKILL.md +227 -185
  108. package/skills/pydantic-ai-expert/SKILL.md +162 -0
  109. package/skills/python-programming-expert/SKILL.md +5 -4
  110. package/skills/rate-limit-abuse-prevention/SKILL.md +5 -4
  111. package/skills/realtime-collaboration-expert/SKILL.md +3 -2
  112. package/skills/rich-text-editor-expert/SKILL.md +178 -177
  113. package/skills/rust-programming-expert/SKILL.md +5 -4
  114. package/skills/saas-architect/SKILL.md +155 -0
  115. package/skills/saas-billing/SKILL.md +394 -382
  116. package/skills/saas-multi-tenant/SKILL.md +7 -6
  117. package/skills/scalability-clean-code/SKILL.md +5 -4
  118. package/skills/search-engine-expert/SKILL.md +90 -89
  119. package/skills/self-healing-cloud-orchestrator/SKILL.md +3 -2
  120. package/skills/senior-frontend/SKILL.md +21 -18
  121. package/skills/senior-frontend/scripts/frontend_scaffolder.py +1 -1
  122. package/skills/seo/SKILL.md +4 -4
  123. package/skills/session-memory-manager/SKILL.md +129 -0
  124. package/skills/solidjs-expert/SKILL.md +81 -80
  125. package/skills/spa-orchestrator/SKILL.md +5 -4
  126. package/skills/sse-websocket-streaming-expert/SKILL.md +3 -2
  127. package/skills/state-management-expert/SKILL.md +5 -4
  128. package/skills/supabase-security-expert/SKILL.md +5 -4
  129. package/skills/svelte-sveltekit-expert/SKILL.md +92 -91
  130. package/skills/svg-animation-motion-expert/SKILL.md +3 -2
  131. package/skills/synthetic-data-finetuning-expert/SKILL.md +156 -0
  132. package/skills/tailwind-expert/SKILL.md +62 -5
  133. package/skills/tanstack-query-expert/SKILL.md +5 -4
  134. package/skills/tauri-expert/SKILL.md +5 -4
  135. package/skills/typescript-expert/SKILL.md +5 -4
  136. package/skills/ui-ux-pro-max/SKILL.md +7 -4
  137. package/skills/vector-db-rag-expert/SKILL.md +209 -208
  138. package/skills/vercel-ai-sdk-expert/SKILL.md +226 -0
  139. package/skills/voice-ai-realtime-agent/SKILL.md +243 -202
  140. package/skills/vue-frontend-expert/SKILL.md +5 -4
  141. package/skills/wasm-edge-computing-expert/SKILL.md +3 -2
  142. package/skills/web-3d-graphics-expert/SKILL.md +259 -82
  143. package/skills/web-game-engine-expert/SKILL.md +278 -50
  144. package/skills/web-scraper/SKILL.md +158 -157
  145. package/skills/website-design-cloner/SKILL.md +5 -4
  146. package/skills/webxr-ar-vr-expert/SKILL.md +105 -65
  147. package/skills/wordpress-headless-expert/SKILL.md +145 -144
  148. package/skills/zero-tech-debt-auditor/SKILL.md +115 -0
  149. package/skills/zero-to-prod-orchestrator/SKILL.md +281 -227
  150. package/skills/zero-trust-secret-vault/SKILL.md +3 -2
  151. package/BLUEPRINT.md +0 -309
  152. package/skills/ai-cost-token-optimizer/SKILL.md +0 -82
  153. package/skills/ai-evals-benchmark-expert/SKILL.md +0 -188
  154. package/skills/asisten-ramah/SKILL.md +0 -47
  155. package/skills/auto-doc-updater/SKILL.md +0 -220
  156. package/skills/autonomous-chaos-monkey/SKILL.md +0 -63
  157. package/skills/background-jobs-queue-expert/SKILL.md +0 -235
  158. package/skills/bootstrap-to-modern/SKILL.md +0 -94
  159. package/skills/database-migration-versioning-expert/SKILL.md +0 -90
  160. package/skills/edge-serverless-db-expert/SKILL.md +0 -99
  161. package/skills/mcp-client-orchestrator/SKILL.md +0 -76
  162. package/skills/mobile-push-notification-expert/SKILL.md +0 -71
  163. package/skills/monday-design-aesthetic/SKILL.md +0 -73
  164. package/skills/multiple-entry-points/SKILL.md +0 -91
  165. package/skills/project-context-mapper/SKILL.md +0 -85
  166. package/skills/saas-mvp-launcher/SKILL.md +0 -260
  167. package/skills/saas-transformer/SKILL.md +0 -500
  168. package/skills/saas-transformer/references/billing_integration_guide.md +0 -401
  169. package/skills/secure-fuzz-testing/SKILL.md +0 -207
  170. package/skills/self-evolving-memory-graph/SKILL.md +0 -91
  171. package/skills/session-context-loader/SKILL.md +0 -83
  172. package/skills/session-handoff-resume/SKILL.md +0 -164
  173. package/skills/skill-baru/SKILL.md +0 -178
  174. package/skills/supabase-migration/SKILL.md +0 -91
  175. package/skills/token-saver/SKILL.md +0 -119
  176. package/skills/ui-components-expert/SKILL.md +0 -166
  177. package/skills/vibe-code-gardener/SKILL.md +0 -181
  178. package/skills/visual-qa-vision-agent/SKILL.md +0 -71
  179. /package/skills/{saas-transformer → saas-architect}/references/feature_gating_patterns.md +0 -0
  180. /package/skills/{saas-transformer → saas-architect}/references/saas_transformation_checklist.md +0 -0
  181. /package/skills/{saas-transformer → saas-architect}/scripts/saas_transformation_scanner.py +0 -0
@@ -1,383 +1,395 @@
1
- ---
2
- name: saas-billing
3
- description: "Implement and audit SaaS billing systems, subscription state machines, secure webhooks, and local database synchronization / Implementasi dan audit sistem billing SaaS, state machine langganan, webhook aman, dan sinkronisasi database lokal."
1
+ ---
2
+ name: saas-billing
3
+ description: "Implement and audit SaaS billing systems, subscription state machines, secure webhooks, and local database synchronization / Implementasi dan audit sistem billing SaaS, state machine langganan, webhook aman, dan sinkronisasi database lokal."
4
4
  author: "Roedy Rustam"
5
- ---
6
-
7
- # SaaS Billing Expert (2026 Edition)
8
-
9
- [English](#english) | [Bahasa Indonesia](#bahasa-indonesia)
10
-
11
- ---
12
-
13
- <a name="english"></a>
14
- ## English
15
-
16
- ### Orchestration & Integration
17
- Connects and orchestrates with relevant domain skills like `brainstorming`, `zero-to-prod-orchestrator`, and `project-context-mapper` to ensure cohesive execution.
18
-
19
- ### Description
20
- Expert guide for implementing and auditing SaaS billing systems. Covers subscription state machines, secure webhook handling, database synchronization, and the 2026 billing landscape including Stripe, **Polar.sh** (open-source, developer-first), **LemonSqueezy**, PayPal, and Midtrans (for Southeast Asia).
21
-
22
- ### Trigger Conditions
23
- - Integrating any payment gateway (Stripe, Polar.sh, LemonSqueezy, Midtrans, PayPal) into a SaaS application.
24
- - Using a Static-to-Dynamic QRIS alternative (with unique nominals and mutation webhooks) for local developers without PG accounts.
25
- - Implementing subscription state machines (active → past_due → canceled → reactivated).
26
- - Building secure webhook handlers with signature verification and idempotency.
27
- - Syncing external subscription status to a local database.
28
- - Implementing usage-based billing or metered API pricing.
29
- - Building the customer billing portal (manage subscription, download invoices).
30
- - Auditing an existing billing system for security gaps.
31
-
32
- ### 2026 Billing Provider Landscape
33
-
34
- | Provider | Best For | Open Source | Merchant of Record |
35
- |---|---|---|---|
36
- | **Stripe** | Enterprise, global, complex billing | ❌ | ❌ |
37
- | **Polar.sh** | Developer-first, open-source products | ✅ | ✅ (optional) |
38
- | **LemonSqueezy** | Indie hackers, simple pricing, global | ❌ | ✅ |
39
- | **Paddle** | B2B SaaS, EU VAT compliance | ❌ | ✅ |
40
- | **Midtrans** | Southeast Asia / Indonesia | ❌ | ❌ |
41
- | **PayPal** | Global, consumer trust | ❌ | ❌ |
42
-
43
- > **Merchant of Record (MoR)**: The provider handles tax compliance (VAT, GST), chargebacks, and legal liability — ideal for small teams without a finance department.
44
-
45
- ### Polar.sh — Developer-First Billing (2026 Rising Star)
46
- Polar.sh is the modern, open-source alternative to Gumroad/LemonSqueezy, purpose-built for developers and open-source projects:
47
- ```typescript
48
- import { Polar } from "@polar-sh/sdk";
49
-
50
- const polar = new Polar({ accessToken: process.env.POLAR_ACCESS_TOKEN });
51
-
52
- // Create a checkout session
53
- const checkout = await polar.checkouts.custom.create({
54
- productId: "prod_xxxx",
55
- successUrl: "https://myapp.com/success?checkout={CHECKOUT_ID}",
56
- customerEmail: user.email,
57
- metadata: { userId: user.id },
58
- });
59
-
60
- // Redirect to checkout
61
- return redirect(checkout.url);
62
- ```
63
-
64
- ```typescript
65
- // Webhook handler (Next.js App Router)
66
- import { validateEvent, WebhookVerificationError } from "@polar-sh/sdk/webhooks";
67
-
68
- export async function POST(req: Request) {
69
- const body = await req.text();
70
- const signature = req.headers.get("webhook-signature") ?? "";
71
-
72
- try {
73
- const event = validateEvent(body, req.headers, process.env.POLAR_WEBHOOK_SECRET!);
74
-
75
- switch (event.type) {
76
- case "subscription.created":
77
- case "subscription.updated":
78
- await syncSubscription(event.data);
79
- break;
80
- case "subscription.canceled":
81
- await cancelSubscription(event.data.id);
82
- break;
83
- }
84
- return new Response(null, { status: 200 });
85
- } catch (e) {
86
- if (e instanceof WebhookVerificationError) {
87
- return new Response("Invalid signature", { status: 403 });
88
- }
89
- throw e;
90
- }
91
- }
92
- ```
93
-
94
- ### Stripe — Production Patterns
95
-
96
- #### Subscription State Machine
97
- ```
98
- FREE ──subscribe──> TRIALING ──trial_ends──> ACTIVE
99
- │
100
- ┌───────────────┤
101
- │ │
102
- payment fails cancel
103
- │ │
104
- PAST_DUE CANCELED
105
- │
106
- 3 failed retries
107
- │
108
- CANCELED
109
- ```
110
-
111
- #### Idempotent Webhook Handler
112
- ```typescript
113
- // app/api/webhooks/stripe/route.ts
114
- import Stripe from 'stripe';
115
- import { db } from '@/lib/db';
116
-
117
- const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
118
-
119
- export async function POST(req: Request) {
120
- const body = await req.text();
121
- const sig = req.headers.get('stripe-signature')!;
122
-
123
- let event: Stripe.Event;
124
- try {
125
- event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET!);
126
- } catch {
127
- return new Response('Invalid signature', { status: 400 });
128
- }
129
-
130
- // Idempotency: skip already-processed events
131
- const processed = await db.webhookEvent.findUnique({ where: { stripeEventId: event.id } });
132
- if (processed) return new Response(null, { status: 200 });
133
-
134
- // Process event
135
- switch (event.type) {
136
- case 'customer.subscription.created':
137
- case 'customer.subscription.updated': {
138
- const sub = event.data.object as Stripe.Subscription;
139
- await db.subscription.upsert({
140
- where: { stripeSubId: sub.id },
141
- create: { stripeSubId: sub.id, status: sub.status, userId: sub.metadata.userId },
142
- update: { status: sub.status, currentPeriodEnd: new Date(sub.current_period_end * 1000) },
143
- });
144
- break;
145
- }
146
- case 'invoice.payment_failed': {
147
- const invoice = event.data.object as Stripe.Invoice;
148
- await sendDunningEmail(invoice.customer_email!);
149
- break;
150
- }
151
- }
152
-
153
- // Mark as processed
154
- await db.webhookEvent.create({ data: { stripeEventId: event.id } });
155
- return new Response(null, { status: 200 });
156
- }
157
- ```
158
-
159
- #### Usage-Based Billing (Metered)
160
- ```typescript
161
- // Report usage at end of billing period
162
- await stripe.subscriptionItems.createUsageRecord(subscriptionItemId, {
163
- quantity: apiCallsThisMonth,
164
- timestamp: Math.floor(Date.now() / 1000),
165
- action: 'set', // 'set' or 'increment'
166
- });
167
- ```
168
-
169
- ### PayPal — Checkout Integration
170
-
171
- PayPal remains a trusted global standard for one-off payments and subscriptions.
172
-
173
- #### Create Order (Server-Side)
174
- ```typescript
175
- // app/api/paypal/create-order/route.ts
176
- import { paypalClient } from '@/lib/paypal';
177
- import paypal from '@paypal/checkout-server-sdk';
178
-
179
- export async function POST() {
180
- const request = new paypal.orders.OrdersCreateRequest();
181
- request.prefer("return=representation");
182
- request.requestBody({
183
- intent: 'CAPTURE',
184
- purchase_units: [{ amount: { currency_code: 'USD', value: '29.99' } }]
185
- });
186
-
187
- const response = await paypalClient().execute(request);
188
- return Response.json({ id: response.result.id });
189
- }
190
- ```
191
-
192
- #### Capture Payment (Server-Side)
193
- ```typescript
194
- // app/api/paypal/capture-order/route.ts
195
- import { db } from '@/lib/db';
196
- import { paypalClient } from '@/lib/paypal';
197
- import paypal from '@paypal/checkout-server-sdk';
198
-
199
- export async function POST(req: Request) {
200
- const { orderID, userId } = await req.json();
201
- const request = new paypal.orders.OrdersCaptureRequest(orderID);
202
- request.requestBody({});
203
-
204
- const response = await paypalClient().execute(request);
205
- if (response.result.status === 'COMPLETED') {
206
- // Grant access or update subscription in DB
207
- await db.subscription.create({
208
- data: { userId, provider: 'paypal', status: 'active' }
209
- });
210
- return Response.json({ success: true });
211
- }
212
- return Response.json({ success: false }, { status: 400 });
213
- }
214
- ```
215
-
216
- ### QRIS Static-to-Dynamic (Indonesia Alternative)
217
-
218
- For Indonesian developers without an official Payment Gateway account, you can create a "Dynamic" QRIS experience using a single Static QRIS combined with unique payment amounts and a bank mutation checking service (e.g., Moota, Cekmutasi) via webhook.
219
-
220
- #### 1. Generate Unique Amount (Server-Side)
221
- ```typescript
222
- // Add a unique 3-digit code to the base price
223
- export async function createQrisTransaction(userId: string, basePrice: number) {
224
- // Generate a random code between 1 and 999
225
- const uniqueCode = Math.floor(Math.random() * 999) + 1;
226
- const totalAmount = basePrice + uniqueCode;
227
-
228
- const transaction = await db.transaction.create({
229
- data: {
230
- userId, basePrice, uniqueCode, totalAmount,
231
- status: 'pending', provider: 'qris_static',
232
- expiresAt: new Date(Date.now() + 15 * 60 * 1000) // 15 mins expiry
233
- }
234
- });
235
-
236
- return {
237
- transactionId: transaction.id,
238
- totalAmount,
239
- qrisUrl: "https://myapp.com/static-qris.png" // User must manually input totalAmount
240
- };
241
- }
242
- ```
243
-
244
- #### 2. Mutation Webhook Handler & Real-Time Notification
245
- ```typescript
246
- // app/api/webhooks/mutation/route.ts
247
- import { db } from '@/lib/db';
248
- import { pusherServer } from '@/lib/pusher';
249
-
250
- export async function POST(req: Request) {
251
- const signature = req.headers.get("signature");
252
- // TODO: Verify signature from mutation service (e.g., Moota)
253
-
254
- const mutations = await req.json();
255
-
256
- for (const mutation of mutations) {
257
- if (mutation.type === 'CR' && mutation.amount > 0) {
258
- // Find pending transaction matching the exact unique amount
259
- const tx = await db.transaction.findFirst({
260
- where: {
261
- totalAmount: mutation.amount,
262
- status: 'pending',
263
- provider: 'qris_static',
264
- expiresAt: { gt: new Date() }
265
- }
266
- });
267
-
268
- if (tx) {
269
- // Mark as paid and activate subscription
270
- await db.transaction.update({ where: { id: tx.id }, data: { status: 'paid' } });
271
- await db.subscription.create({
272
- data: { userId: tx.userId, provider: 'qris_static', status: 'active' }
273
- });
274
-
275
- // Trigger real-time notification to frontend
276
- await pusherServer.trigger(`payment-${tx.id}`, 'payment-success', { success: true });
277
- }
278
- }
279
- }
280
-
281
- return new Response("OK", { status: 200 });
282
- }
283
- ```
284
-
285
- ### Database Schema for Multi-Provider Billing
286
- ```typescript
287
- // Drizzle ORM — supports Stripe, Polar, LemonSqueezy, PayPal, QRIS Static
288
- export const subscriptions = pgTable('subscriptions', {
289
- id: text('id').primaryKey(),
290
- workspaceId: text('workspace_id').references(() => workspaces.id).notNull(),
291
- provider: text('provider').$type<'stripe' | 'polar' | 'lemonsqueezy' | 'paypal' | 'qris_static'>().notNull(),
292
- externalCustomerId: text('external_customer_id').notNull(),
293
- externalSubId: text('external_sub_id').notNull().unique(),
294
- status: text('status').$type<'active' | 'trialing' | 'past_due' | 'canceled' | 'paused'>().notNull(),
295
- plan: text('plan').$type<'free' | 'pro' | 'enterprise'>().default('free').notNull(),
296
- currentPeriodEnd: timestamp('current_period_end'),
297
- cancelAtPeriodEnd: boolean('cancel_at_period_end').default(false),
298
- createdAt: timestamp('created_at').defaultNow().notNull(),
299
- updatedAt: timestamp('updated_at').defaultNow().notNull(),
300
- });
301
- ```
302
-
303
- ### Billing Security Checklist
304
- - [ ] Webhook signature verified on every request — reject without valid signature.
305
- - [ ] Webhook idempotency implemented — never process the same event twice.
306
- - [ ] Session Management Optimization: Secure the billing portal route with strict session validation and CSRF protection. Do not cache session-dependent billing states.
307
- - [ ] Use Stripe CLI / Polar.sh test webhooks for local development.
308
- - [ ] All billing API calls use server-side code only — never expose secret keys to frontend.
309
- - [ ] Plan limits enforced on every protected route (not just at checkout).
310
- - [ ] Failed payment dunning flow configured (email sequence, grace period).
311
- - [ ] Customer portal link available from within the app.
312
-
313
- ---
314
-
315
- <a name="bahasa-indonesia"></a>
316
- ## Bahasa Indonesia
317
-
318
- ### Integrasi Orkestrasi
319
- Terhubung dan mengorkestrasi skill domain yang relevan seperti `brainstorming`, `zero-to-prod-orchestrator`, dan `project-context-mapper` untuk memastikan eksekusi yang kohesif.
320
-
321
- ### Deskripsi
322
- Panduan ahli untuk mengimplementasikan dan mengaudit sistem billing SaaS. Mencakup state machine langganan, penanganan webhook aman, sinkronisasi database, dan lanskap billing 2026 termasuk Stripe, **Polar.sh** (open-source, developer-first), **LemonSqueezy**, PayPal, dan Midtrans (untuk Asia Tenggara).
323
-
324
- ### Kondisi Pemicu
325
- - Mengintegrasikan payment gateway (Stripe, Polar.sh, LemonSqueezy, Midtrans, PayPal) ke aplikasi SaaS.
326
- - Menggunakan alternatif QRIS Statis menjadi Dinamis (dengan nominal unik dan webhook mutasi) untuk developer lokal tanpa akun PG.
327
- - Mengimplementasikan state machine langganan.
328
- - Membangun webhook handler aman dengan verifikasi tanda tangan dan idempotency.
329
- - Menyinkronkan status langganan eksternal ke database lokal.
330
- - Mengimplementasikan billing berbasis penggunaan (metered pricing).
331
- - Membangun portal billing pelanggan.
332
- - Mengaudit sistem billing yang ada untuk celah keamanan.
333
-
334
- ### Lanskap Provider Billing 2026
335
-
336
- | Provider | Terbaik Untuk | Open Source | Merchant of Record |
337
- |---|---|---|---|
338
- | **Stripe** | Enterprise, global, billing kompleks | ❌ | ❌ |
339
- | **Polar.sh** | Developer-first, produk open-source | ✅ | ✅ (opsional) |
340
- | **LemonSqueezy** | Indie hackers, harga sederhana | ❌ | ✅ |
341
- | **Paddle** | B2B SaaS, kepatuhan PPN EU | ❌ | ✅ |
342
- | **Midtrans** | Asia Tenggara / Indonesia | ❌ | ❌ |
343
- | **PayPal** | Global, kepercayaan konsumen (consumer trust) | ❌ | ❌ |
344
-
345
- > **Merchant of Record (MoR)**: Provider menangani kepatuhan pajak (PPN, GST), chargeback, dan tanggung jawab hukum — ideal untuk tim kecil tanpa departemen keuangan.
346
-
347
- ### Polar.sh — Billing Developer-First
348
- Polar.sh adalah alternatif open-source modern untuk Gumroad/LemonSqueezy, dirancang khusus untuk developer dan proyek open-source. Mendukung checkout, webhook, dan manajemen langganan dengan SDK TypeScript yang bersih.
349
-
350
- ### PayPal — Integrasi Checkout
351
- PayPal sering digunakan sebagai gateway alternatif atau utama karena tingginya kepercayaan konsumen global.
352
- - **Server-Side Checkout**: Gunakan `create-order` dan `capture-order` di backend (menggunakan `@paypal/checkout-server-sdk`) untuk memastikan keamanan dan mencegah manipulasi harga di sisi klien.
353
- - **Webhook**: Verifikasi webhook dari PayPal untuk langganan yang diperbarui atau dibatalkan.
354
-
355
- ### Stripe — Pola Produksi
356
-
357
- #### State Machine Langganan
358
- Kelola transisi status: `FREE → TRIALING → ACTIVE → PAST_DUE → CANCELED → (reaktivasi)`.
359
-
360
- #### Webhook Handler Idempoten
361
- Selalu verifikasi tanda tangan webhook, tandai event sebagai diproses di database untuk mencegah duplikasi.
362
-
363
- #### Billing Berbasis Penggunaan (Metered)
364
- Laporkan penggunaan API dengan `stripe.subscriptionItems.createUsageRecord()` di akhir periode billing.
365
-
366
- ### Alternatif Lokal: QRIS Statis Rasa Dinamis (Tanpa Akun Payment Gateway)
367
- Bagi pengguna/developer di Indonesia yang belum memiliki Payment Gateway (seperti Midtrans), Anda dapat membuat pengalaman QRIS "Dinamis" menggunakan satu gambar QRIS statis biasa.
368
- - **Generate Nominal Unik (Endpoint)**: Tambahkan angka unik (misalnya 3 digit acak) ke harga dasar (contoh: Rp 100.000 menjadi Rp 100.123). Simpan ke database sebagai transaksi `pending` dengan batas waktu kadaluarsa (misal 15 menit). Tampilkan gambar QRIS beserta instruksi transfer sesuai nominal unik.
369
- - **Webhook Mutasi Bank**: Gunakan layanan pihak ketiga (seperti Moota, Cekmutasi) yang mengirimkan notifikasi webhook (ke `/api/webhooks/mutation`) setiap kali ada uang masuk.
370
- - **Validasi Otomatis**: Saat webhook menerima payload mutasi kredit (`CR`), sistem mencari transaksi `pending` yang jumlahnya sama persis (`totalAmount`). Jika cocok, sistem menandai tagihan sebagai lunas (`paid`) dan mengaktifkan langganan.
371
- - **Notifikasi Klien**: Gunakan WebSocket (seperti Pusher atau Socket.io) di backend untuk melakukan *trigger event* "pembayaran berhasil". Di frontend, *listen* ke event tersebut dan tampilkan notifikasi *real-time* kepada pengguna secara instan tanpa perlu me-refresh halaman.
372
-
373
- ### Skema Database Multi-Provider
374
- Rancang tabel `subscriptions` yang mendukung beberapa provider (`stripe`, `polar`, `lemonsqueezy`, `paypal`, `qris_static`) dengan kolom `provider` dan ID eksternal yang terpisah.
375
-
376
- ### Checklist Keamanan Billing
377
- - [ ] Tanda tangan webhook diverifikasi pada setiap permintaan.
378
- - [ ] Idempotency webhook diimplementasikan.
379
- - [ ] Optimasi Session Management: Amankan rute portal billing dengan validasi sesi yang ketat dan perlindungan CSRF. Jangan gunakan cache untuk state billing yang bergantung pada sesi pengguna.
380
- - [ ] Semua panggilan API billing menggunakan kode sisi server saja.
381
- - [ ] Batas plan diterapkan pada setiap rute yang dilindungi.
382
- - [ ] Alur dunning pembayaran gagal dikonfigurasi.
383
- - [ ] Tautan portal pelanggan tersedia dari dalam aplikasi.
5
+ version: "4.0.0"
6
+ ---
7
+
8
+ # SaaS Billing Expert (2026 Edition)
9
+
10
+ [English](#english) | [Bahasa Indonesia](#bahasa-indonesia)
11
+
12
+ ---
13
+
14
+ <a name="english"></a>
15
+ ## English
16
+
17
+ ### Orchestration & Integration
18
+ Connects and orchestrates with relevant domain skills like `brainstorming`, `zero-to-prod-orchestrator`, and `session-memory-manager` to ensure cohesive execution.
19
+
20
+ ### Description
21
+ Expert guide for implementing and auditing SaaS billing systems. Covers subscription state machines, secure webhook handling, atomic database synchronization, and the 2026 billing landscape including Stripe, **Polar.sh** (open-source, developer-first), **LemonSqueezy**, Paddle, DOKU (SNAP BI), and Midtrans (for Southeast Asia).
22
+
23
+ ### Trigger Conditions
24
+ - Integrating any payment gateway (Stripe, Polar.sh, LemonSqueezy, DOKU SNAP BI, Midtrans, PayPal) into a SaaS application.
25
+ - Using a Static-to-Dynamic QRIS alternative (with unique nominals and mutation webhooks) for local developers without PG accounts.
26
+ - Implementing subscription state machines (active → past_due → canceled → reactivated).
27
+ - Building secure webhook handlers with signature verification and atomic idempotency.
28
+ - Syncing external subscription status to a local database.
29
+ - Implementing usage-based billing or metered API pricing.
30
+ - Building the customer billing portal (manage subscription, download invoices).
31
+ - Auditing an existing billing system for security gaps.
32
+
33
+ ### 2026 Billing Provider Landscape
34
+
35
+ | Provider | Best For | Open Source | Merchant of Record |
36
+ |---|---|---|---|
37
+ | **Stripe** | Enterprise, global, complex billing | ❌ | ❌ |
38
+ | **Polar.sh** | Developer-first, open-source products | ✅ | ✅ (optional) |
39
+ | **LemonSqueezy** | Indie hackers, simple pricing, global | ❌ | ✅ |
40
+ | **Paddle** | B2B SaaS, EU VAT compliance | ❌ | ✅ |
41
+ | **DOKU (SNAP BI)** | Indonesia & SE Asia enterprise, QRIS, VA | ❌ | ❌ |
42
+ | **Midtrans** | Southeast Asia / Indonesia | ❌ | ❌ |
43
+ | **PayPal** | Global, consumer trust | ❌ | ❌ |
44
+
45
+ > **Merchant of Record (MoR)**: The provider handles tax compliance (VAT, GST), chargebacks, and legal liability — ideal for small teams without a finance department.
46
+
47
+ ### Polar.sh — Developer-First Billing (2026 Rising Star)
48
+ Polar.sh is the modern, open-source alternative to Gumroad/LemonSqueezy, purpose-built for developers and open-source projects:
49
+ ```typescript
50
+ import { Polar } from "@polar-sh/sdk";
51
+
52
+ const polar = new Polar({ accessToken: process.env.POLAR_ACCESS_TOKEN });
53
+
54
+ // Create a checkout session
55
+ const checkout = await polar.checkouts.custom.create({
56
+ productId: "prod_xxxx",
57
+ successUrl: "https://myapp.com/success?checkout={CHECKOUT_ID}",
58
+ customerEmail: user.email,
59
+ metadata: { userId: user.id },
60
+ });
61
+
62
+ // Redirect to checkout
63
+ return redirect(checkout.url);
64
+ ```
65
+
66
+ ```typescript
67
+ // Webhook handler (Next.js App Router)
68
+ import { validateEvent, WebhookVerificationError } from "@polar-sh/sdk/webhooks";
69
+
70
+ export async function POST(req: Request) {
71
+ const body = await req.text();
72
+ const signature = req.headers.get("webhook-signature") ?? "";
73
+
74
+ try {
75
+ const event = validateEvent(body, req.headers, process.env.POLAR_WEBHOOK_SECRET!);
76
+
77
+ switch (event.type) {
78
+ case "subscription.created":
79
+ case "subscription.updated":
80
+ await syncSubscription(event.data);
81
+ break;
82
+ case "subscription.canceled":
83
+ await cancelSubscription(event.data.id);
84
+ break;
85
+ }
86
+ return new Response(null, { status: 200 });
87
+ } catch (e) {
88
+ if (e instanceof WebhookVerificationError) {
89
+ return new Response("Invalid signature", { status: 403 });
90
+ }
91
+ throw e;
92
+ }
93
+ }
94
+ ```
95
+
96
+ ### Stripe — Production Patterns
97
+
98
+ #### Subscription State Machine
99
+ ```
100
+ FREE ──subscribe──> TRIALING ──trial_ends──> ACTIVE
101
+ │
102
+ ┌───────────────┤
103
+ │ │
104
+ payment fails cancel
105
+ │ │
106
+ PAST_DUE CANCELED
107
+ │
108
+ 3 failed retries
109
+ │
110
+ CANCELED
111
+ ```
112
+
113
+ #### Idempotent Webhook Handler (Race Condition Prevention)
114
+ ```typescript
115
+ // app/api/webhooks/stripe/route.ts
116
+ import Stripe from 'stripe';
117
+ import { db } from '@/lib/db';
118
+
119
+ const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
120
+
121
+ export async function POST(req: Request) {
122
+ const body = await req.text(); // Raw body is mandatory
123
+ const sig = req.headers.get('stripe-signature')!;
124
+
125
+ let event: Stripe.Event;
126
+ try {
127
+ event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET!);
128
+ } catch {
129
+ return new Response('Invalid signature', { status: 400 });
130
+ }
131
+
132
+ // Idempotency: Atomic insert on unique constraint to guarantee zero race conditions
133
+ try {
134
+ await db.webhookEvent.create({
135
+ data: {
136
+ stripeEventId: event.id,
137
+ eventType: event.type,
138
+ processedAt: new Date(),
139
+ },
140
+ });
141
+ } catch (err: any) {
142
+ // Unique violation (e.g. Prisma code P2002) means this event is ALREADY processed or currently processing
143
+ return new Response(JSON.stringify({ message: 'Event already received' }), { status: 200 });
144
+ }
145
+
146
+ // Process event safely knowing this invocation is guaranteed unique
147
+ switch (event.type) {
148
+ case 'customer.subscription.created':
149
+ case 'customer.subscription.updated': {
150
+ const sub = event.data.object as Stripe.Subscription;
151
+ await db.subscription.upsert({
152
+ where: { stripeSubId: sub.id },
153
+ create: { stripeSubId: sub.id, status: sub.status, userId: sub.metadata.userId },
154
+ update: { status: sub.status, currentPeriodEnd: new Date(sub.current_period_end * 1000) },
155
+ });
156
+ break;
157
+ }
158
+ case 'invoice.payment_failed': {
159
+ const invoice = event.data.object as Stripe.Invoice;
160
+ await sendDunningEmail(invoice.customer_email!);
161
+ break;
162
+ }
163
+ }
164
+
165
+ return new Response(null, { status: 200 });
166
+ }
167
+ ```
168
+
169
+ #### Usage-Based Billing (Metered)
170
+ ```typescript
171
+ // Report usage at end of billing period
172
+ await stripe.subscriptionItems.createUsageRecord(subscriptionItemId, {
173
+ quantity: apiCallsThisMonth,
174
+ timestamp: Math.floor(Date.now() / 1000),
175
+ action: 'set', // 'set' or 'increment'
176
+ });
177
+ ```
178
+
179
+ ### PayPal — Checkout Integration
180
+
181
+ PayPal remains a trusted global standard for one-off payments and subscriptions.
182
+
183
+ #### Create Order (Server-Side)
184
+ ```typescript
185
+ // app/api/paypal/create-order/route.ts
186
+ import { paypalClient } from '@/lib/paypal';
187
+ import paypal from '@paypal/checkout-server-sdk';
188
+
189
+ export async function POST() {
190
+ const request = new paypal.orders.OrdersCreateRequest();
191
+ request.prefer("return=representation");
192
+ request.requestBody({
193
+ intent: 'CAPTURE',
194
+ purchase_units: [{ amount: { currency_code: 'USD', value: '29.99' } }]
195
+ });
196
+
197
+ const response = await paypalClient().execute(request);
198
+ return Response.json({ id: response.result.id });
199
+ }
200
+ ```
201
+
202
+ #### Capture Payment (Server-Side)
203
+ ```typescript
204
+ // app/api/paypal/capture-order/route.ts
205
+ import { db } from '@/lib/db';
206
+ import { paypalClient } from '@/lib/paypal';
207
+ import paypal from '@paypal/checkout-server-sdk';
208
+
209
+ export async function POST(req: Request) {
210
+ const { orderID, userId } = await req.json();
211
+ const request = new paypal.orders.OrdersCaptureRequest(orderID);
212
+ request.requestBody({});
213
+
214
+ const response = await paypalClient().execute(request);
215
+ if (response.result.status === 'COMPLETED') {
216
+ // Grant access or update subscription in DB
217
+ await db.subscription.create({
218
+ data: { userId, provider: 'paypal', status: 'active' }
219
+ });
220
+ return Response.json({ success: true });
221
+ }
222
+ return Response.json({ success: false }, { status: 400 });
223
+ }
224
+ ```
225
+
226
+ ### QRIS Static-to-Dynamic (Indonesia Alternative)
227
+
228
+ For Indonesian developers without an official Payment Gateway account, you can create a "Dynamic" QRIS experience using a single Static QRIS combined with unique payment amounts and a bank mutation checking service (e.g., Moota, Cekmutasi) via webhook.
229
+
230
+ #### 1. Generate Unique Amount (Server-Side)
231
+ ```typescript
232
+ // Add a unique 3-digit code to the base price
233
+ export async function createQrisTransaction(userId: string, basePrice: number) {
234
+ // Generate a random code between 1 and 999
235
+ const uniqueCode = Math.floor(Math.random() * 999) + 1;
236
+ const totalAmount = basePrice + uniqueCode;
237
+
238
+ const transaction = await db.transaction.create({
239
+ data: {
240
+ userId, basePrice, uniqueCode, totalAmount,
241
+ status: 'pending', provider: 'qris_static',
242
+ expiresAt: new Date(Date.now() + 15 * 60 * 1000) // 15 mins expiry
243
+ }
244
+ });
245
+
246
+ return {
247
+ transactionId: transaction.id,
248
+ totalAmount,
249
+ qrisUrl: "https://myapp.com/static-qris.png" // User must manually input totalAmount
250
+ };
251
+ }
252
+ ```
253
+
254
+ #### 2. Mutation Webhook Handler & Real-Time Notification
255
+ ```typescript
256
+ // app/api/webhooks/mutation/route.ts
257
+ import { db } from '@/lib/db';
258
+ import { pusherServer } from '@/lib/pusher';
259
+
260
+ export async function POST(req: Request) {
261
+ const signature = req.headers.get("signature");
262
+ // TODO: Verify signature from mutation service (e.g., Moota)
263
+
264
+ const mutations = await req.json();
265
+
266
+ for (const mutation of mutations) {
267
+ if (mutation.type === 'CR' && mutation.amount > 0) {
268
+ // Atomic Update: Only update if still pending
269
+ const updateResult = await db.transaction.updateMany({
270
+ where: {
271
+ totalAmount: mutation.amount,
272
+ status: 'pending',
273
+ provider: 'qris_static',
274
+ expiresAt: { gt: new Date() }
275
+ },
276
+ data: { status: 'paid' }
277
+ });
278
+
279
+ if (updateResult.count > 0) {
280
+ const tx = await db.transaction.findFirst({ where: { totalAmount: mutation.amount, status: 'paid' } });
281
+ if (tx) {
282
+ await db.subscription.create({
283
+ data: { userId: tx.userId, provider: 'qris_static', status: 'active' }
284
+ });
285
+ // Trigger real-time notification to frontend
286
+ await pusherServer.trigger(`payment-${tx.id}`, 'payment-success', { success: true });
287
+ }
288
+ }
289
+ }
290
+ }
291
+
292
+ return new Response("OK", { status: 200 });
293
+ }
294
+ ```
295
+
296
+ ### Database Schema for Multi-Provider Billing
297
+ ```typescript
298
+ // Drizzle ORM — supports Stripe, Polar, LemonSqueezy, PayPal, DOKU, QRIS Static
299
+ export const subscriptions = pgTable('subscriptions', {
300
+ id: text('id').primaryKey(),
301
+ workspaceId: text('workspace_id').references(() => workspaces.id).notNull(),
302
+ provider: text('provider').$type<'stripe' | 'polar' | 'lemonsqueezy' | 'doku' | 'paypal' | 'qris_static'>().notNull(),
303
+ externalCustomerId: text('external_customer_id').notNull(),
304
+ externalSubId: text('external_sub_id').notNull().unique(),
305
+ status: text('status').$type<'active' | 'trialing' | 'past_due' | 'canceled' | 'paused'>().notNull(),
306
+ plan: text('plan').$type<'free' | 'pro' | 'enterprise'>().default('free').notNull(),
307
+ currentPeriodEnd: timestamp('current_period_end'),
308
+ cancelAtPeriodEnd: boolean('cancel_at_period_end').default(false),
309
+ createdAt: timestamp('created_at').defaultNow().notNull(),
310
+ updatedAt: timestamp('updated_at').defaultNow().notNull(),
311
+ });
312
+ ```
313
+
314
+ ### Billing Security Checklist
315
+ - [ ] Webhook signature verified on every request — reject without valid signature.
316
+ - [ ] Webhook idempotency implemented with Atomic Lock — never process the same event twice.
317
+ - [ ] Session Management Optimization: Secure the billing portal route with strict session validation and CSRF protection. Do not cache session-dependent billing states.
318
+ - [ ] Use Stripe CLI / Polar.sh test webhooks for local development.
319
+ - [ ] All billing API calls use server-side code only — never expose secret keys to frontend.
320
+ - [ ] Plan limits enforced on every protected route (not just at checkout).
321
+ - [ ] Failed payment dunning flow configured (email sequence, grace period).
322
+ - [ ] Customer portal link available from within the app.
323
+
324
+ ---
325
+
326
+ <a name="bahasa-indonesia"></a>
327
+ ## Bahasa Indonesia
328
+
329
+ ### Integrasi Orkestrasi
330
+ Terhubung dan mengorkestrasi skill domain yang relevan seperti `brainstorming`, `zero-to-prod-orchestrator`, dan `session-memory-manager` untuk memastikan eksekusi yang kohesif.
331
+
332
+ ### Deskripsi
333
+ Panduan ahli untuk mengimplementasikan dan mengaudit sistem billing SaaS. Mencakup state machine langganan, penanganan webhook aman, sinkronisasi database, dan lanskap billing 2026 termasuk Stripe, **Polar.sh** (open-source, developer-first), **LemonSqueezy**, Paddle, DOKU (SNAP BI), dan Midtrans (untuk Asia Tenggara).
334
+
335
+ ### Kondisi Pemicu
336
+ - Mengintegrasikan payment gateway (Stripe, Polar.sh, LemonSqueezy, DOKU SNAP BI, Midtrans, PayPal) ke aplikasi SaaS.
337
+ - Menggunakan alternatif QRIS Statis menjadi Dinamis (dengan nominal unik dan webhook mutasi) untuk developer lokal tanpa akun PG.
338
+ - Mengimplementasikan state machine langganan.
339
+ - Membangun webhook handler aman dengan verifikasi tanda tangan dan idempotency berbasis atomic lock.
340
+ - Menyinkronkan status langganan eksternal ke database lokal.
341
+ - Mengimplementasikan billing berbasis penggunaan (metered pricing).
342
+ - Membangun portal billing pelanggan.
343
+ - Mengaudit sistem billing yang ada untuk celah keamanan.
344
+
345
+ ### Lanskap Provider Billing 2026
346
+
347
+ | Provider | Terbaik Untuk | Open Source | Merchant of Record |
348
+ |---|---|---|---|
349
+ | **Stripe** | Enterprise, global, billing kompleks | ❌ | ❌ |
350
+ | **Polar.sh** | Developer-first, produk open-source | ✅ | ✅ (opsional) |
351
+ | **LemonSqueezy** | Indie hackers, harga sederhana | ❌ | ✅ |
352
+ | **Paddle** | B2B SaaS, kepatuhan PPN EU | ❌ | ✅ |
353
+ | **DOKU (SNAP BI)** | Indonesia & Asia Tenggara, QRIS, Virtual Account | ❌ | ❌ |
354
+ | **Midtrans** | Asia Tenggara / Indonesia | ❌ | ❌ |
355
+ | **PayPal** | Global, kepercayaan konsumen (consumer trust) | ❌ | ❌ |
356
+
357
+ > **Merchant of Record (MoR)**: Provider menangani kepatuhan pajak (PPN, GST), chargeback, dan tanggung jawab hukum — ideal untuk tim kecil tanpa departemen keuangan.
358
+
359
+ ### Polar.sh — Billing Developer-First
360
+ Polar.sh adalah alternatif open-source modern untuk Gumroad/LemonSqueezy, dirancang khusus untuk developer dan proyek open-source. Mendukung checkout, webhook, dan manajemen langganan dengan SDK TypeScript yang bersih.
361
+
362
+ ### PayPal — Integrasi Checkout
363
+ PayPal sering digunakan sebagai gateway alternatif atau utama karena tingginya kepercayaan konsumen global.
364
+ - **Server-Side Checkout**: Gunakan `create-order` dan `capture-order` di backend (menggunakan `@paypal/checkout-server-sdk`) untuk memastikan keamanan dan mencegah manipulasi harga di sisi klien.
365
+ - **Webhook**: Verifikasi webhook dari PayPal untuk langganan yang diperbarui atau dibatalkan.
366
+
367
+ ### Stripe — Pola Produksi
368
+
369
+ #### State Machine Langganan
370
+ Kelola transisi status: `FREE → TRIALING → ACTIVE → PAST_DUE → CANCELED → (reaktivasi)`.
371
+
372
+ #### Webhook Handler Idempoten (Pencegahan Race Condition)
373
+ Selalu verifikasi tanda tangan webhook dari raw body. Gunakan insert unik atomic di database (bukan sekadar `findUnique`) agar request retry simultan tidak menyebabkan penambahan saldo atau perpanjangan langganan ganda. Jika terdeteksi duplikat, langsung kembalikan status HTTP `200 OK`.
374
+
375
+ #### Billing Berbasis Penggunaan (Metered)
376
+ Laporkan penggunaan API dengan `stripe.subscriptionItems.createUsageRecord()` di akhir periode billing.
377
+
378
+ ### Alternatif Lokal: QRIS Statis Rasa Dinamis (Tanpa Akun Payment Gateway)
379
+ Bagi pengguna/developer di Indonesia yang belum memiliki Payment Gateway (seperti Midtrans/DOKU), Anda dapat membuat pengalaman QRIS "Dinamis" menggunakan satu gambar QRIS statis biasa.
380
+ - **Generate Nominal Unik (Endpoint)**: Tambahkan angka unik (misalnya 3 digit acak) ke harga dasar (contoh: Rp 100.000 menjadi Rp 100.123). Simpan ke database sebagai transaksi `pending` dengan batas waktu kadaluarsa (misal 15 menit). Tampilkan gambar QRIS beserta instruksi transfer sesuai nominal unik.
381
+ - **Webhook Mutasi Bank**: Gunakan layanan pihak ketiga (seperti Moota, Cekmutasi) yang mengirimkan notifikasi webhook (ke `/api/webhooks/mutation`) setiap kali ada uang masuk.
382
+ - **Validasi Otomatis & Atomic Lock**: Saat webhook menerima payload mutasi kredit (`CR`), sistem memperbarui transaksi pending secara atomik. Jika cocok, sistem menandai tagihan sebagai lunas (`paid`) dan mengaktifkan langganan.
383
+ - **Notifikasi Klien**: Gunakan WebSocket (seperti Pusher atau Socket.io) di backend untuk melakukan *trigger event* "pembayaran berhasil". Di frontend, *listen* ke event tersebut dan tampilkan notifikasi *real-time* kepada pengguna secara instan tanpa perlu me-refresh halaman.
384
+
385
+ ### Skema Database Multi-Provider
386
+ Rancang tabel `subscriptions` yang mendukung beberapa provider (`stripe`, `polar`, `lemonsqueezy`, `doku`, `paypal`, `qris_static`) dengan kolom `provider` dan ID eksternal yang terpisah.
387
+
388
+ ### Checklist Keamanan Billing
389
+ - [ ] Tanda tangan webhook diverifikasi pada setiap permintaan menggunakan raw body.
390
+ - [ ] Idempotency webhook diimplementasikan dengan penguncian atomic.
391
+ - [ ] Optimasi Session Management: Amankan rute portal billing dengan validasi sesi yang ketat dan perlindungan CSRF. Jangan gunakan cache untuk state billing yang bergantung pada sesi pengguna.
392
+ - [ ] Semua panggilan API billing menggunakan kode sisi server saja.
393
+ - [ ] Batas plan diterapkan pada setiap rute yang dilindungi.
394
+ - [ ] Alur dunning pembayaran gagal dikonfigurasi.
395
+ - [ ] Tautan portal pelanggan tersedia dari dalam aplikasi.