Skip to content

Payment Flow

Flow Diagram

graph TB
    subgraph Checkout
        A["POST /order/process\nOrderController::store()"] --> B["CartItem validation\nBuyableRule\nPurchaseLimitRule"]
        B --> C["StoreOrderAction\nOrder status: open\nlockForUpdate variations + ckc_codes"]
        C --> D{Payment method}
    end

    subgraph SBPS
        D -->|"credit/docomo/au/softbank/paypay/unionpay"| E["PaymentManager::provider('sbps', method)\nSbpsLinkService\nSHA-1 sps_hashcode"]
        E --> F["POST to SBPS gateway\nShift_JIS encoded\nHTTP redirect"]
        F --> G{SBPS result}
        G -->|success| H["POST /order/finish/{order}\nOrderController::complete()"]
        G -->|failure| I["POST /order/failure/{order}\nOrderController::fail()"]
        H --> J["Order status: success\nCkcCode assigned\nOrderFinished event"]
        I --> K["Order status: failure\nOrderRetry created"]
    end

    subgraph Atone
        D -->|"atone BNPL"| L["AtoneChecksumService\nSHA-256 HMAC\nJS SDK on confirm page"]
        L --> M["Inline fulfilment\nOrderController::store()\nno redirect"]
        M --> N["POST /webhook/atone\nhash_equals Np-Confirmation-Checksum"]
        N --> J
    end

    subgraph Webhook
        P["POST /webhook/sbps\nSbpsPaymentController"] --> Q["idempotency: status === OPEN\nno HMAC verification"]
        Q --> J
    end

    J --> R["SendOrderFinishNotification\nemail to customer (sync)"]

Purpose

Orchestrates the complete purchase-to-fulfilment pipeline including payment gateway integration, CKC serial code assignment, and order status reconciliation via webhooks.

Trigger Points

  • POST /order/process — customer submits order
  • POST /order/finish/{order} — SBPS success redirect
  • POST /order/failure/{order} — SBPS failure redirect
  • POST /webhook/sbps — asynchronous SBPS status update
  • POST /webhook/atone — Atone authorization result webhook

Actors

  • User (authenticated, verified, address complete)
  • PaymentManager — provider factory
  • SBPS payment gateway (external)
  • Atone BNPL platform (external)

Lifecycle Steps

SBPS Path

  1. OrderController::store(): validate cart items, StoreOrderAction creates Order (status open) inside DB transaction with lockForUpdate on product_variations and ckc_codes
  2. PaymentManager::provider('sbps', $method) returns the appropriate SbpsLinkService subclass
  3. SHA-1 sps_hashcode computed from params; Shift_JIS encoding applied to item_name
  4. HTTP POST/redirect to SBPS gateway URL (env-switched prod/test)
  5. On success: POST /order/finish/{order}OrderController::complete()
  6. On failure: POST /order/failure/{order}OrderController::fail(); OrderRetry record created
  7. SBPS also sends asynchronous webhook POST /webhook/sbps — idempotency guard: only processes if Order::status === OPEN

Atone Path

  1. Confirm page renders JS SDK with checksum computed by AtoneChecksumService (SHA-256 HMAC, ksort+flatten of params, separate prod/test key pairs)
  2. Customer completes BNPL inline
  3. OrderController::store() handles Atone inline (no redirect) — fulfilment runs synchronously
  4. Atone sends POST /webhook/atonehash_equals on Np-Confirmation-Checksum header for timing-safe verification

Post-Fulfilment

  1. CkcCode records assigned to OrderDetail (if product has CKC codes)
  2. CkcConnect::attach() called inside DB transaction — failure rolls back entire order
  3. OrderFinished event dispatched → SendOrderFinishNotification runs synchronously (no queue)
  4. Email sent to customer

Laravel Components Involved

Component Path
OrderController app/Http/Controllers/Web/Order/OrderController.php
PaymentManager app/Modules/Payment/PaymentManager.php
AtoneChecksumService app/Modules/Payment/AtoneChecksumService.php
SbpsLinkService app/Modules/Payment/Providers/Sbps/SbpsLinkService.php
SbpsPaymentController app/Http/Controllers/Web/Webhook/SbpsPaymentController.php
AtoneWebhookController app/Http/Controllers/Web/Webhook/AtoneWebhookController.php
StoreOrderAction app/Modules/Order/Actions/StoreOrderAction.php
OrderRetry model app/Models/OrderRetry.php
Webhook routes routes/webhook.php

Data Mutations

Step Table Change
Order creation orders status = open, FK to user
Order detail order_details line items per variation
CKC assignment ckc_codes order_detail_id populated
Fulfilment orders status = success
Failure orders status = failure
Retry order_retries new retry record with token
Stock product_variations stock decremented

State Transitions

open → success (payment confirmed + fulfilment complete)
open → failure (payment rejected)
failure → open (OrderRetry retry attempt)
success → processing (admin marks processing)
processing → shipped (admin imports shipping CSV)
shipped → canceled (admin action)

Failure Paths

Scenario Behaviour
SBPS payment rejected order/failure → Order failure, OrderRetry created with token-gated retry URL
SBPS webhook duplicate Idempotency guard: status === OPEN check prevents double-fulfilment
CkcConnect::attach() failure DB transaction rollback — order NOT completed
Stock exhausted at checkout lockForUpdate + BuyableRule validation prevents oversell
Atone webhook missing checksum hash_equals fails → 400 response
SBPS webhook: no HMAC No inbound signature check — relies on state-based idempotency only

Security Considerations

  • SBPS webhook has no inbound HMAC verification — any party can POST to /webhook/sbps and trigger state checks. Idempotency guard (status === OPEN) prevents duplicate fulfilment but doesn't prevent probing.
  • Atone webhook: Np-Confirmation-Checksum validated with hash_equals (timing-safe).
  • Webhook routes use api middleware group — no CSRF, no session auth. SBPS also explicitly excluded from VerifyCsrfToken.
  • CkcConnect::attach() inside DB transaction: external API failure causes full rollback — network latency directly affects DB lock hold time.

Performance Considerations

  • lockForUpdate on product_variations and ckc_codes during order creation — lock duration = network round-trip to payment gateway (SBPS redirect path). Minimise by completing DB work before redirect.
  • SendOrderFinishNotification fires synchronously — mail send on order complete is blocking.
  • Atone fulfilment is inline in OrderController::store() — no async queue.