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õ |