| 參數 | 說明 | 範例 |
| partner | 合作夥伴 ID (PID) | 2088xxxxxxxx |
| key | 交易安全校驗碼 (MD5 Key) | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |
| seller_email | 簽約支付寶賬號 | merchant@alipay.com |
| service | 接口類型 | create_direct_pay_by_user (即時到帳) |
| _input_charset | 編碼格式 | utf-8 |
| sign_type | 簽名方式 | MD5 |
1. 請求頁 (Default.aspx) - 產生 POST 表單
protected void btnPay_Click(object sender, EventArgs e) {
string gateway = "https://www.alipay.com/cooperate/gateway.do";
var para = new Dictionary {
{"service", "create_direct_pay_by_user"},
{"partner", partner},
{"seller_email", seller_email},
{"out_trade_no", txtOrderNo.Text.Trim()}, // 商家訂單號,唯一
{"subject", "訂單標題"},
{"body", "訂單描述"},
{"total_fee", txtAmount.Text.Trim()}, // 金額,元
{"show_url", "https://myshop.com/product/123"},
{"payment_type", "1"},
{"notify_url", "https://myshop.com/Alipay/Notify.aspx"}, // 必須可公開訪問
{"return_url", "https://myshop.com/Alipay/Return.aspx"},
{"_input_charset", "utf-8"}
};
string sign = AliPayHelper.BuildSign(para, key, "utf-8"); // 參考原文 AliPay.CreatUrl
// 輸出自動提交表單
Response.Write("<form id='alipayForm' action='" + gateway + "' method='post'>");
foreach (var kv in para) {
Response.Write($"<input type='hidden' name='{kv.Key}' value='{HttpUtility.HtmlEncode(kv.Value)}' />");
}
Response.Write($"<input type='hidden' name='sign' value='{sign}' />");
Response.Write($"<input type='hidden' name='sign_type' value='MD5' />");
Response.Write("</form><script>document.getElementById('alipayForm').submit();</script>");
Response.End();
}
2. 同步跳轉頁 (Return.aspx) - 僅顯示結果
protected void Page_Load(object sender, EventArgs e) {
bool verified = AliPayHelper.VerifyNotify(Request.QueryString, key, "utf-8");
string outTradeNo = Request.QueryString["out_trade_no"];
string tradeStatus = Request.QueryString["trade_status"];
if (verified && (tradeStatus == "TRADE_FINISHED" || tradeStatus == "TRADE_SUCCESS")) {
lblMsg.Text = $"訂單 {outTradeNo} 付款成功!金額:{Request.QueryString["total_fee"]} 元";
LogSuccess(outTradeNo, "Return");
} else {
lblMsg.Text = "付款失敗或驗證未通過";
LogFail(outTradeNo, "Return");
}
}
// 注意:此處**不做**帳務更新,僅顯示結果、記錄日誌
3. 異步通知頁 (Notify.aspx) - 核心帳務處理
protected void Page_Load(object sender, EventArgs e) {
// 1. 驗證簽名 + 支付寶伺服器二次確認
bool verified = AliPayHelper.VerifyNotify(Request.Form, key, "utf-8");
if (!verified) { Response.Write("fail"); return; }
string outTradeNo = Request.Form["out_trade_no"];
string tradeStatus = Request.Form["trade_status"];
string totalFee = Request.Form["total_fee"];
// 2. 業務冪等處理:同一訂單只處理一次成功狀態
if (tradeStatus == "TRADE_FINISHED" || tradeStatus == "TRADE_SUCCESS") {
if (OrderService.TryMarkPaid(outTradeNo, decimal.Parse(totalFee))) {
// 成功:更新訂單狀態、增加餘額、發送通知、記錄流水...
Response.Write("success"); // 必須輸出純文字 success,支付寶才停止重試
} else {
Response.Write("fail"); // 處理失敗,支付寶會按規則重試 (最長 24h)
}
} else if (tradeStatus == "WAIT_BUYER_PAY") {
// 待付款,可記錄日誌
Response.Write("success");
} else {
Response.Write("success"); // 其他狀態也回 success 避免重試風暴
}
}
核心輔助類 AliPayHelper (關鍵邏輯)
public static class AliPayHelper {
// 1. 冒泡排序 (支付寶要求參數按鍵名 ASCII 升序)
public static string[] BubbleSort(string[] arr) { ... }
// 2. MD5 簽名
public static string GetMD5(string s, string charset) {
using (var md5 = MD5.Create())
return BitConverter.ToString(md5.ComputeHash(Encoding.GetEncoding(charset).GetBytes(s)))
.Replace("-", "").ToLower();
}
// 3. 組裝待簽字串並簽名
public static string BuildSign(IDictionary parameters, string key, string charset) {
var sorted = parameters.Where(p => !string.IsNullOrEmpty(p.Value))
.OrderBy(p => p.Key, StringComparer.Ordinal)
.Select(p => $"{p.Key}={p.Value}");
string prestr = string.Join("&", sorted) + key;
return GetMD5(prestr, charset);
}
// 4. 驗證通知 (同步/異步通用)
public static bool VerifyNotify(NameValueCollection coll, string key, string charset) {
// 4.1 取出 sign, sign_type
string sign = coll["sign"];
if (string.IsNullOrEmpty(sign)) return false;
// 4.2 組裝待驗證字串 (排除 sign, sign_type, 空值)
var sorted = coll.AllKeys
.Where(k => k != "sign" && k != "sign_type" && !string.IsNullOrEmpty(coll[k]))
.OrderBy(k => k, StringComparer.Ordinal)
.Select(k => $"{k}={coll[k]}");
string prestr = string.Join("&", sorted) + key;
// 4.3 本地簽名比對
if (GetMD5(prestr, charset) != sign) return false;
// 4.4 遠程二次驗證 (notify_verify) - 可選但建議
string verifyUrl = $"https://www.alipay.com/cooperate/gateway.do?service=notify_verify&partner={partner}¬ify_id={coll["notify_id"]}";
using (var wc = new WebClient()) {
string remoteResult = wc.DownloadString(verifyUrl);
return remoteResult == "true";
}
}
}
關鍵最佳實務
| 原則 | 做法 |
| 冪等性 | 資料庫訂單表加 UNIQUE(out_trade_no),或狀態機 WAIT_PAY → PAID 只允許單向流轉 |
| 金額核對 | 通知中的 total_fee 必須與建單金額一致,防範惡意竄改 |
| HTTPS 強制 | notify_url/return_url 必須為 HTTPS,防止中間人竊取參數 |
| 日誌完整 | 請求參數、驗證結果、業務處理結果全記錄 (含 notify_id),便於對帳 |
| 超時處理 | 異步通知 24 小時內重試,業務邏輯必須能承受重複調用 |
新版支付寶 SDK 遷移建議
- 官方現行 SDK:
Alipay.AopSdk.F2F (當面付)、Alipay.AopSdk.AspNetCore (網頁/APP)。
- 簽名演算法升級為 **RSA2 (SHA256 with RSA)**,不再使用 MD5。
- 介面變更:
alipay.trade.page.pay (電腦網站)、alipay.trade.wap.pay (手機網站)、alipay.trade.app.pay (APP)。
- 建議新專案直接使用新版 SDK,舊專案維護期內可續用 MD5 版本但需規劃遷移。
測試環境 (沙箱)
- 註冊支付寶開放平台開發者賬號,建立沙箱應用獲取
partner、key (或 RSA 密鑰對)。
- 網關:
https://openapi.alipaydev.com/gateway.do。
- 使用沙箱賬號掃碼/登入完成測試付款。
- 上一篇文章: C# 圖片縮放壓縮元件:指定模板寬高、保持比例、高品質插值、支援水印擴充
- 下一篇文章: ASP.NET/C# 三元運算子與 if-else 條件判斷的等效寫法與最佳實踐
Copyright ©
程式碼之家 All Rights Reserved