> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vibestrap.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# NOWPayments

> Crypto-only hosted invoice gateway — accept BTC/ETH/USDC and 300+ coins with no merchant KYC.

NOWPayments is a crypto-only hosted invoice gateway. Use it when you want to
accept Bitcoin, Ethereum, USDC/USDT and 300+ other coins without going through
a Coinbase-style KYC. The merchant flow is email-only signup; the buyer picks
their coin on a hosted page and you receive crypto in your wallet.

Pick NOWPayments if you're an indie hacker who wants to add a crypto checkout
option alongside (or in place of) a card processor — and don't need
auto-debit subscriptions.

<Note>
  **No subscriptions.** NOWPayments has a "recurring billing" feature, but it's
  just an email-resend of new invoices each cycle — not auto-debit, because
  on-chain crypto wallets can't pre-authorize recurring withdrawals. The
  provider in Vibestrap **throws on `type: 'subscription'`** to fail loudly
  rather than silently book bad UX. Use NOWPayments for one-time / lifetime /
  credit-pack purchases. Pair with Stripe (USDC) or Helio if you need true
  crypto subscriptions.

  **No customer portal.** `createPortalLink` throws — there's no NOWPayments
  equivalent of Stripe's billing portal.
</Note>

## Prerequisites

* A NOWPayments account at [nowpayments.io](https://nowpayments.io). **No
  KYC** required for basic merchant signup — just an email.
* A receiving wallet address registered in the NOWPayments dashboard
  (**Settings → Payment settings → Outcome wallet**). This is where your
  crypto lands. Use a non-custodial wallet you control (Ledger / Trezor / a
  trusted CEX deposit address).
* An IPN secret generated in **Settings → Store settings → IPN Secret**.
* Postgres up, schema pushed.

## 1. Set the active provider

In `src/config/site.ts`:

```ts theme={null}
payment: {
  provider: 'nowpayments' as 'stripe' | 'creem' | 'nowpayments',
  currency: 'usd',
},
```

`currency` stays `'usd'` — you price in USD on your end, and NOWPayments
quotes the equivalent crypto amount at checkout time.

## 2. Set env vars

In `.env.local` (variable names match `src/env.ts`):

```bash theme={null}
NOWPAYMENTS_API_KEY=YOUR_API_KEY              # Settings → Store settings → API key
NOWPAYMENTS_IPN_SECRET=YOUR_IPN_SECRET        # Settings → Store settings → IPN Secret

# Vibestrap product prices in USD (string form — "49" means $49)
NOWPAYMENTS_PRICE_VIBESTRAP_PROMO=49
NOWPAYMENTS_PRICE_VIBESTRAP_STANDARD=99
```

<Note>
  NOWPayments has no concept of a "product" or "price ID". Other providers'
  `PRICE_*` env vars hold a Stripe `price_xxx` / Creem product id / etc.;
  NOWPayments' just hold the **dollar amount as a string**. The provider parses
  it to a number and POSTs it as `price_amount` directly.
</Note>

## 3. Configure the IPN webhook

NOWPayments calls these "IPN" (Instant Payment Notifications). In **Settings
→ IPN settings**:

* **IPN callback URL**: `https://your-domain.com/api/webhooks/nowpayments`

Vibestrap also sends `ipn_callback_url` per-invoice so it works even if the
dashboard global is unset — but setting both is harmless and lets failed
invoices retry against the right host.

## How the flow works

1. User clicks "Pay with crypto" → server action calls
   `paymentManager.createCheckout({ priceId: '49', type: 'one_time', ... })`.
2. The provider POSTs to `https://api.nowpayments.io/v1/invoice` with
   `price_amount: 49`, `price_currency: 'usd'`, an `order_id` packed as
   `vbs|<userId>|<scene>|<type>` so we can recover context from the IPN, and
   `ipn_callback_url` set to your `/api/webhooks/nowpayments` route.
3. NOWPayments returns a hosted invoice URL like
   `https://nowpayments.io/payment?iid=4514933743`. We redirect the user
   there.
4. The user picks a coin (BTC / ETH / USDC / …), sends the funds.
5. NOWPayments emits multiple IPNs as the payment progresses:
   `waiting → confirming → confirmed → sending → finished`. The provider
   only treats `finished` as paid — the rest are normalized to `unknown`
   and ignored. Other terminal states (`failed`, `expired`, `partially_paid`,
   `refunded`) are also `unknown` from the handler's perspective — those need
   manual reconciliation in the NOWPayments dashboard.
6. On `finished`, the standard `processNormalizedEvent` flow runs: payment
   row inserted (idempotent on `payment.invoiceId`, which we set to the
   NOWPayments `payment_id`), credits granted, license issued if applicable.

## Webhook signature verification

NOWPayments signs the IPN body with **HMAC-SHA512** of the
**alphabetically-sorted JSON serialization** of the body, using your IPN
secret. The signature arrives in the `x-nowpayments-sig` header.

The verification is implemented in `verifyNowpaymentsWebhook` and matches
the algorithm shown in the official Node.js sample
(`JSON.stringify(params, Object.keys(params).sort())`) and the official PHP
WooCommerce plugin's `check_ipn_request_is_valid()` (ksort + hash\_hmac).
Tests cover the round-trip plus tampering, malformed JSON, and wrong-length
signatures.

## Verify it works

1. Use NOWPayments' **sandbox** at [sandbox.nowpayments.io](https://sandbox.nowpayments.io)
   for test transactions (separate API key + IPN secret).
2. Expose `localhost:3000` via cloudflared / ngrok, set the resulting URL as
   your IPN callback in the sandbox dashboard.
3. `pnpm dev`, open `/pricing`, click checkout — NOWPayments hosted invoice
   page opens.
4. Pay with the sandbox testnet faucet they provide.
5. Watch the IPNs arrive — you'll see several with `payment_status` going
   from `waiting` to `finished`.
6. Check Postgres after `finished`:
   ```sql theme={null}
   select id, provider, scene, status, amount, currency, invoice_id
   from payment
   where provider = 'nowpayments'
   order by created_at desc limit 1;
   ```

## Common pitfalls

* **`is_fee_paid_by_user: false`** — Vibestrap's provider always sets this so
  you eat the (small) network fee instead of surprising the buyer with a
  larger invoice. Toggle in `src/payment/provider/nowpayments.ts` if you'd
  rather pass it through.
* **Sandbox vs production keys are completely separate** — including the IPN
  secret. Mixing them silently fails signature verification.
* **`partially_paid` requires manual reconciliation** — if a buyer underpays
  (sent the wrong amount, or the price moved during the confirmation window),
  NOWPayments emits `partially_paid` and holds the funds. The provider
  ignores it; you'll need to refund or top-up via the dashboard.
* **Refunds are out-of-band** — there's no Stripe-style "create refund" API;
  you initiate refunds in the NOWPayments dashboard. The handler doesn't
  reverse credit grants automatically. If you need that, listen to the
  raw `refunded` IPN status and call `refundCredits()` yourself.
* **Order ID format is load-bearing** — we encode `vbs|<userId>|<scene>|<type>`
  so the IPN handler can recover context (NOWPayments has no metadata field).
  If you change the prefix or delimiter, update `parseOrderId` in
  `src/payment/provider/nowpayments.ts` to match.
* **Price is fixed at invoice creation** — if BTC moves 5% between invoice
  creation and the buyer paying, NOWPayments locks in the original quote.
  Buyers occasionally complain they "overpaid" or "underpaid" by a few
  cents; this is normal crypto-payment behavior, not a bug in Vibestrap.
* **No customer portal** — buyers can't self-serve refunds, billing history,
  or payment method updates. Build your own `/settings/billing` page that
  reads from the `payment` table if you want a UI.

## Switching from Stripe / Creem

Already running another provider? Three steps:

1. Flip `siteConfig.payment.provider` to `'nowpayments'`.
2. Set the four `NOWPAYMENTS_*` env vars.
3. Configure the IPN URL in the NOWPayments dashboard.

In-flight Stripe / Creem subscriptions keep working until they cancel
naturally — Vibestrap dispatches webhooks based on `event.provider`, and the
shared handler is idempotent, so old provider events still hit the same
`payment` table without conflict.

## Official docs

* NOWPayments: [nowpayments.io](https://nowpayments.io)
* API reference: [documenter.getpostman.com/view/7907941/S1a32n38](https://documenter.getpostman.com/view/7907941/S1a32n38)
* IPN setup guide: [nowpayments.io/help/ipn-callbacks](https://nowpayments.io/help/ipn-callbacks)
* Sandbox: [sandbox.nowpayments.io](https://sandbox.nowpayments.io)
* Official Node SDK: [github.com/NowPaymentsIO/nowpayments-api-js](https://github.com/NowPaymentsIO/nowpayments-api-js)
