Skip to content

Thiết kế: Payment & Marketplace Feature

1. Tổng quan kiến trúc

flowchart TB
    subgraph Client
        App["Mobile App"]
    end

    subgraph Marketplace["webmarketplace module (port 8084)"]
        CC["CoinController\n/api/coin/*"]
        PC["PaymentController\n/api/payment/*"]
        WH["WebHookController\n/api/webhook/*"]
        UC["UserController\n/api/users/*"]
        RC["NftRentalController\n/api/nft-rental/*"]
    end

    subgraph Core["PaymentService (orchestrator)"]
        PS["PaymentService"]
        CARD["PaymentCreditCardService\n(Fincode/SBPS)"]
        CRYPTO["PaymentCryptoService\n(Slash)"]
        NATIVE["PaymentNativeTokenService\n(on-chain)"]
        APPLE["PaymentAppleService\n(Apple IAP)"]
        GOOGLE["PaymentGoogleService\n(Google Play)"]
    end

    subgraph External["External Gateways"]
        Fincode["Fincode/SBPS"]
        Slash["Slash Crypto"]
        AppleServer["Apple IAP Server"]
        GoogleServer["Google Play Server"]
        NodeSvr["Node Server\n(Blockchain)"]
    end

    subgraph DB["Persistence"]
        Order[("Payment Orders\n(MySQL)")]
        Wallet[("User Wallet/Coin\n(MySQL)")]
        NFT[("NFT Ownership\n(MySQL)")]
    end

    App -->|"buy-by-*"| CC & UC
    App -->|"payment utils"| PC
    CC & UC --> PS
    PS --> CARD & CRYPTO & NATIVE & APPLE & GOOGLE

    CARD -->|"checkout"| Fincode
    CRYPTO -->|"invoice"| Slash
    APPLE -->|"verify receipt"| AppleServer
    GOOGLE -->|"verify receipt"| GoogleServer
    NATIVE -->|"on-chain verify"| NodeSvr

    Fincode -->|"webhook"| WH
    Slash -->|"webhook"| WH
    NodeSvr -->|"confirm callbacks"| WH

    WH --> PS
    PS --> Order & Wallet & NFT

2. Sequence diagrams

2.1 Buy coin by card (Fincode flow)

sequenceDiagram
    participant App as Mobile App
    participant CC as CoinController
    participant PS as PaymentService
    participant CARD as PaymentCreditCardService
    participant Fincode as Fincode API
    participant DB as MySQL
    participant WH as WebHookController
    participant Coin as UserSystemCoinService

    App->>CC: POST /api/coin/buy-by-card\n{amount, cardToken, orderId}
    CC->>PS: buyCoinByCreditCard(req, buyer)
    PS->>CARD: buyCoinByCreditCard(req, buyer)
    CARD->>DB: create payment order (status=PENDING)
    CARD->>Fincode: checkout API call
    Fincode-->>CARD: checkout response (paymentId)
    CARD-->>CC: ResponseBuyByCardDto
    CC-->>App: Result.OK(response)

    Note over Fincode,WH: Async: Fincode processes payment
    Fincode->>WH: POST /api/webhook/confirm-card-payment\n(Fincode-Signature header)
    WH->>WH: validate signature
    WH->>PS: confirmCardPayment(params)
    PS->>CARD: confirmCardPayment(paymentId, status)
    CARD->>DB: update order status=SUCCESS
    CARD->>Coin: depositCoin(userId, amount)
    PS-->>WH: ResponseWebhookDto
    WH-->>Fincode: 200 OK

2.2 Buy coin by Apple IAP

sequenceDiagram
    participant App as Mobile App
    participant CC as CoinController
    participant PS as PaymentService
    participant APPLE as PaymentAppleService
    participant AppleSvr as Apple IAP Server
    participant DB as MySQL

    App->>CC: POST /api/coin/buy-by-apple\n{orderId, productId, receiptData}
    CC->>PS: buyCoinByApplePayment(req, buyer)
    PS->>APPLE: buyCoinByApplePayment(req, buyer)
    APPLE->>AppleSvr: verifyReceipt(receiptData)
    AppleSvr-->>APPLE: receipt valid + product info
    APPLE->>DB: create + confirm order (synchronous)
    APPLE->>APPLE: creditCoin(userId, coinAmount)
    APPLE-->>CC: success
    CC-->>App: Result.OK({orderId, productId})

2.3 NFT transfer webhook (3 types)

sequenceDiagram
    participant NodeSvr as Node Server
    participant WH as WebHookController
    participant TNS as TransferNftService

    NodeSvr->>WH: POST /api/webhook/nft-transfer-confirm\n{transferType, ...nfts}
    WH->>WH: check transferType

    alt transferType == TRANSFER_NFT_TO_ADMIN_WALLET
        WH->>TNS: confirmTransferNftToAdmin(requestDto)
    else transferType == TRANSFER_NFT_OWNER
        WH->>TNS: confirmTransferOwnerNft(requestDto)
    else default
        WH->>TNS: confirmNftTransfer(requestDto)
    end

    TNS->>TNS: update ownership records
    TNS-->>WH: done
    WH-->>NodeSvr: Result.OK()

3. Order state machine

stateDiagram-v2
    [*] --> PENDING: buy-by-* API called

    PENDING --> SUCCESS: webhook confirm (payment completed)
    PENDING --> FAILED: webhook confirm (payment failed)
    PENDING --> EXPIRED: batch job (timeout)

    SUCCESS --> [*]: Coin/NFT credited
    FAILED --> [*]: No action
    EXPIRED --> [*]: Cleanup

    note right of PENDING: CardCheckoutExpiredConfig\nCryptoCheckoutExpiredConfig\nNativeTokenCheckoutExpiredConfig\ncheck every N minutes
    note right of SUCCESS: UserSystemCoinService.deposit()\nor NFT ownership update

4. Gateway integration summary

Gateway Auth Async? Webhook format
Fincode API key Yes (webhook) JSON + Fincode-Signature header
SBPS Hash-based Yes (webhook) Form params (@RequestParam)
Slash (crypto) API key Yes (webhook) JSON
Apple IAP Server-to-server No (synchronous verify) N/A
Google Play Service account No (synchronous verify) N/A
Node Server Internal Yes (callback) JSON

5. Security design

// Fincode webhook signature validation
@PostMapping("/confirm-card-payment")
public Result<ResponseWebhookDto> confirmCardPayment(
        @RequestHeader("Fincode-Signature") String signature,
        @RequestBody Map<String, Object> params) {

    var setting = paymentFincodeSettingService.getPaymentFincodeSettingOrThrow();
    if (StringUtils.isNullOrEmpty(signature) || !setting.getSignature().equals(signature)) {
        return Result.error("FINCODE_SIGNATURE_IS_INVALID");
    }
    // process...
}

6. Idempotency pattern

  • Check order status trước khi credit (order status != PENDING → skip).
  • confirmCryptoPayment returns Boolean (false nếu đã confirmed).
  • Compressed NFT mint: check isSuccess != null && !isSuccess vs leafIndex == "Unknown" trước khi process.

7. Điểm cần chú ý

# Vấn đề Chi tiết
1 Apple/Google verify synchronous Nếu Apple/Google server chậm → timeout request
2 Webhook whitelist /api/webhook/* phải không cần JWT (SecurityConfig)
3 SBPS webhook format Form-encoded, khác JSON → dùng @RequestParam
4 Presale/genesis ordering Multi-step flow cần transaction boundary rõ