# Veracity Pay – Stripe flavour in Next.js

Base URL `https://pay.veracity.se/v1`, auth `Authorization: Bearer $VERACITY_PAY_SECRET_KEY`. Bodies are JSON or Stripe form encoding. Objects, ids (cs_test_, pi_test_, cus_, sub_, in_, evt_) and list pagination match Stripe.

## 1. Environment
```
VERACITY_PAY_BASE_URL=https://pay.veracity.se
VERACITY_PAY_SECRET_KEY=vp_sk_test_…            # from the registration e-mail
NEXT_PUBLIC_VERACITY_PAY_PUBLISHABLE_KEY=vp_pk_test_…
VERACITY_PAY_WEBHOOK_SECRET=whsec_…
```

## 2. Client
Download the dependency-free client: `curl -s https://pay.veracity.se/sdk.ts -o lib/veracity-pay.ts` (createVeracityPay + constructEvent). Stripe's own SDK also works for the common calls (point it at the base URL).

## 3. Create a checkout (subscription in SEK)
```ts
// app/api/billing/checkout/route.ts
import { createVeracityPay } from "@/lib/veracity-pay";
const vp = createVeracityPay({ secretKey: process.env.VERACITY_PAY_SECRET_KEY!, baseUrl: process.env.VERACITY_PAY_BASE_URL! });

export async function POST(req: Request) {
  const { email, accountId } = await req.json();
  const session = await vp.checkout.sessions.create({
    mode: "subscription",                                   // or "payment"
    line_items: [{ price_data: { currency: "sek", unit_amount: 9900, product_data: { name: "Plan" }, recurring: { interval: "month" } }, quantity: 1 }],
    customer_email: email,
    client_reference_id: accountId,
    subscription_data: { metadata: { account_id: accountId } },
    success_url: `${process.env.APP_URL}/billing/done?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `${process.env.APP_URL}/billing`,
  }, { idempotencyKey: `checkout-${accountId}` });
  return Response.json({ url: session.url });
}
```
Create reusable prices with `POST /v1/prices` (`recurring[interval]=month|year`, `lookup_key`) and pass `{ price: "price_…" }` instead of price_data.

## 4. Webhooks
```ts
// app/api/webhooks/veracity-pay/route.ts
import { constructEvent } from "@/lib/veracity-pay";
export async function POST(req: Request) {
  const raw = await req.text();
  const event = await constructEvent(raw, req.headers.get("veracity-signature"), process.env.VERACITY_PAY_WEBHOOK_SECRET!);
  switch (event.type) {
    case "checkout.session.completed":     // link customer + subscription to your account (client_reference_id)
    case "customer.subscription.created":
    case "customer.subscription.updated":  // status active | past_due | canceled, current_period_end
    case "customer.subscription.deleted":
    case "invoice.paid":
    case "invoice.payment_failed":
      break;
  }
  return Response.json({ received: true });
}
```
Point the endpoint at your app once: `curl https://pay.veracity.se/v1/webhook_endpoints/{we_id} -u $VERACITY_PAY_SECRET_KEY: -d url=https://YOUR-APP/api/webhooks/veracity-pay` (the id is in the registration e-mail). On localhost: `curl -s https://pay.veracity.se/listen.mjs -o veracity-listen.mjs && node veracity-listen.mjs http://localhost:3000/api/webhooks/veracity-pay`.

## 5. Cancel a subscription
- At period end: `POST /v1/subscriptions/{id}` with `cancel_at_period_end=true`.
- Now: `DELETE /v1/subscriptions/{id}`. Both end with `customer.subscription.deleted`.

### Test outcomes

- Hosted checkout: a "Test mode" panel picks approve, decline (with a reason), 3-D Secure, pending, timeout or provider error.
- Magic cards: `4242424242424242` Visa – succeeds; `5555555555554444` Mastercard – succeeds; `4000000000000002` Declined: card declined; `4000000000009995` Declined: insufficient funds; `4000000000000069` Declined: expired card; `4100000000000019` Declined: suspected fraud; `4000000000003220` Requires 3-D Secure authentication; `4000000000000150` Pending, succeeds after 5 s; `4000000000000168` Pending, fails after 5 s; `4000000000000119` Provider error (500); `4000000000000184` Timeout – the session expires; `4000000000000341` Succeeds, but renewals fail.
- Magic amounts (total ends in these öre/cents, at least 1.00): `.01` Declined: card declined; `.02` Declined: insufficient funds; `.03` Declined: expired card; `.04` Declined: suspected fraud; `.05` Declined: credit check failed (invoice); `.06` Requires an extra authentication step; `.07` Pending, succeeds after 5 s; `.08` Pending, fails after 5 s; `.09` Timeout – the session expires.
- `metadata[veracity_test_outcome]` on a Checkout Session or PaymentIntent: `approve`, `decline_card_declined`, `decline_insufficient_funds`, `decline_expired_card`, `decline_fraudulent`, `decline_credit_rejected`, `challenge`, `pending_succeed`, `pending_fail`, `timeout`, `provider_error` (`metadata[veracity_test_delay]` seconds for pending/timeout).
- Without a browser: `POST /v1/test_helpers/checkout/sessions/{id}/complete` with `outcome` (MCP tool `complete_checkout`).
- Next renewal of a subscription: `POST /v1/test_helpers/subscriptions/{id}/next_charge_outcome` with `outcome` = approve | insufficient_funds | restricted_card | timeout (MCP tool `set_next_charge_outcome`).
- Time: `POST /v1/test_helpers/advance_clock` with `months` / `days` (MCP tool `advance_clock`); renewals are charged without a code and send invoice.paid or invoice.payment_failed.