pay
Send payments to recipients using Pix keys, email, phone, CPF, CNPJ, or IBAN. Processes transactions in cents with multi-provider routing and supports payment memos.
Instructions
Send money to a destination via the best available provider. Amount in cents.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| amount | Yes | Amount in cents. Example: 5000 = R$50.00 | |
| currency | No | ISO 4217 currency code | BRL |
| destination | Yes | Recipient: Pix key, email, phone, CPF, CNPJ, IBAN | |
| destination_type | No | Type hint: EMAIL, PHONE, CPF, CNPJ, RANDOM, IBAN | |
| note | No | Payment memo | |
| provider | No | Force a specific provider |
Implementation Reference
- src/index.ts:184-271 (handler)Registration and handling logic for the "pay" MCP tool.
server.tool( "pay", "Send money to a destination via the best available provider. Amount in cents.", PaySchema.shape, async (params) => { const check = guardrails.checkPay(params); if (!check.allowed) { guardrails.audit({ timestamp: new Date().toISOString(), type: "payment", action: "pay", tool: "pay", amount: params.amount, currency: params.currency, destination: params.destination, status: "blocked", reason: check.reason, }); return textResult(`Payment blocked: ${check.reason}`); } // Note: recordSpend is NOT called here because no money moves yet. // Spend is recorded when the user confirms and pay is called again, // reaching the try block below which calls recordSpend(). if (check.needs_confirmation) { guardrails.audit({ timestamp: new Date().toISOString(), type: "payment", action: "pay", tool: "pay", amount: params.amount, currency: params.currency, destination: params.destination, status: "pending_confirmation", reason: check.reason, }); const formatted = (params.amount / 100).toFixed(2); return textResult( `Confirmation required\n\n` + ` Amount: ${params.currency} ${formatted}\n` + ` To: ${params.destination}\n` + ` Reason: ${check.reason}\n\n` + `Please confirm with the user before proceeding. ` + `Call pay again with the same parameters after approval.` ); } try { const provider = pickProvider(params.currency, undefined, params.provider); const result = await provider.pay(params); guardrails.recordSpend(params.amount); guardrails.audit({ timestamp: new Date().toISOString(), type: "payment", action: "pay", tool: "pay", amount: params.amount, currency: params.currency, provider: provider.name, destination: params.destination, status: "executed", }); return jsonResult({ success: true, message: `Sent ${params.currency} ${(params.amount / 100).toFixed(2)} to ${params.destination} via ${provider.name}`, ...result, }); } catch (err) { guardrails.audit({ timestamp: new Date().toISOString(), type: "payment", action: "pay", tool: "pay", amount: params.amount, currency: params.currency, destination: params.destination, status: "failed", reason: formatError(err), }); return textResult(`Payment failed: ${formatError(err)}`); } } ); - src/providers/woovi.ts:126-150 (handler)Implementation of the "pay" method within the Woovi provider.
async pay(req: PayRequest): Promise<PayResult> { const correlationID = req.correlation_id ?? `junto-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; const raw = await this.request("POST", "/payment", { value: req.amount, destinationAlias: req.destination, destinationAliasType: this.mapDestinationType(req.destination_type), correlationID, comment: req.note ?? "Payment via Junto", }); const data = WooviPaymentResponse.parse(raw); return { id: correlationID, status: (data.payment.status as PayResult["status"]) ?? "CREATED", provider: this.name, amount: req.amount, currency: "BRL", destination: req.destination, timestamp: new Date().toISOString(), metadata: { woovi_response: data.payment }, }; }