Create a subscription plan
deonpay_create_subscriptionSet up a recurring charge by creating a subscription plan. Specify name, amount in centavos, and billing interval; optionally add trial days and customer controls.
Instructions
Create a new subscription PLAN (template). Use this when the user wants to set up a recurring charge: 'create a $299 monthly plan called Premium'. Required: name, amount (centavos, min 100 = $1 MXN), interval_type. Important: in production the merchant must have an On-Demand NetPay key configured for the active environment, otherwise this returns ondemand_key_missing. trial_days > 0 enables a free trial period (charges a $10 MXN card-validation tx on the first day to verify the card). Optional flags allow_customer_cancel/pause/advance_payments control the customer self-service portal.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | Plan name shown to subscribers. | |
| amount | Yes | Amount per cycle in CENTAVOS (min 100 = $1 MXN). | |
| interval_type | Yes | Billing cadence. | |
| description | No | ||
| currency | No | ISO 3-letter code, default MXN. | |
| interval_count | No | Multiplier of interval_type, e.g. interval_type='monthly' + interval_count=3 -> every 3 months. | |
| max_charges | No | Hard cap on total charges per subscriber. Null/omitted = unlimited. | |
| trial_days | No | Free trial length in days. 0 = no trial. | |
| product_id | No | Optional product to associate (must be active and not stock-tracked). | |
| contract_terms | No | Terms shown at checkout. | |
| allow_customer_cancel | No | Default true. | |
| allow_customer_pause | No | Default false. | |
| allow_advance_payments | No | Default false. | |
| max_advance_payments | No | ||
| portal_enabled | No | Default true. | |
| metadata | No |
Implementation Reference
- src/tools/subscriptions.ts:110-112 (handler)The actual execution logic: posts the validated args as JSON body to POST /api/v1/subscriptions via the DeonPay HTTP client, wrapped in safeHandler to catch errors.
safeHandler(async (args) => { return client.post("/subscriptions", compact(args)); }), - src/tools/subscriptions.ts:70-108 (schema)Zod-based input validation schema defining all fields for creating a subscription plan: name (required), amount (required, centavos), interval_type (required), plus optionals like description, currency, interval_count, max_charges, trial_days, product_id, contract_terms, customer portal flags, and metadata.
inputSchema: { name: z.string().min(1).max(255).describe("Plan name shown to subscribers."), amount: z.number().int().min(100).describe("Amount per cycle in CENTAVOS (min 100 = $1 MXN)."), interval_type: IntervalSchema.describe("Billing cadence."), description: z.string().max(1000).optional(), currency: z.string().length(3).optional().describe("ISO 3-letter code, default MXN."), interval_count: z .number() .int() .min(1) .max(365) .optional() .describe("Multiplier of interval_type, e.g. interval_type='monthly' + interval_count=3 -> every 3 months."), max_charges: z .number() .int() .min(1) .optional() .describe("Hard cap on total charges per subscriber. Null/omitted = unlimited."), trial_days: z .number() .int() .min(0) .max(365) .optional() .describe("Free trial length in days. 0 = no trial."), product_id: z .string() .uuid() .optional() .describe("Optional product to associate (must be active and not stock-tracked)."), contract_terms: z.string().max(20000).optional().describe("Terms shown at checkout."), allow_customer_cancel: z.boolean().optional().describe("Default true."), allow_customer_pause: z.boolean().optional().describe("Default false."), allow_advance_payments: z.boolean().optional().describe("Default false."), max_advance_payments: z.number().int().min(1).optional(), portal_enabled: z.boolean().optional().describe("Default true."), metadata: z.record(z.unknown()).optional(), }, - src/tools/subscriptions.ts:64-113 (registration)Registration of the tool with the MCP server via server.registerTool, called within the registerSubscriptionTools function.
server.registerTool( "deonpay_create_subscription", { title: "Create a subscription plan", description: "Create a new subscription PLAN (template). Use this when the user wants to set up a recurring charge: 'create a $299 monthly plan called Premium'. Required: name, amount (centavos, min 100 = $1 MXN), interval_type. Important: in production the merchant must have an On-Demand NetPay key configured for the active environment, otherwise this returns ondemand_key_missing. trial_days > 0 enables a free trial period (charges a $10 MXN card-validation tx on the first day to verify the card). Optional flags allow_customer_cancel/pause/advance_payments control the customer self-service portal.", inputSchema: { name: z.string().min(1).max(255).describe("Plan name shown to subscribers."), amount: z.number().int().min(100).describe("Amount per cycle in CENTAVOS (min 100 = $1 MXN)."), interval_type: IntervalSchema.describe("Billing cadence."), description: z.string().max(1000).optional(), currency: z.string().length(3).optional().describe("ISO 3-letter code, default MXN."), interval_count: z .number() .int() .min(1) .max(365) .optional() .describe("Multiplier of interval_type, e.g. interval_type='monthly' + interval_count=3 -> every 3 months."), max_charges: z .number() .int() .min(1) .optional() .describe("Hard cap on total charges per subscriber. Null/omitted = unlimited."), trial_days: z .number() .int() .min(0) .max(365) .optional() .describe("Free trial length in days. 0 = no trial."), product_id: z .string() .uuid() .optional() .describe("Optional product to associate (must be active and not stock-tracked)."), contract_terms: z.string().max(20000).optional().describe("Terms shown at checkout."), allow_customer_cancel: z.boolean().optional().describe("Default true."), allow_customer_pause: z.boolean().optional().describe("Default false."), allow_advance_payments: z.boolean().optional().describe("Default false."), max_advance_payments: z.number().int().min(1).optional(), portal_enabled: z.boolean().optional().describe("Default true."), metadata: z.record(z.unknown()).optional(), }, }, safeHandler(async (args) => { return client.post("/subscriptions", compact(args)); }), ); - src/tools/_helpers.ts:57-68 (helper)safeHandler wraps the handler fn with try/catch, converting thrown errors into structured MCP error results so the LLM sees a clear failure message.
export function safeHandler<TArgs>( fn: (args: TArgs) => Promise<unknown>, ): (args: TArgs) => Promise<CallToolResult> { return async (args: TArgs) => { try { const value = await fn(args); return jsonResult(value); } catch (err) { return errorResult(err); } }; } - src/tools/_helpers.ts:83-91 (helper)compact() strips undefined/null/empty-string entries from the args object before POSTing, ensuring server-side defaults aren't overridden.
export function compact<T extends Record<string, unknown>>(obj: T): Partial<T> { const out: Record<string, unknown> = {}; for (const [key, value] of Object.entries(obj)) { if (value === undefined || value === null) continue; if (typeof value === "string" && value.trim() === "") continue; out[key] = value; } return out as Partial<T>; }