Table of Contents
Installation
The fetch handlers live at the @solvapay/server/fetch subpath, consumed through Deno’s npm resolution. No npm install is needed — instead, create an import map.
@solvapay/server/fetch replaces the older standalone @solvapay/supabase / @solvapay/fetch packages. Same handlers, same signatures — now co-located with the *Core primitives they wrap.
Create supabase/functions/deno.json:
{
"imports": {
"@solvapay/server": "npm:@solvapay/server",
"@solvapay/server/": "npm:/@solvapay/server/",
"@solvapay/auth": "npm:@solvapay/auth",
"@solvapay/core": "npm:@solvapay/core"
}
}
The "@solvapay/server/" trailing-slash entry is what unlocks the /fetch subpath import in Deno.
Basic Setup
Set secrets
supabase secrets set SOLVAPAY_SECRET_KEY=sk_sandbox_...
supabase secrets set SOLVAPAY_WEBHOOK_SECRET=whsec_...
The SOLVAPAY_SECRET_KEY is required for all handlers. The webhook secret is only needed if you deploy the webhook function.
Prerequisites
Creating Edge Functions
Each handler is a two-line file. Create one function per endpoint:
// supabase/functions/check-purchase/index.ts
import { checkPurchase } from '@solvapay/server/fetch'
Deno.serve(checkPurchase)
The adapter handles CORS preflight, JSON serialization, error formatting, and auth extraction internally.
Available Handlers
| Function | Method | Handler | Description |
|---|
check-purchase | GET | checkPurchase | Check user’s purchase status |
sync-customer | POST | syncCustomer | Sync/create customer in SolvaPay |
create-payment-intent | POST | createPaymentIntent | Create payment intent for a plan |
process-payment | POST | processPayment | Process confirmed payment |
create-topup-payment-intent | POST | createTopupPaymentIntent | Create credit top-up intent |
customer-balance | GET | customerBalance | Get customer credit balance |
cancel-renewal | POST | cancelRenewal | Cancel subscription renewal |
reactivate-renewal | POST | reactivateRenewal | Reactivate cancelled subscription |
activate-plan | POST | activatePlan | Activate a free/usage plan |
list-plans | GET | listPlans | List available plans |
track-usage | POST | trackUsage | Track usage for metered billing |
create-checkout-session | POST | createCheckoutSession | Create hosted checkout session |
create-customer-session | POST | createCustomerSession | Create customer portal session |
get-merchant | GET | getMerchant | Fetch merchant branding (name, iconUrl, logoUrl, terms, privacy) |
get-payment-method | GET | getPaymentMethod | Preview mirrored card brand / last4 for a customer |
get-product | GET | getProduct | Fetch product + public plans (used by checkout UI) |
All handlers are pure (req: Request) => Promise<Response> and run on any web-standards runtime (Supabase Edge, Deno, Cloudflare Workers, Bun, Next.js Edge, Vercel Edge Functions).
Deploy
Deploy all functions at once:
supabase functions deploy
Or deploy individually:
supabase functions deploy check-purchase
supabase functions deploy create-payment-intent
# ... etc
Webhook Handling
For receiving webhook events, use the solvapayWebhook factory instead of a direct handler. It verifies HMAC signatures automatically:
// supabase/functions/solvapay-webhook/index.ts
import type { WebhookEvent } from '@solvapay/server'
import { solvapayWebhook } from '@solvapay/server/fetch'
Deno.serve(solvapayWebhook({
onEvent: async (event: WebhookEvent) => {
switch (event.type) {
case 'purchase.created':
case 'purchase.updated':
// Grant or update access
break
case 'purchase.cancelled':
case 'purchase.expired':
// Revoke access
break
case 'payment_intent.succeeded':
case 'payment_intent.failed':
// Update payment records; mirrored card brand/last4 is now
// persisted on the Customer and exposed via `get-payment-method`
break
}
},
}))
The factory reads SOLVAPAY_WEBHOOK_SECRET from the environment automatically. You can also pass it explicitly:
solvapayWebhook({
secret: Deno.env.get('SOLVAPAY_WEBHOOK_SECRET')!,
onEvent: async event => { /* ... */ },
})
When to use solvapayWebhook vs verifyWebhook
solvapayWebhook from @solvapay/server/fetch: fetch-runtime convenience wrapper. Handles the full request lifecycle (read body, verify, parse, respond).
verifyWebhook from @solvapay/server: low-level primitive for any runtime. You handle body reading, signature extraction, and response formatting yourself.
CORS Configuration
By default, all handlers respond with Access-Control-Allow-Origin: *. For production, restrict to your app’s origin:
// supabase/functions/check-purchase/index.ts
import { checkPurchase, configureCors } from '@solvapay/server/fetch'
configureCors({
origins: ['https://myapp.com', 'http://localhost:5173'],
})
Deno.serve(checkPurchase)
Call configureCors() before Deno.serve() in each function that needs restricted origins. CORS state is per-isolate, so each function file must configure it independently.
Frontend Integration
Point your React app’s SolvaPayProvider at the Edge Function URLs:
import { SolvaPayProvider } from '@solvapay/react'
import { createSupabaseAuthAdapter } from '@solvapay/react-supabase'
const SUPABASE_URL = 'https://<project-ref>.supabase.co/functions/v1'
function App() {
return (
<SolvaPayProvider
config={{
auth: {
adapter: createSupabaseAuthAdapter(supabase),
},
api: {
checkPurchase: `${SUPABASE_URL}/check-purchase`,
createPayment: `${SUPABASE_URL}/create-payment-intent`,
processPayment: `${SUPABASE_URL}/process-payment`,
createTopupPayment: `${SUPABASE_URL}/create-topup-payment-intent`,
customerBalance: `${SUPABASE_URL}/customer-balance`,
cancelRenewal: `${SUPABASE_URL}/cancel-renewal`,
reactivateRenewal: `${SUPABASE_URL}/reactivate-renewal`,
activatePlan: `${SUPABASE_URL}/activate-plan`,
listPlans: `${SUPABASE_URL}/list-plans`,
getMerchant: `${SUPABASE_URL}/get-merchant`,
getPaymentMethod: `${SUPABASE_URL}/get-payment-method`,
getProduct: `${SUPABASE_URL}/get-product`,
},
}}
>
{/* Your app */}
</SolvaPayProvider>
)
}
The sync-customer, create-checkout-session, create-customer-session, and solvapay-webhook functions are server-side only and are called directly from application code, not through the React provider.
Complete Example
The full reference project is available at examples/supabase-edge in the SDK repository.
supabase/functions/
├── deno.json # npm import map
├── check-purchase/index.ts # 2 lines
├── sync-customer/index.ts # 2 lines
├── create-payment-intent/index.ts # 2 lines
├── process-payment/index.ts # 2 lines
├── create-topup-payment-intent/index.ts # 2 lines
├── customer-balance/index.ts # 2 lines
├── cancel-renewal/index.ts # 2 lines
├── reactivate-renewal/index.ts # 2 lines
├── activate-plan/index.ts # 2 lines
├── list-plans/index.ts # 2 lines
├── track-usage/index.ts # 2 lines
├── create-checkout-session/index.ts # 2 lines
├── create-customer-session/index.ts # 2 lines
├── get-merchant/index.ts # 2 lines
├── get-payment-method/index.ts # 2 lines
├── get-product/index.ts # 2 lines
└── solvapay-webhook/index.ts # ~10 lines
Total backend code: ~50 lines across 17 files.
Best Practices
- Set secrets via Supabase CLI, not in source code or
.env files. Use supabase secrets set for both SOLVAPAY_SECRET_KEY and SOLVAPAY_WEBHOOK_SECRET.
- Restrict CORS origins in production. The default
* is fine for development.
- Process webhooks idempotently. The same event may be delivered more than once.
- Test locally first with
supabase start && supabase functions serve before deploying.
- Deploy all functions at once with
supabase functions deploy to avoid version skew between handlers.
Next Steps