Composes a full vertical payment integration from canonical templates. Called by integration-specialist subagent after the user selects a provider. Loads templates by (provider, stack, ORM, deploy target, billing_mode, frontend_style, use_cases) and emits a coherent file plan that gets written to the user's project. Always cites webhook-verifier and SECURITY_RULES. Phase 1 supports stripe, mercadopago, wompi, lemonsqueezy on Next.js App Router and Express; Phase 2 adds more.
Scanned 5/28/2026
Install via CLI
openskills install Hainrixz/agente-pagokit---
name: integration-builder
description: Composes a full vertical payment integration from canonical templates. Called by integration-specialist subagent after the user selects a provider. Loads templates by (provider, stack, ORM, deploy target, billing_mode, frontend_style, use_cases) and emits a coherent file plan that gets written to the user's project. Always cites webhook-verifier and SECURITY_RULES. Phase 1 supports stripe, mercadopago, wompi, lemonsqueezy on Next.js App Router and Express; Phase 2 adds more.
when_to_use: |
- integration-specialist subagent is implementing a chosen provider for the user
- The user asks "regenerate the stripe integration with subscription mode" or equivalent rebuild
allowed-tools: Read, Glob
---
# integration-builder
You are the catalog of how to compose a PagoKit integration. You do NOT write to the user's project — that is integration-specialist's job. You read templates and produce a structured plan that the subagent then executes.
## Inputs (passed by integration-specialist)
```json
{
"provider": "stripe|mercadopago|wompi|lemonsqueezy",
"stack": "nextjs-app-router|express|fastapi|...",
"orm": "prisma|drizzle|sqlalchemy|...|none",
"deploy_target": "vercel|railway|...|none",
"billing_mode": "one_time|subscription",
"frontend_style": "hosted|embedded|widget",
"required_methods": ["card", "oxxo", "..."],
"use_cases_detected": ["save_card_subscription", "..."],
"language": "es|en|...",
"example_transaction_amount": 20,
"example_currency": "USD"
}
```
## Templates you load
Per provider (e.g., Stripe):
- `templates/stripe/reference.md` — canonical patterns + anti-patterns
- `templates/stripe/webhook.md` — verification specifics
- `templates/stripe/one-time.md` OR `templates/stripe/subscription.md` (by billing_mode)
- `templates/stripe/save-card.md` if `save_card_subscription` detected
- `templates/stripe/customer-portal.md` if `billing_mode == subscription`
- `templates/stripe/refund-endpoint.md` (always — every integration ships with refund capability)
- `templates/stripe/errors.md`
- `templates/stripe/frontend-hosted.md` or `templates/stripe/frontend-embedded.md` (by frontend_style)
- `templates/stripe/3ds-handling.md` if buyer regions include EU/UK
- `templates/stripe/tax.md` if user opted for tax automation
Per stack:
- `templates/_stack-adapters/nextjs-app-router.md` (raw body, NextResponse, runtime: 'nodejs')
- `templates/_stack-adapters/express.md` (express.raw middleware ordering)
Per ORM:
- `templates/_db-adapters/prisma.md` (schema.prisma + migration commands)
- `templates/_db-adapters/drizzle.md`
- `templates/_db-adapters/sqlalchemy.md` (+ alembic)
Per deploy target:
- `templates/_deploy-targets/vercel.md` (vercel env add commands)
- `templates/_deploy-targets/railway.md`
## Pre-compiled combos (preferred)
For the 8 highest-traffic combos, `templates/compiled/<provider>-<stack>-<billing>.md` exists with a single canonical file already composed. ALWAYS prefer these over runtime composition when the combo matches exactly. Phase 1 ships:
- `stripe-nextjs-app-router-one-time.md`
- `stripe-nextjs-app-router-subscription.md`
- `stripe-express-subscription.md`
- `mercadopago-nextjs-app-router-one-time.md`
- `mercadopago-express-one-time.md`
- `wompi-nextjs-app-router-one-time.md`
- `wompi-express-one-time.md`
- `lemonsqueezy-nextjs-app-router-subscription.md`
For non-matching combos, fall back to runtime composition using the per-provider and per-stack templates.
## The integration plan you emit
After loading templates, emit a structured plan in your response (in a fenced ```json block) that integration-specialist then executes:
```json
{
"files_to_create": [
{
"path": "app/api/checkout/route.ts",
"purpose": "POST /api/checkout — creates a payment_intent with idempotency",
"template_source": "templates/stripe/one-time.md + templates/_stack-adapters/nextjs-app-router.md",
"must_include_rule_tags": ["// Rule 4: idempotency", "// Rule 5: raw body N/A here, checkout endpoint"]
},
{
"path": "app/api/webhook/stripe/route.ts",
"purpose": "POST /api/webhook/stripe — verify signature, dispatch events",
"template_source": "templates/stripe/webhook.md + templates/_stack-adapters/nextjs-app-router.md",
"must_include_rule_tags": ["// Rule 3: signature", "// Rule 5: raw body", "// Rule 9: timestamp window", "// Rule 10: body size cap"],
"events_routed": ["payment_intent.succeeded", "payment_intent.payment_failed", "charge.refunded", "charge.dispute.created", "invoice.payment_failed", "customer.subscription.deleted", "customer.subscription.updated"]
},
{
"path": "app/api/portal/route.ts",
"purpose": "POST /api/portal — billingPortal.sessions.create for subscription self-service",
"template_source": "templates/stripe/customer-portal.md",
"skip_if": "billing_mode != subscription"
},
{
"path": "app/api/refund/route.ts",
"purpose": "POST /api/refund — auth-checked refund emission",
"template_source": "templates/stripe/refund-endpoint.md"
},
{
"path": "components/CheckoutButton.tsx",
"purpose": "Frontend trigger — Stripe Checkout redirect (hosted)",
"template_source": "templates/stripe/frontend-hosted.md"
},
{
"path": "lib/payments/errors.ts",
"purpose": "Map provider error codes to {code, user_message:{es,en}}",
"template_source": "templates/stripe/errors.md"
},
{
"path": "prisma/schema.prisma",
"operation": "extend",
"purpose": "Add tables payments, subscriptions, customers, idempotency_keys, webhook_events_processed",
"template_source": "templates/_db-adapters/prisma.md"
},
{
"path": ".env.example",
"operation": "create_or_extend",
"purpose": "Document required env vars with test-key prefixes (Rule 8)",
"vars": ["STRIPE_SECRET_KEY=sk_test_…", "STRIPE_PUBLISHABLE_KEY=pk_test_…", "STRIPE_WEBHOOK_SECRET=whsec_…"]
},
{
"path": "PAGOKIT_INTEGRATION.md",
"purpose": "Audit trail of what was generated",
"template_source": "internal — generated by integration-specialist"
},
{
"path": "PAGOKIT_PRODUCTION_CHECKLIST.md",
"purpose": "Step-by-step guide to flip from sandbox to live",
"template_source": "internal — generated by integration-specialist"
}
],
"commands_to_run": [
"npm install stripe@^17",
"npx prisma migrate dev --name pagokit_init"
],
"post_install_hint": "Run /pagokit:test to send synthetic webhook events to your local server.",
"frontend_style_chosen": "hosted",
"deploy_target_instructions": "After deploying, run `vercel env add STRIPE_SECRET_KEY` and paste your test key."
}
```
## Composition rules
1. **Webhook routes are always namespaced** `/api/webhook/<provider>/...`. Even if no collision is detected, the convention is mandatory from Phase 1 to allow multi-provider co-existence.
2. **SDK version is pinned** in `package.json` (e.g., `stripe@^17.x`). Match `providers.json.api_version`.
3. **Use `crypto.randomUUID()`** literally on the idempotency key line (Rule 4 — validators check for this canonical string).
4. **Frontend goes in `components/` (or `src/components/`) for Next.js**, `public/` for Express HTML, or framework-equivalent.
5. **DB tables are non-destructive** — if the schema already declares `payments`, append a `pagokit_payments` table instead of clobbering. integration-specialist runs the collision check.
6. **PAGOKIT_INTEGRATION.md is mandatory.** Without it, `/pagokit:doctor` can't audit later.
## Frontend choice (hosted vs embedded vs widget)
If `provider.frontend_options` has only one option (e.g., Wompi only has `widget`), choose it.
If multiple, decide as follows:
- `billing_mode == one_time` AND `stack == nextjs-app-router` AND no specific branding ask → **hosted** (lowest friction, redirects to provider's checkout page, fewest things to break).
- `billing_mode == subscription` AND product is SaaS → **embedded** (better UX for repeat customers; Stripe Elements / MP Bricks).
- Wompi → always **widget** (only option).
- Lemon Squeezy → **hosted** by default; **embedded** if the user specifically asked.
When in doubt, ask the user once with a single sentence: "¿Prefieres página de checkout del proveedor (más rápido) o componentes integrados en tu app (más control)?".
## Anti-patterns
- Do NOT execute the file writes yourself — emit the plan, return to integration-specialist.
- Do NOT recommend Stripe Charges API; always Payment Intents.
- Do NOT skip the `customer-portal.md` template for subscriptions — without it the user has no cancellation flow.
- Do NOT generate generic "console.log('Webhook received')" code — log only `event.id`, `event.type`, `event.created` (Rule 6).
- Do NOT generate frontend code that posts the user's PAN to your backend — use the provider's element/widget for PCI scope reasons (Rule 12).
No comments yet. Be the first to comment!