指南
Express
在 Express 後端整合 StableOps:安全建立冪等付款單、保留 Webhook 原始請求體、驗證 HMAC 簽名,並以可重放且去重的方式處理支付狀態事件。示例覆蓋環境變數、路由實現、錯誤回應與訂單查詢,幫助服務在網路重試和重複事件下仍保持一致履約。
安裝
pnpm add @stableops/api-sdk express設定
STABLEOPS_API_KEY=sk_sandbox_...
STABLEOPS_WEBHOOK_SECRET=whsec_...建立 lib/stableops.js:
const { StableOps } = require('@stableops/api-sdk')
const stableops = new StableOps({
apiKey: process.env.STABLEOPS_API_KEY,
})
module.exports = { stableops }建立付款單
const express = require('express')
const { stableops } = require('../lib/stableops')
const router = express.Router()
router.post('/orders', async (req, res) => {
try {
const { merchantOrderId, amount, metadata } = req.body
if (!merchantOrderId || !amount) {
return res
.status(400)
.json({ error: 'merchantOrderId and amount are required' })
}
const order = await stableops.paymentOrders.create(
{
merchantOrderId,
amount: String(amount),
acceptedAssets: [{ chain: 'base', asset: 'USDC' }],
expiresAt: new Date(Date.now() + 30 * 60 * 1000).toISOString(),
metadata: metadata || {},
},
{ idempotencyKey: merchantOrderId },
)
res.status(201).json({
id: order.id,
merchantOrderId: order.merchantOrderId,
amount: order.amount,
status: order.status,
paymentInstructions: order.paymentInstructions,
})
} catch (error) {
res.status(error.status || 500).json({
error: error.code || 'stableops_error',
message: error.message,
})
}
})
router.get('/orders/:id', async (req, res) => {
const order = await stableops.paymentOrders.retrieve(req.params.id)
res.json(order)
})
router.post('/orders/:id/cancel', async (req, res) => {
const order = await stableops.paymentOrders.cancel(req.params.id)
res.json(order)
})
module.exports = router驗證 Webhook
Webhook 路由必須掛在 express.json() 之前,讓 handler 拿到原始 body 用於驗籤。
const express = require('express')
const {
SIGNATURE_HEADER,
verifySignature,
} = require('@stableops/api-sdk/webhooks')
const router = express.Router()
const WEBHOOK_SECRET = process.env.STABLEOPS_WEBHOOK_SECRET
router.post(
'/stableops',
express.raw({ type: 'application/json' }),
async (req, res) => {
const rawBody = req.body.toString('utf8')
const result = verifySignature({
secrets: [WEBHOOK_SECRET],
header: req.header(SIGNATURE_HEADER),
rawBody,
})
if (!result.ok) {
return res.status(400).json({ error: result.reason })
}
const event = JSON.parse(rawBody)
switch (event.type) {
case 'payment.finalized':
// 用 X-Event-Id 去重後再履約 event.data.payment_order_id
break
case 'payment.reverted':
case 'payment.expired':
break
}
res.sendStatus(200)
},
)
module.exports = router這篇文件怎麼樣?
最後更新