# PayLynks agent skill

Instructions for AI coding agents adding PayLynks payments to an app. Also served at https://paylynks.io/SKILL.md.

## Setup

1. Run `npx paylynks init` in the project root. It writes `PAYLYNKS_SECRET_KEY` to `.env.local` and, for Next.js, adds:
   - `app/api/paylynks/checkout/route.ts` — creates a payment intent on the server
   - `components/PayLynksCheckout.tsx` — mounts the secure checkout
   - `app/checkout/page.tsx` — a demo page
   - `app/api/paylynks/webhook/route.ts` — verifies and handles webhooks
2. Non-Next.js apps: call the MCP tool `get_snippet` (kinds: `react`, `supabase`, `html`, `node`, `webhook`) or read https://paylynks.io/docs/checkout.md.

## Do

- Create payment intents **on the server** with the secret key: `POST https://paylynks.io/api/v1/payment_intents` with `Authorization: Bearer $PAYLYNKS_SECRET_KEY`.
- Look prices up on the server from a product id. The browser sends `{"product": "pro-plan"}`, never an amount.
- Send only the `client_secret` to the browser and mount checkout with `PayLynks().mountCheckout(el, { clientSecret })` from `https://paylynks.io/js/v1.js`.
- Use integer cents everywhere: `2500` is $25.00.
- Send an `Idempotency-Key` header on POSTs (a UUID or your order id).
- Fulfil orders from the `payment_intent.succeeded` webhook, and verify the `PayLynks-Signature` header against the **raw** body first. Handle duplicates.
- Treat `processing` as "not paid yet": bank (ACH) payments stay `processing` for up to 4 business days before `succeeded` or `payment_failed`.
- Send `PayLynks-Version: 2026-10-21` so later API changes don't change your integration.
- For donations, create a donation page (`POST /api/v1/donation_pages` or the MCP tool `create_donation_page`) and link to its `url` instead of building a giving form.
- To let customers manage cards and subscriptions, redirect them to a portal link from `POST /api/v1/customers/:id/portal_sessions`.
- Keep `.env.local` out of git.

## Don't

- Don't put `sk_test_` / `sk_live_` keys in client code, `NEXT_PUBLIC_*` / `VITE_*` variables, or the repo.
- Don't build your own card form or collect card numbers. Checkout's iframe does that (PCI).
- Don't trust `onSuccess` in the browser as proof of payment. Confirm with the webhook or `GET /api/v1/payment_intents/:id` (status `succeeded`).
- Don't add a surcharge yourself. PayLynks applies the merchant's surcharge or dual-pricing rules and shows the disclosure.
- Don't invent endpoints. The full list is at https://paylynks.io/docs/api-reference.md.

## Test cards

| Card | Result |
| --- | --- |
| 4242 4242 4242 4242 | Visa credit, succeeds (surcharge applies if the merchant surcharges) |
| 4000 0566 5566 5556 | Visa debit, succeeds, never surcharged |
| 4000 0000 0000 0002 | Declined |

Any future expiry, any CVC, any ZIP.

## Common errors

| Message | Fix |
| --- | --- |
| `Provide an API key as Authorization: Bearer <key>` | Missing or malformed header; check the env var is loaded on the server. |
| `This endpoint requires a secret key.` | You used a `pk_` key on a server endpoint. |
| `Invalid client_secret` | Pass the whole `pi_…_secret_…` value from the intent you just created. |
| `This payment_intent is succeeded and cannot be confirmed.` | Create a new intent for each checkout attempt. |
| `The total is now X (was Y)` | You sent `expected_total` and pricing changed (often credit vs debit); show the new total and retry. |
| `Idempotency-Key reused with a different request` | Use a new key for a different request. |
| `Bank payments need the payer's authorization.` | Confirming a bank payment from your server: pass `mandate: {ip_address, user_agent}` from the payer's request. |
| `Unknown PayLynks-Version` | Use a version listed at https://paylynks.io/docs/security.md. |
