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. .. contents:: Table of Contents :depth: 3 :local: 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 ------------ .. code-block:: text 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): .. code-block:: http POST /api/platforms/shopify/connect Content-Type: application/json { "store_id": "", "shop_domain": "my-shop.myshopify.com", "payment_mode": "direct" } - ``payment_mode`` — ``direct`` 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: .. code-block:: http 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: .. list-table:: :header-rows: 1 * - 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.