💳 第十四章:緑界金流 ECPay 接続実戦
おめでとうございます!「不遠露營度假山莊」のフロントエンド表示とSupabaseデータベース接続を完成させましたね! しかし、レジスターのないウェブサイトは、決して実際の収入をもたらすことはありません。 想像してみてください:顧客がサイトで日付を選び、「星空神殿帳」を選択した後、最後のステップで「LINEカスタマーサービスを追加し、振り込み後に通知してください」と表示されたら...2024年では完全に受け入れられないユーザー体験であり、衝動買いの顧客の50%以上を失うことになります!
台湾で最も普及しており、独立開発者やSaaS起業家に最適な金流サービスは**緑界科技(ECPay)**です。 緑界を接続すれば、顧客はあなたのサイトで直接クレジットカード決済、Apple Pay、街口支付、さらには7-11でバーコードをスキャンして支払うことができます!
この6000字に及ぶ究極の実戦講座では、Vibe Codingの手法で、従来のエンジニアが最も恐れる「CheckMacValueハッシュ暗号化アルゴリズム」をステップバイステップで分解し、緑界金流をnot-far-webプロジェクトに完璧に統合する方法をお伝えします!
🔐 なぜ緑界金流は接続が難しいのか?
プログラミングを始める前に、まず金流の本質を理解する必要があります。
顧客を緑界のカード決済ページにリダイレクトする際、単にURLの後ろに?price=1500と追加するだけではいけません。
なぜなら、この方法では、賢いハッカーがブラウザ上で?price=1500を?price=1に変更すれば、1500元の価値があるサービスを1元で購入できてしまうからです!
この問題を解決するため、緑界は**CheckMacValue(チェックマック値)**メカニズムを開発しました。
- 「注文番号、金額、商品名」などの情報に、あなたと緑界だけが知るHashKeyとHashIV(秘密鍵)を追加します。
- これらをすべて組み合わせ、一連の複雑なURLエンコードと文字置換を行います。
- 最後にSHA-256アルゴリズムで64桁のランダムなコードに圧縮します(これがCheckMacValueです)。
- このコードを金額とともに緑界に送信します。
- 緑界は受信後、自分たちの秘密鍵で同じ計算を行います。計算結果が送信されたコードと完全に一致すれば、緑界はこの注文を承認します。一致しない場合、途中で金額が改ざんされたことを意味し、緑界は直ちにエラーを返して取引を拒否します!
これがセキュリティ保護の核心です。この計算プロセスは非常に間違いやすい(大文字小文字が違ったり、1つの記号の置換を忘れたりするだけでチェックコードがエラーになります)。 以前は、エンジニアがこのアルゴリズムのデバッグに数日を費やしていました。今、私たちにはAIがあります!
⚙️ 実戦1:緑界テストアカウントの申請と環境変数の準備
プログラミングを始める前に、「レジスターの鍵」を準備してください。 緑界のメンバー登録をまだ行っていない場合、緑界が提供する公版テストアカウントを使用できます。
not-far-webプロジェクトのルートディレクトリに.envファイルを作成(または開き)、以下の設定を追加してください:
# 緑界テスト環境APIエンドポイント
ECPAY_ENDPOINT="https://payment-stage.ecpay.com.tw/Cashier/AioCheckOut/V5"
# 緑界公版テスト店舗ID
ECPAY_MERCHANT_ID="3002607"
# 緑界公版テストHashKey (絶対に漏洩しないように)
ECPAY_HASH_KEY="pwFHCqoQZGmho4w6"
# 緑界公版テストHashIV (絶対に漏洩しないように)
ECPAY_HASH_IV="EkRm7iFT261dpevs"
# 開発環境のローカルURL (Astroが4321ポートで動作している場合)
ECPAY_RETURN_URL="https://localhost:4321/api/ecpay/return"
ECPAY_CLIENT_BACK_URL="https://localhost:4321/booking-success"
🧙♂️ 実戦2:AIに緑界専用の暗号化アルゴリズムを書かせる
ここで、バックエンドAPIを作成します。
注意:金流暗号化は絶対にフロントエンド(ブラウザ)で行ってはいけません! フロントエンドで行うと、ハッカーがF12開発者ツールを開くだけで、あなたのHashKeyを見ることができてしまいます。Astroのサーバーサイド(SSR API Route)でこの処理を行う必要があります。
💡 Vibe Prompt 実戦1:CheckMacValueアルゴリズムの生成
暗号化ロジックを手作業で書くのではなく、Claude 3.5 SonnetやGPT-4oに任せましょう!
[!IMPORTANT] 以下のPromptをAIにコピーして送信してください:
私はNode.js/TypeScriptで台湾緑界金流(ECPay)を接続しています。関数generateCheckMacValue(params: Record<string, string>, hashKey: string, hashIV: string): stringを書いてください。要件:1. 緑界の公式仕様に従い、まずキーをアルファベット順にソートします。2. HashKey=xxx&Key1=Value1&Key2=Value2...&HashIV=xxxのように組み合わせます。3. encodeURIComponentでエンコード後、すべて小文字に変換します。4. 緑界専用の特殊文字置換を行います(%2d, %5f, %2e, %21, %2a, %28, %29, %20を対応する記号に置換)。5. cryptoパッケージを使用してSHA256ハッシュ化し、最後に大文字に変換して返します。
🤖 AIが生成した完璧な暗号化コア:
import crypto from 'crypto';
function generateCheckMacValue(params: Record<string, string>, hashKey: string, hashIV: string): string {
// 1. アルファベット順にソート(A-Z)
const sortedKeys = Object.keys(params).sort();
// 2. 文字列を組み合わせ、先頭と末尾にHashKeyとHashIVを追加
let str = `HashKey=${hashKey}`;
for (const key of sortedKeys) {
str += `&${key}=${params[key]}`;
}
str += `&HashIV=${hashIV}`;
// 3. URLエンコードを行い、小文字に変換
let encoded = encodeURIComponent(str).toLowerCase();
// 4. 緑界の厄介なC# url encode特殊文字互換置換
// これは無数のエンジニアが夜を徹して見つけ出した対応表です!
encoded = encoded
.replace(/%2d/g, '-')
.replace(/%5f/g, '_')
.replace(/%2e/g, '.')
.replace(/%21/g, '!')
.replace(/%2a/g, '*')
.replace(/%28/g, '(')
.replace(/%29/g, ')')
.replace(/%20/g, '+')
.replace(/'/g, '%27')
.replace(/~/g, '%7e');
// 5. SHA-256で不可逆暗号化し、大文字に変換
return crypto.createHash('sha256').update(encoded).digest('hex').toUpperCase();
}
このコードは非常に価値があります!もし.replace(/%20/g, '+')の1行を忘れると、商品名に「空白」が含まれている場合、緑界はエラーを返し続けます。AIはこの落とし穴を事前に回避してくれました。
🚀 実戦3:決済リダイレクトAPIの作成(Astro API Route)
暗号化コアが完成したので、not-far-webに決済APIを作成します。
ユーザーがショッ���ングカートで「決済確認」をクリックすると、フロントエンドは注文情報をこのAPIにPOSTし、APIはチェックコードを計算した後、「自動送信スクリプトを含むHTMLフォーム」を生成し、ユーザーを緑界に送ります。
ファイル作成:src/pages/api/ecpay/create.ts
💡 Vibe Prompt 実戦2:決済APIの作成
[!IMPORTANT] 以下のPromptをAIにコピーして送信してください:
AstroフレームワークでGET/POST API Route(/api/ecpay/create)を作成します。先ほど作成したgenerateCheckMacValue関数を使用してください。フロー:1. フロントエンドから送信されたcourseIdとpriceを受け取ります。2. タイムスタンプ注文番号MerchantTradeNoを生成します。3. envからECPAY_MERCHANT_ID, HASH_KEY, HASH_IVを読み取ります。4. 緑界の必須パラメータを整理します(MerchantID, MerchantTradeDate, PaymentType='aio', TotalAmount, TradeDesc, ItemName, ReturnURL, ChoosePayment='Credit', EncryptType='1', ClientBackURL)。5. CheckMacValueを計算後、hidden inputを含むHTMLコードを動的に生成し、JavaScript(document.getElementById('form').submit())を追加して自動的にリダイレクトさせます。6. このHTMLをブラウザに返します(Content-Type: text/html)。
🤖 AIのシームレスリダイレクトエンジン:
import type { APIRoute } from 'astro';
import crypto from 'crypto';
// (上記のgenerateCheckMacValue関数は省略...)
export const POST: APIRoute = async ({ request }) => {
const formData = await request.formData();
const campName = formData.get('campName') as string || '不遠露營度假山莊線上預訂';
const price = formData.get('price') as string || '1500';
// タイムスタンプ注文番号を生成(20桁を超えないように)
const timestamp = new Date().getTime().toString().slice(-6);
const random = Math.floor(Math.random() * 1000).toString().padStart(3, '0');
const orderNo = `CAMP${timestamp}${random}`;
const merchantId = import.meta.env.ECPAY_MERCHANT_ID;
const hashKey = import.meta.env.ECPAY_HASH_KEY;
const hashIv = import.meta.env.ECPAY_HASH_IV;
const endpoint = import.meta.env.ECPAY_ENDPOINT;
// 台湾時間を取得し、yyyy/MM/dd HH:mm:ss形式にフォーマット
const twTime = new Date(new Date().getTime() + 8 * 60 * 60 * 1000);
const pad = (n: number) => n.toString().padStart(2, '0');
const tradeDate = `${twTime.getUTCFullYear()}/${pad(twTime.getUTCMonth() + 1)}/${pad(twTime.getUTCDate())} ${pad(twTime.getUTCHours())}:${pad(twTime.getUTCMinutes())}:${pad(twTime.getUTCSeconds())}`;
const params: Record<string, string> = {
MerchantID: merchantId,
MerchantTradeNo: orderNo,
MerchantTradeDate: tradeDate,
PaymentType: 'aio',
TotalAmount: price.toString(),
TradeDesc: '不遠露營度假山莊線上預訂',
ItemName: campName,
ReturnURL: import.meta.env.ECPAY_RETURN_URL, // 緑界サーバーが呼び出す用
ChoosePayment: 'Credit', // クレジットカード決済のみ許可
EncryptType: '1',
ClientBackURL: import.meta.env.ECPAY_CLIENT_BACK_URL, // 顧客が決済後に戻る用
};
// 究極のパスワードを計算
params.CheckMacValue = generateCheckMacValue(params, hashKey, hashIv);
// すべてのパラメータをhidden inputフォームに変換
const inputs = Object.entries(params).map(([key, val]) =>
`<input type="hidden" name="${key}" value="${val}" />`
).join('\n');
// 「読み込み中」画面を作成し、背後で瞬時にフォームを緑界に送信
const html = `
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<title>緑界安全暗号化チャネルに接続中</title>
<style>
body { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; background: #0f172a; color: white; font-family: sans-serif; }
.spinner { border: 4px solid rgba(255,255,255,0.1); border-top: 4px solid #10b981; border-radius: 50%; width: 50px; height: 50px; animation: spin 1s linear infinite; margin-bottom: 20px;}
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
</style>
</head>
<body>
<div class="spinner"></div>
<h2>安全暗号化サーバーに接続中...</h2>
<form id="ecpay-form" action="${endpoint}" method="POST">
${inputs}
</form>
<script>
// ユーザーにかっこいい読み込み画面を見せるため、意図的に0.8秒遅延させてリダイレクト
setTimeout(() => {
document.getElementById('ecpay-form').submit();
}, 800);
</script>
</body>
</html>
`;
return new Response(html, {
headers: { 'Content-Type': 'text/html; charset=utf-8' },
});
};
📡 実戦4:偽造防止Server Callback(Webhooks)の実装
多くの人は、顧客のカード決済が成功し、緑界が顧客をサイト(ClientBackURL)に戻せば決済完了だと考えています。
これは大きな間違いです!非常に危険な方法です!
ハッカーが自分でhttps://あなたのサイト.com/booking-success?order=123のようなURLを作成してクリックした場合、システムがこれだけで出荷処理を行えば、明日には破産してしまいます。
本当に安全な方法は、緑界の**ReturnURL(Server Callback)**を受信することです。
緑界のサーバーは、Line BotのWebhookのように、HTTP POSTリクエストをあなたのサーバーに自動的に送信します。このリクエストにはRtnCode=1(決済成功を示す)が含まれ、さらにCheckMacValueも付属しています!
緑界から送信されたパラメータを同じアルゴリ��ムで再計算し、計算結果が緑界から送信されたコードと一致する場合にのみ、このデータが本当に緑界から送信されたものであり、ハッカーによる偽造ではないと判断できます!
ファイル作成:src/pages/api/ecpay/return.ts
💡 Vibe Prompt 実戦3:超安全なWebhook検証の実装
[!IMPORTANT] 以下のPromptをAIにコピーして送信してください:
緑界ReturnURLを処理するAstro API(/api/ecpay/return)が必要です。1. application/x-www-form-urlencoded形式のPOSTリクエストを受け取ります。`2. すべてのパラメータを