Skip to main content

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

FunctionMethodHandlerDescription
check-purchaseGETcheckPurchaseCheck user’s purchase status
sync-customerPOSTsyncCustomerSync/create customer in SolvaPay
create-payment-intentPOSTcreatePaymentIntentCreate payment intent for a plan
process-paymentPOSTprocessPaymentProcess confirmed payment
create-topup-payment-intentPOSTcreateTopupPaymentIntentCreate credit top-up intent
customer-balanceGETcustomerBalanceGet customer credit balance
cancel-renewalPOSTcancelRenewalCancel subscription renewal
reactivate-renewalPOSTreactivateRenewalReactivate cancelled subscription
activate-planPOSTactivatePlanActivate a free/usage plan
list-plansGETlistPlansList available plans
track-usagePOSTtrackUsageTrack usage for metered billing
create-checkout-sessionPOSTcreateCheckoutSessionCreate hosted checkout session
create-customer-sessionPOSTcreateCustomerSessionCreate customer portal session
get-merchantGETgetMerchantFetch merchant branding (name, iconUrl, logoUrl, terms, privacy)
get-payment-methodGETgetPaymentMethodPreview mirrored card brand / last4 for a customer
get-productGETgetProductFetch 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

  1. 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.
  2. Restrict CORS origins in production. The default * is fine for development.
  3. Process webhooks idempotently. The same event may be delivered more than once.
  4. Test locally first with supabase start && supabase functions serve before deploying.
  5. Deploy all functions at once with supabase functions deploy to avoid version skew between handlers.

Next Steps