ecpay
Provides ECPay (綠界科技) integration guide including payment setup and electronic invoice integration. Use when integrating ECPay…
Implements PAYUNi webhook handling including signature verification, replay attack prevention, and payment status updates. Use when building payment notification endpoints for 統一金流.
$ npx -y skills add paid-tw/skills --skill payuni-webhook --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/payuni-webhookContext preview
The summary Claude sees to decide when to auto-load this skill.
Implements PAYUNi webhook handling including signature verification, replay attack prevention, and payment status updates. Use when building payment notification endpoints for 統一金流.
name: payuni-webhook description: > Implements PAYUNi webhook handling including signature verification, replay attack prevention, and payment status updates. Use when building payment notification endpoints for 統一金流. argument-hint: "[框架: Next.js/Express/其他]" context: fork agent: general-purpose disable-model-invocation: true allowed-tools: - Read - Write - Edit - Bash - Grep - Glob user-invocable: true
你的任務是在用戶的專案中實作統一金流 Webhook 接收與處理功能。
---
用戶輸入: `$ARGUMENTS`
詢問用戶:
1. **框架類型**:
2. **資料庫**:用什麼來儲存訂單?
// app/api/webhooks/payuni/route.ts
import { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto';
const config = {
hashKey: process.env.PAYUNI_HASH_KEY!,
hashIV: process.env.PAYUNI_HASH_IV!,
};
// 簽名驗證(使用 constant-time 比較防止 timing attack)
function verifyCheckCode(params: Record<string, string>): boolean {
const { CheckCode, ...otherParams } = params;
if (!CheckCode) return false;
const sortedKeys = Object.keys(otherParams).sort();
const paramStr = sortedKeys.map(k => `${k}=${otherParams[k]}`).join('&');
const signStr = `HashKey=${config.hashKey}&${paramStr}&HashIV=${config.hashIV}`;
const calculated = crypto
.createHash('sha256')
.update(signStr)
.digest('hex')
.toUpperCase();
try {
return crypto.timingSafeEqual(
Buffer.from(calculated),
Buffer.from(CheckCode)
);
} catch {
return false;
}
}
export async function POST(request: NextRequest) {
try {
// 解析請求
const contentType = request.headers.get('content-type');
let params: Record<string, string>;
if (contentType?.includes('application/json')) {
params = await request.json();
} else {
const formData = await request.formData();
params = Object.fromEntries(formData.entries()) as Record<string, string>;
}
// 驗證簽名
if (!verifyCheckCode(params)) {
console.error('[Webhook] Invalid signature');
return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
}
// 取得訂單資訊
const { Status, MerchantOrderNo, TradeNo, TradeAmt } = params;
// TODO: 檢查是否已處理過此 TradeNo(防重放攻擊)
// const exists = await db.webhookLog.findUnique({ where: { tradeNo: TradeNo } });
// if (exists) return NextResponse.json({ success: true, message: 'Already processed' });
if (Status === 'SUCCESS') {
// TODO: 更新訂單狀態為已付款
// await db.order.update({
// where: { id: MerchantOrderNo },
// data: { status: 'paid', paymentDetails: { tradeNo: TradeNo, amount: TradeAmt } }
// });
console.log('[Webhook] Payment success:', MerchantOrderNo);
} else {
// TODO: 更新訂單狀態為失敗
console.log('[Webhook] Payment failed:', MerchantOrderNo);
}
// TODO: 記錄 webhook 請求(防重放)
// await db.webhookLog.create({ data: { tradeNo: TradeNo, processedAt: new Date() } });
return NextResponse.json({ success: true });
} catch (error) {
console.error('[Webhook] Error:', error);
return NextResponse.json({ error: 'Internal error' }, { status: 500 });
}
}
// 健康檢查端點
export async function GET() {
return NextResponse.json({
message: 'PAYUNi Webhook endpoint',
timestamp: new Date().toISOString(),
});
}import express from 'express';
import crypto from 'crypto';
const router = express.Router();
router.post('/webhooks/payuni', async (req, res) => {
try {
const params = req.body;
// 驗證簽名
if (!verifyCheckCode(params)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const { Status, MerchantOrderNo, TradeNo } = params;
if (Status === 'SUCCESS') {
// 更新訂單狀態
console.log('Payment success:', MerchantOrderNo);
}
res.json({ success: true });
} catch (error) {
console.error('Webhook error:', error);
res.status(500).json({ error: 'Internal error' });
}
});
export default router;建立 webhook 請求記錄表:
CREATE TABLE webhook_requests ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), provider VARCHAR(50) NOT NULL DEFAULT 'payuni', trade_no VARCHAR(100) UNIQUE, merchant_order_no VARCHAR(100), checksum VARCHAR(64), status VARCHAR(20) DEFAULT 'processing', processed_at TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_webhook_trade_no ON webhook_requests(trade_no); CREATE INDEX idx_webhook_checksum ON webhook_requests(checksum);
async function checkDuplicate(tradeNo: string): Promise<boolean> {
const existing = await db.webhookRequest.findUnique({
where: { tradeNo }
});
return !!existing;
}
async function markProcessed(tradeNo: string): Promise<void> {
await db.webhookRequest.create({
data: {
tradeNo,
provider: 'payuni',
processedAt: new Date(),
}
});
}1. **使用 ngrok 暴露本地服務**
ngrok http 3000
2. **設定 NotifyURL** 將 ngrok 提供的 HTTPS URL 設定為 NotifyURL
3. **發起測試付款** 在統一金流測試環境發起付款
4. **檢查日誌** 確認 Webhook 正確接收並處理
---
| 參數 | 說明 | |------|------| | Status | 付款狀態 `SUCCESS` / `FAIL` | | MerchantOrderNo | 商店訂單編號 | | TradeNo | PAYUNi 交易編號 | | TradeAmt | 交易金額 | | PaymentType | 付款方式 | | PayTime | 付款時間 | | CheckCode | 驗證碼 |
1. 將所有參數(除了 CheckCode)按字母順序排序 2. 組成 key=value&key=value 的字串 3. 在開頭加上 HashKey=xxx&,結尾加上 &HashIV=xxx 4. 進行 SHA256 雜湊後轉大寫
---
1. **使用 HTTPS** - 正式環境必須使用 HTTPS 2. **驗證簽名** - 每次都要驗證 C
台灣第三方金流 Skills for AI Agents - 藍新金流、綠界科技 ECPay、統一金流 PAYUNi、KryptoGO 穩定幣支付 支援 Claude Code、Cursor、Codex、GitHub Copilot 等 AI coding agents。
Provides ECPay (綠界科技) integration guide including payment setup and electronic invoice integration. Use when integrating ECPay…
Implements KryptoGO Payment checkout integration including Payment Intent creation, React SDK usePayment hook, and Direct API…
Implements KryptoGO Payment query functionality for checking payment intent status and listing payment intents. Use when building…
Implements KryptoGO Payment token transfer/withdrawal functionality using the Asset Pro Transfer API. Use when building…
Implements KryptoGO Payment webhook/callback handling for receiving payment status notifications. Use when building webhook…
Provides KryptoGO Payment integration overview and guides users to the appropriate skill. Use when starting KryptoGO crypto…