Skip to content

Checkout recovery: every CoinPay coin + PayPal, tagged payments, one-click finish - #105

Merged
ralyodio merged 1 commit into
masterfrom
fix/checkout-recovery
Oct 21, 2026
Merged

ralyodio merged 1 commit into
masterfrom
fix/checkout-recovery

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Revenue audit 2026-10-21: 30 number orders since Oct 5, 27 expired unpaid.

  1. 60-minute window: already sent (expires_in_minutes: 60, fix(numbers): 60-minute CoinPay checkout window #94). Confirmed in CoinPay's payments rows: every smshub payment since 2026-10-17 12:30Z has a 60 min window. Also sent on hosted sessions.
  2. All coins + PayPal: the offer is now every coin CoinPay reports active for the business (GET /api/supported-coins, 10 min cache, static fallback). Adds USDC_BASE, BCH, DOGE, XRP, ADA, BNB (and USDT_TRON automatically once CoinPay turns it on). /numbers has two tiles, PayPal first ("No crypto needed"), Crypto with a coin picker. No Stripe.
  3. Metadata: every payment carries { property: "smshub.dev", order_id, user_id, kind, email, customer_email }; the PayPal rail also gets top-level customer_email. The webhook maps by payment id, then by metadata (order_id + user_id) for payments it never saw the id of (session payments), and refuses a user mismatch.
  4. Recovery: POST /api/numbers/orders/:id/reopen reopens an expired unpaid order as a fresh checkout with the same terms, optionally switching to PayPal; returns the open checkout instead of a second one. "Finish your order" banner on /numbers and in the inbox, plus "Pay with PayPal instead". No finish-your-order email: the app has no transactional email system (only GoTrue auth mail), so skipped.
  5. $1-2 single code: skipped. The backend only rents whole Telnyx numbers by the month; there is no one-off code product to price.

Also: prefers CoinPay hosted checkout sessions (POST /api/checkout/sessions) when CoinPay serves them, feature-detected (today it answers its HTML 404 page, so the direct payment is used and the probe backs off 15 min).

Tests: 303 pass (new: chain discovery, metadata, session preference and fallback, metadata-mapped settlement, reopen, recovery helper); tsc, eslint and next build clean.

🤖 Generated with Claude Code

…click finish

- Offer every coin CoinPay has live for the business (GET /api/supported-coins,
  cached 10 min, static fallback): adds USDC_BASE, BCH, DOGE, XRP, ADA, BNB and
  USDT_TRON once CoinPay enables it. PayPal gets its own prominent tile.
- Every payment carries metadata { property: smshub.dev, order_id, user_id,
  kind, email, customer_email }; PayPal also gets customer_email top level.
- Prefer CoinPay hosted checkout sessions (POST /api/checkout/sessions) when
  CoinPay has them, feature-detected, with the direct payment as fallback.
- Webhook maps a payment back by metadata (order_id + user_id) when its id is
  unknown (session payments), and refuses a user mismatch.
- POST /api/numbers/orders/:id/reopen: an expired unpaid order reopens as a
  fresh 60-minute checkout with the same terms, optionally on PayPal. "Finish
  your order" banner on /numbers and the inbox.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
const j = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(j.error || "Could not reopen the checkout");
const pay = safePayUrl(j.order?.pay_url);
window.location.href = pay || "/numbers";
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

12 finding(s)

HIGH/CRITICAL: 2 | MEDIUM: 4 | LOW: 6

Severity Rule Location
HIGH sh-unquoted-expansion-destructive public/install.sh:67
HIGH sh-remote-script-execution public/install.sh:89
MEDIUM sh-remote-script-execution public/install.sh:133
MEDIUM js-open-redirect src/app/numbers/page.tsx:178
MEDIUM js-open-redirect src/components/inbox-client.tsx:35
MEDIUM js-unescaped-html-sink src/components/pit-footer.tsx:17
LOW secret-generic-credential src/lib/providers/__tests__/twilio.test.ts:24
LOW secret-generic-credential src/lib/providers/__tests__/twilio.test.ts:46
LOW secret-generic-credential src/lib/providers/__tests__/twilio.test.ts:60
LOW secret-generic-credential src/lib/providers/__tests__/twilio.test.ts:78
LOW secret-generic-credential supabase/config.toml:287
LOW secret-generic-credential supabase/config.toml:319

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit 5a73322 into master Oct 21, 2026
12 checks passed
@ralyodio
ralyodio deleted the fix/checkout-recovery branch October 21, 2026 12:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants