Skip to content

Pricing page + working Pro/Lifetime checkout via CoinPay (all coins, PayPal, card) - #642

Merged
ralyodio merged 1 commit into
masterfrom
pro-checkout
Oct 21, 2026
Merged

ralyodio merged 1 commit into
masterfrom
pro-checkout

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Why

Revenue audit 2026-10-21: 1,167 signups in 30 days, zero Pro subscriptions ever. "Upgrade to Pro" linked to /signup, /pricing returned 404, and the only checkout (dashboard/subscription) hardcoded USDC on Polygon.

What

  • /pricing: server-rendered, indexable (canonical, index, follow, JSON-LD offers), Free / Pro ($9/mo, $90/yr) / Lifetime ($100) from src/lib/plans.ts. Added to sitemap and footer.
  • /checkout?plan=monthly|annual|lifetime: signed-out visitors go to /login?redirect=/checkout?plan=... and land back on checkout after signing in (LoginForm and Sign in with CoinPay now honor a same-site redirect).
  • Coin picker lists every coin ugig's CoinPay business accepts, read live from /api/supported-coins (16 today: USDC/USDT on several chains, BTC, BCH, ETH, POL, SOL, DOGE, XRP, ADA, BNB). Plus PayPal or card (PayPal guest checkout on CoinPay's hosted page). No Stripe.
  • POST /api/payments/coinpayportal/create: any live coin or method: "paypal". Prefers CoinPay hosted checkout sessions (POST /api/checkout/sessions) when CoinPay serves them (feature-detected: 404/405/501 means fall back, cached 5 min).
  • Every payment carries metadata {property: "ugig.net", user_id, username, email, ugig_payment_id, type, plan, rail}; PayPal and sessions also send customer_email. Crypto sends idempotency_key.
  • Webhook settlement maps a paid payment to the user by coinpay_payment_id, then metadata.ugig_payment_id (hosted sessions; owner checked), then rebuilds the row from the signed metadata if the local insert was lost (only when the plan price is covered).
  • A coin CoinPay lists but refuses at create (USDC on Base: "Unsupported blockchain for fee estimation: BASE", a CoinPay bug) is hidden for an hour and the buyer is told to pick another.
  • GET /api/payments/coinpayportal/options for API/CLI parity; openapi.json updated.
  • Every Upgrade to Pro link (home, dashboard, gig post limit, subscription page) now goes to /checkout.
  • Fix: addMonths clamps to month end (paying on Oct 31 gave Pro through Dec 1; also a date-dependent test failure today).

Tested

  • vitest run: 307 files / 3007 tests pass; tsc --noEmit clean; eslint clean on touched files; bun run build OK.
  • Local production build against prod env: /pricing 200 with canonical + robots index; /checkout signed out 307 to /login?redirect=%2Fcheckout%3Fplan%3Dannual; signed in, all 16 coins in the picker; created a real unpaid USDC (Polygon) $9 payment (CoinPay hosted page 200) and a real unpaid PayPal $100 Lifetime payment (coinpayportal.com/pay/paypal/... 200) as the riotcoder account.

🤖 Generated with Claude Code

…CoinPay (all coins + PayPal/card)

ugig had 1,167 signups in 30 days and zero Pro purchases: "Upgrade to Pro"
linked to /signup, /pricing was a 404, and the only checkout hardcoded
usdc_pol.

- /pricing: server-rendered, indexable (canonical, robots index, JSON-LD
  offers), plans and prices from src/lib/plans.ts; in sitemap and footer.
- /checkout?plan=monthly|annual|lifetime: signed-out buyers go to
  /login?redirect=... and come back (LoginForm and Sign in with CoinPay now
  honor a same-site redirect). Coin picker lists every coin ugig's CoinPay
  business accepts, live from /supported-coins (16 today), plus PayPal or
  card (PayPal guest checkout through CoinPay; never Stripe).
- POST /api/payments/coinpayportal/create: any live CoinPay coin, or
  method=paypal; prefers CoinPay hosted checkout sessions
  (POST /api/checkout/sessions) when CoinPay serves them, feature-detected
  with a 5 min negative cache. Every payment carries metadata
  {property: ugig.net, user_id, username, email, ugig_payment_id, type,
  plan, rail}; PayPal and sessions also get customer_email. Crypto passes
  idempotency_key and falls back to coinpayportal.com/pay/<id>. A coin
  CoinPay refuses at create (USDC on Base today) is hidden for an hour.
