mercadopago-subscriptions
MercadoPago Subscriptions
Architecture overview
Frontend (React + Vite) Edge Functions (Deno) MercadoPago API
───────────────────────── ───────────────────── ──────────────
CardForm (Secure Fields) → mp-create-subscription → POST /preapproval
↓ card token mp-manage-subscription → PUT /preapproval/{id}
SubscriptionPage / Settings → mp-get-subscription-status → GET /preapproval/{id}
webhook-mercadopago ← Webhook notifications
cron-check-subscriptions → GET /preapproval/{id}
Key principle: Frontend only tokenizes cards (Secure Fields). All MercadoPago API calls happen server-side in Supabase Edge Functions.
Subscription lifecycle
| Action | Edge Function | MP API | DB Effect |
|---|---|---|---|
| Create | mp-create-subscription |
POST /preapproval (status=authorized) |
Insert subscription, update company |
| Cancel | mp-manage-subscription (action=cancel) |
PUT /preapproval/{id} (status=cancelled) |
Set cancelled, keep access until period end |
| Change plan | mp-manage-subscription (action=change_plan) |
PUT /preapproval/{id} (transaction_amount) |
Update plan_key, amount |
| Change card | mp-manage-subscription (action=change_card) |
PUT /preapproval/{id} (card_token_id) |
No DB card storage (MP stores it) |
| Pause | mp-manage-subscription (action=pause) |
PUT /preapproval/{id} (status=paused) |
Set suspended |
| Reactivate | mp-manage-subscription (action=reactivate) |
PUT /preapproval/{id} (status=authorized) |
Set active |
Quick reference
Creating a subscription (happy path)
Frontend: Generate card token via Secure Fields → call edge function.
// 1. Tokenize card (CardForm component)
const tokenResponse = await mp.fields.createCardToken({
cardholderName, identificationType, identificationNumber
});
// 2. Call edge function
const result = await api.mpCreateSubscription({
planKey: 'basic',
companyId,
cardTokenId: tokenResponse.id,
payerEmail: email
});
Edge function: Create preapproval with status: 'authorized' (triggers immediate charge).
const preapproval = await mpFetch<MpPreapproval>(
`${mpConfig.apiBase}/preapproval`,
{
method: 'POST',
headers: getMpHeaders({ 'X-Idempotency-Key': `mp-sub-${companyId}-${planKey}-${Date.now()}` }),
body: JSON.stringify({
preapproval_plan_id: mpPlanId,
payer_email: payerEmail,
card_token_id: cardTokenId,
auto_recurring: {
frequency: 1, frequency_type: 'months',
transaction_amount: planMeta.amount,
currency_id: 'ARS'
},
status: 'authorized',
back_url: `${Deno.env.get('APP_URL')}/settings`
}),
}
);
Managing a subscription
All management goes through a single mp-manage-subscription edge function dispatching on action:
await api.mpManageSubscription({
action: 'cancel', // or 'change_plan' | 'change_card' | 'pause' | 'reactivate'
mpPreapprovalId: subscription.mpPreapprovalId,
newPlanKey: 'premium', // only for change_plan
cardTokenId: token, // only for change_card
reason: 'User requested', // optional
});
Detailed references
- Frontend patterns (CardForm, hooks, API service): See FRONTEND.md
- Edge function patterns (create, manage, status, CRON): See EDGE_FUNCTIONS.md
- Webhook handling (signature validation, event processing): See WEBHOOK.md
- Type definitions & DB schema: See TYPES.md
- Email notifications: See EMAIL_TEMPLATES.md
Environment variables
Frontend (.env)
VITE_MP_PUBLIC_KEY=<mercadopago-public-key>
Edge Functions (Supabase Secrets)
MP_MODE=sandbox|production
MP_ACCESS_TOKEN=<mercadopago-access-token>
MP_WEBHOOK_SECRET=<webhook-signing-secret>
MP_PLAN_ID_BASIC=<preapproval-plan-id>
MP_PLAN_ID_STANDARD=<preapproval-plan-id>
MP_PLAN_ID_PREMIUM=<preapproval-plan-id>
RESEND_API_KEY=<for-email-notifications>
Plans configuration
Plans are defined in supabase/functions/_shared/mp-plans.ts:
const MP_PLAN_METADATA = {
basic: { name: 'Básico', amount: 25000, features: [...] },
standard: { name: 'Estándar', amount: 49000, features: [...] },
premium: { name: 'Premium', amount: 89000, features: [...] },
};
Each plan has a corresponding MercadoPago preapproval plan ID stored in env vars (MP_PLAN_ID_BASIC, etc.). These plans are created once in the MP dashboard and referenced by ID.
Critical rules
- Never store raw card data. Use MercadoPago Secure Fields for tokenization only.
- Always use
status: 'authorized'when creating subscriptions (triggers immediate charge). - Always send
X-Idempotency-Keyon MP API calls to prevent duplicate operations. - Webhooks must return 200 within 22 seconds — MP retries on failure.
- Webhook signature validation is mandatory — use HMAC-SHA256 with
MP_WEBHOOK_SECRET. - Grace period: Cancelled subscriptions keep access until
current_period_end. - Use
mpFetchwrapper for all MP API calls — handles retries (3x for 5xx/429) with exponential backoff. - Use admin Supabase client (service role) for cross-user DB updates in edge functions.
More from duvesalo/app
api-design-principles
Master REST and GraphQL API design principles to build intuitive, scalable, and maintainable APIs that delight developers. Use when designing new APIs, reviewing API specifications, or establishing API design standards.
4pnpm
Node.js package manager with strict dependency resolution. Use when running pnpm specific commands, configuring workspaces, or managing dependencies with catalogs, patches, or overrides.
4pdf
Use this skill whenever the user wants to do anything with PDF files. This includes reading or extracting text/tables from PDFs, combining or merging multiple PDFs into one, splitting PDFs apart, rotating pages, adding watermarks, creating new PDFs, filling PDF forms, encrypting/decrypting PDFs, extracting images, and OCR on scanned PDFs to make them searchable. If the user mentions a .pdf file or asks to produce one, use this skill.
4vercel-react-best-practices
React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.
4typescript-best-practices
Provides TypeScript patterns for type-first development, making illegal states unrepresentable, exhaustive handling, and runtime validation. Must use when reading or writing TypeScript/JavaScript files.
4vitest
Vitest fast unit testing framework powered by Vite with Jest-compatible API. Use when writing tests, mocking, configuring coverage, or working with test filtering and fixtures.
4