docs: add NowPayments integration plan (v1 top-ups) - #376
Conversation
Captures the locked-in v1 scope (crypto top-ups alongside Stripe), DB provider column strategy, IPN webhook handling, and deferred v2 subscription work.
|
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Code Review
This pull request introduces a detailed integration plan for adding NowPayments as a crypto payment provider, covering database schema updates, webhook handling, and UI changes. The feedback focuses on technical implementation details necessary for a robust system, including ensuring database atomicity for credit grants, strictly enforcing idempotency in webhooks to prevent duplicate transactions, and resolving logic inconsistencies regarding underpayments. Additionally, the reviewer pointed out technical requirements for HMAC signature verification, such as specific JSON serialization rules and the correct usage of Buffers with timingSafeEqual, while also noting the need to explicitly link the currency whitelist to environment variables and handle credit decrements during refunds.
| - `finished` → atomically look up the pending row by `(provider='nowpayments', | ||
| reference_id=order_id)`, set `amount = credits` and | ||
| `metadata.status = 'finished'`, then call `increment_user_credits` RPC. |
There was a problem hiding this comment.
| - `finished` → atomically look up the pending row by `(provider='nowpayments', | ||
| reference_id=order_id)`, set `amount = credits` and | ||
| `metadata.status = 'finished'`, then call `increment_user_credits` RPC. |
There was a problem hiding this comment.
The update to the credit_transactions table and the call to the increment_user_credits RPC should be performed within a single database transaction. This ensures that credits are not granted without a corresponding record update, and vice versa, maintaining data consistency in case of partial failures.
| - `finished` → atomically look up the pending row by `(provider='nowpayments', | ||
| reference_id=order_id)`, set `amount = credits` and | ||
| `metadata.status = 'finished'`, then call `increment_user_credits` RPC. | ||
| - `partially_paid` → update metadata, Sentry breadcrumb, no credit grant. |
| - Recursively sort all object keys (NowPayments quirk — applies to nested | ||
| objects and arrays-of-objects). | ||
| - `crypto.createHmac('sha512', IPN_SECRET).update(sortedJson).digest('hex')`. | ||
| - Compare to `x-nowpayments-sig` via `crypto.timingSafeEqual`. |
| - **Refunds.** NowPayments refunds are manual. If we see `payment_status: | ||
| refunded` after we've credited, we need to insert a compensating | ||
| `refund`-type row. The enum already supports `refund` — handler stub is |
| - Recursively sort all object keys (NowPayments quirk — applies to nested | ||
| objects and arrays-of-objects). | ||
| - `crypto.createHmac('sha512', IPN_SECRET).update(sortedJson).digest('hex')`. |
| - `pay_currency` whitelist (optional, hosted page lets user pick from | ||
| enabled coins) |
There was a problem hiding this comment.
Pull request overview
Adds a design/specification document describing how to integrate NowPayments as a second payment provider alongside Stripe, with v1 focused on one-time crypto top-ups and v2 deferring crypto “subscriptions”.
Changes:
- Introduces a detailed NowPayments integration plan (architecture, DB changes, webhook security, idempotency, UI changes, env vars, testing, rollout order).
- Documents key behavioral differences vs Stripe (hosted invoices, IPN webhook signature scheme, payment status state machine).
- Captures “locked-in” product/UX decisions for v1 and explicitly defers v2 items.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| - **DB:** `credit_transactions` (enum `purchase|usage|freemium|topup|refund`, | ||
| plus `reference_id`, `subscription_id`, `metadata JSONB`); `profiles.stripe_id` | ||
| links the Supabase user to a Stripe customer. |
| - Drop existing `credit_transactions_reference_id_idx`. | ||
| - Add composite uniqueness: | ||
| `UNIQUE (provider, reference_id) WHERE reference_id IS NOT NULL`. | ||
| - Backfill not needed (default takes care of existing rows). |
| - Generate `order_id = "topup_${userId}_${nanoid()}"`. | ||
| - Insert a *pending* row in `credit_transactions`: | ||
| `{ provider: 'nowpayments', reference_id: orderId, amount: 0, | ||
| type: 'topup', metadata: { status: 'pending', packageId, credits, | ||
| dollarAmount } }`. |
Summary
Add comprehensive integration plan for NowPayments (crypto payments) as a second payment provider alongside Stripe. This document outlines the architecture, locked-in decisions, and implementation roadmap for v1 (one-time top-ups) and v2 (crypto subscriptions).
Changes
docs/specs/nowpayments-integration.mdwith complete integration specification including:How to test
This is a specification document; no code changes or testing required. The plan is ready for implementation once approved.
Scope
Checklist
Notes for reviewers
This is a locked-in design document (decisions finalized 2026-05-13). It serves as the blueprint for the actual implementation PR. Key points for review:
(provider, reference_id)at DB level + pending row inserted before redirecthttps://claude.ai/code/session_01Jkmvx86vnQER8fwzR1rym7