- Webhook settlement finds the row by coinpay_payment_id, then by
  metadata.ugig_payment_id (hosted sessions; owner checked), then rebuilds
  it from signed metadata when the plan price is covered.
- GET /api/payments/coinpayportal/options for API/CLI parity; openapi.json.
- addMonths clamps to month end (Oct 31 + 1 month = Nov 30, not Dec 1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
return;
}
if (data.checkout_url) {
window.location.href = data.checkout_url as string;
// Checkout through CoinPay: any coin it supports, PayPal or card. The
// /checkout page picks the rail; this page only chooses the plan.
const handleCryptoCheckout = (plan: CoinPayPlan) => {
window.location.href = checkoutHref(plan);
Comment thread src/app/pricing/page.tsx
<Header />
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd()) }}
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

57 finding(s)

HIGH/CRITICAL: 1 | MEDIUM: 12 | LOW: 44

Severity Rule Location
HIGH js-ssrf-outbound-request scripts/scan-all-skills.ts:38
MEDIUM js-open-redirect src/app/agent-login/AgentLoginForm.tsx:38
MEDIUM js-unescaped-html-sink src/app/blog/[slug]/page.tsx:80
MEDIUM js-open-redirect src/app/checkout/CheckoutClient.tsx:77
MEDIUM js-open-redirect src/app/dashboard/subscription/page.tsx:101
MEDIUM js-open-redirect src/app/dashboard/subscription/page.tsx:113
MEDIUM js-unescaped-html-sink src/app/page.tsx:70
MEDIUM js-unescaped-html-sink src/app/pricing/page.tsx:84
MEDIUM js-open-redirect src/components/funding/FundingClient.tsx:138
MEDIUM js-open-redirect src/components/gigs/BuyNowButton.tsx:106
MEDIUM js-unescaped-html-sink src/components/layout/PitFooter.tsx:25
MEDIUM js-dynamic-code-execution src/lib/skills/metadata-extract.ts:300
MEDIUM js-dynamic-code-execution src/lib/skills/security-scan.ts:48
LOW secret-generic-credential cli/src/commands/auth.test.ts:66
LOW secret-generic-credential cli/src/commands/auth.test.ts:85
LOW secret-generic-api-key docs/agents/integration-guide.md:893
LOW secret-generic-credential src/app/api/auth/agent-register/route.test.ts:27
LOW secret-generic-credential src/app/api/auth/login/route.test.ts:53
LOW secret-generic-credential src/app/api/auth/login/route.test.ts:68
LOW secret-generic-credential src/app/api/auth/login/route.test.ts:87
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:158
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:180
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:197
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:208
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:247
LOW secret-generic-credential src/app/api/gigs/[id]/buy/route.test.ts:48
LOW secret-generic-credential src/app/api/gigs/[id]/buy/route.test.ts:99
LOW secret-generic-credential src/app/api/gigs/[id]/buy/route.test.ts:158
LOW secret-generic-credential src/app/api/orders/[id]/release/route.test.ts:18
LOW js-dynamic-code-execution src/app/api/skills/[slug]/scan/route.test.ts:212
LOW js-dynamic-code-execution src/app/api/skills/[slug]/scan/route.test.ts:223
LOW js-dynamic-code-execution src/app/api/skills/[slug]/scan/route.test.ts:239
LOW secret-generic-credential src/lib/api.test.ts:126
LOW secret-generic-credential src/lib/api.test.ts:131
LOW js-dynamic-code-execution src/lib/skills/composite-scanner.test.ts:106
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:36
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:44
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:66
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:81
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:94
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:103
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:118
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:144
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:162
LOW js-dynamic-code-execution src/lib/skills/security-scan.test.ts:28
LOW js-dynamic-code-execution src/lib/skills/url-import.test.ts:178
LOW js-dynamic-code-execution src/lib/skills/url-import.test.ts:191
LOW secret-jwt src/lib/supabase/service.test.ts:16
LOW secret-jwt src/lib/supabase/service.test.ts:40
LOW secret-generic-credential src/lib/validations.test.ts:148

…and 7 more. Full results in the Security tab.

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit b0ddab7 into master Oct 21, 2026
7 checks passed
@ralyodio
ralyodio deleted the pro-checkout branch October 21, 2026 12:15
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