Shopify Payments App Integration

Piaxis can act as the payment provider at a Shopify store’s checkout: buyers pay with mobile money (MTN or Airtel in v1) and the money lands either directly in the merchant’s Piaxis store settlement or inside a Piaxis escrow — the business chooses at install time.

Status

The integration ships disabled by default. It activates only when both are true:

  1. The platform operator has set SHOPIFY_INTEGRATION_ENABLED and the Shopify app credentials (see Configuration).

  2. The merchant’s store holds the shopify_payments entitlement, granted by a Piaxis administrator (plan or override). This key is deliberately not part of the free baseline.

Until Shopify Partner review of the Piaxis Payments App completes, treat every environment as sandbox.

How It Works

Shopify checkout
     │  payment session webhook (HMAC-verified)
     ▼
Piaxis records the session and answers with a hosted payment URL
     │  buyer is redirected
     ▼
gopiaxis.com/pay/shopify/{session}   ← buyer picks MTN/Airtel, enters phone
     │  mobile-money push on existing Piaxis rails
     ▼
money is FINAL (settled directly, or held in escrow)
     │  paymentSessionResolve via Shopify GraphQL
     ▼
Shopify completes the order; buyer returns to the shop

Key guarantees:

  • A Shopify order is never confirmed before the buyer’s money is final.

  • Webhook replays are idempotent by Shopify session id — money can never be collected twice for one session.

  • If the buyer closes the page mid-payment, a reconciliation task finishes the session (resolve or reject) on its own.

  • Refunds can never exceed what was actually collected; an over-amount refund session is rejected with a typed reason.

Connecting a Shop (merchant flow)

The store owner starts the install from Piaxis (session-authenticated):

POST /api/platforms/shopify/connect
Content-Type: application/json

{
  "store_id": "<piaxis store uuid>",
  "shop_domain": "my-shop.myshopify.com",
  "payment_mode": "direct"
}
  • payment_modedirect settles collected money straight to the store’s settlement balance; escrow holds each payment in a Piaxis escrow released through the normal escrow lifecycle (buyer protection). A business may run both models by connecting different shops.

  • The response contains install_url: send the merchant’s browser there. It is Shopify’s own authorize page requesting the minimum Payments App scopes (write_payment_gateways, write_payment_sessions).

  • Shopify redirects back to GET /api/platforms/shopify/oauth/callback; Piaxis verifies the signature and state, exchanges the code, and stores the shop’s offline token encrypted at rest. The plaintext token never reaches a client.

To disconnect:

DELETE /api/platforms/shopify/connect/{shop_domain}

The token reference is dropped immediately; further payment sessions from that shop are refused. Uninstalling the app from the Shopify side has the same effect via the app/uninstalled webhook.

Hosted Payment Page

Buyers land on https://gopiaxis.com/pay/shopify/{session_id}. The page is guest-safe: no Piaxis account, KYC, or PIN prompt — the mobile-money PIN is only ever entered on the buyer’s own phone. It offers exactly the methods the store’s market configuration enables, and it polls session status so the order confirms without any buyer action after approving on the phone.

The page consumes these public endpoints (the unguessable session UUID from the Shopify redirect is the capability):

  • GET /api/platforms/shopify/sessions/{session_id} — status, amount, currency, available methods.

  • POST /api/platforms/shopify/sessions/{session_id}/pay — body {"phone_number": "...", "method": "mtn" | "airtel"}. Single-flight: while a push is pending the endpoint answers 409.

  • POST /api/platforms/shopify/sessions/{session_id}/cancel — allowed only before money moves; rejects the Shopify session as customer-cancelled.

Webhooks (Shopify-facing)

These endpoints exist for Shopify itself, not for merchant integrations. Every call is verified against the raw request body with the app secret (X-Shopify-Hmac-Sha256) before any processing:

  • POST /api/platforms/shopify/webhooks/payment-session

  • POST /api/platforms/shopify/webhooks/refund-session

  • POST /api/platforms/shopify/webhooks/capture-session (v1 declares automatic capture)

  • POST /api/platforms/shopify/webhooks/void-session (cancels sessions whose money is not yet final; settled money requires a refund)

  • POST /api/platforms/shopify/webhooks/events — lifecycle and the mandatory GDPR topics (customers/data_request, customers/redact, shop/redact), all recorded replay-safe.

Configuration

Platform-operator environment variables:

Variable

Purpose

SHOPIFY_INTEGRATION_ENABLED

Master switch. Everything answers 503 until set truthy.

SHOPIFY_API_KEY / SHOPIFY_API_SECRET

The Shopify Partner app credentials. The secret verifies webhook and OAuth signatures — rotating it invalidates in-flight installs.

SHOPIFY_APP_ENVIRONMENT

sandbox (default) or production; stamped on connections.

FERNET_KEY

Already required by the platform; also encrypts shop offline tokens.

SHOPIFY_SHOP_TOKENS

Sandbox-only JSON map of token references for shops connected outside the OAuth flow. Production connections use encrypted refs instead.

Administrator steps (one-time, via the admin console → Store Entitlements):

  1. Create the shopify_payments feature (default state plan_required or disabled).

  2. Enable it for the market (Market availability row, e.g. UG / *).

  3. Grant it to pilot stores via a plan or per-store override.

Existing Integrations Are Unaffected

This surface is additive. No existing Payment API route, SDK method, webhook payload, or escrow behavior changed; the regression suites for the external API and escrow run against every release of this integration.