本文將介紹在前後分離的狀況下,後端如何與前端配合製作 Web API ,串接藍新金流服務。
至藍新測試帳號頁面註冊個人會員並登入,於會員中心⇒商店管理⇒開立商店,開立"網路商店"
開立商店完成後,回到商店資料設定,進入商店的詳細資料頁,於金流特約商店設定中,啟用的項目的支付方式於本範例只啟用 信用卡一次付清 及 WebATM,其它項目先設為不啟用

點選生成 API串接金鑰

上傳商店 Logo
後端建立 CryptoUtil.cs 類別檔,用於加解密
using System;
using System.Security.Cryptography;
using System.Text;
namespace MyProject.Security
{
    /// <summary>
    /// 藍新加解密 Util
    /// </summary>
    public class CryptoUtil
    {
        /// <summary>
        /// 字串加密 AES
        /// </summary>
        /// <param name="source">加密前字串</param>
        /// <param name="cryptoKey">加密金鑰</param>
        /// <param name="cryptoIV">cryptoIV</param>
        /// <returns>加密後字串</returns>
        public static byte[] EncryptAES(byte[] source, string cryptoKey, string cryptoIV)
        {
            byte[] dataKey = Encoding.UTF8.GetBytes(cryptoKey);
            byte[] dataIV = Encoding.UTF8.GetBytes(cryptoIV);
            using (var aes = Aes.Create()) {
                aes.Mode = CipherMode.CBC;
                aes.Padding = PaddingMode.PKCS7;
                aes.Key = dataKey;
                aes.IV = dataIV;
                using (var encryptor = aes.CreateEncryptor()) {
                    return encryptor.TransformFinalBlock(source, 0, source.Length);
                }
            }
        }
        /// <summary>
        /// 字串解密 AES
        /// </summary>
        /// <param name="source">解密前字串</param>
        /// <param name="cryptoKey">解密金鑰</param>
        /// <param name="cryptoIV">cryptoIV</param>
        /// <returns>解密後字串</returns>
        public static byte[] DecryptAES(byte[] source, string cryptoKey, string cryptoIV)
        {
            byte[] dataKey = Encoding.UTF8.GetBytes(cryptoKey);
            byte[] dataIV = Encoding.UTF8.GetBytes(cryptoIV);
            using (var aes = Aes.Create()) {
                aes.Mode = CipherMode.CBC;
                // 無法直接用 PaddingMode.PKCS7,會跳"填補無效,而且無法移除。"
                // 所以改為 PaddingMode.None 並搭配 RemovePKCS7Padding
                aes.Padding = PaddingMode.None;
                aes.Key = dataKey;
                aes.IV = dataIV;
                using (var decryptor = aes.CreateDecryptor()) {
                    return RemovePKCS7Padding(decryptor.TransformFinalBlock(source, 0, source.Length));
                }
            }
        }
        /// <summary>
        /// 加密後再轉 16 進制字串
        /// </summary>
        /// <param name="source">加密前字串</param>
        /// <param name="cryptoKey">加密金鑰</param>
        /// <param name="cryptoIV">cryptoIV</param>
        /// <returns>加密後的字串</returns>
        public static string EncryptAESHex(string source, string cryptoKey, string cryptoIV)
        {
            string result = string.Empty;
            if (!string.IsNullOrEmpty(source)) {
                var encryptValue = EncryptAES(Encoding.UTF8.GetBytes(source), cryptoKey, cryptoIV);
                if (encryptValue != null) {
                    result = BitConverter.ToString(encryptValue)?.Replace("-", string.Empty)?.ToLower();
                }
            }
            return result;
        }
        /// <summary>
        /// 16 進制字串解密
        /// </summary>
        /// <param name="source">加密前字串</param>
        /// <param name="cryptoKey">加密金鑰</param>
        /// <param name="cryptoIV">cryptoIV</param>
        /// <returns>解密後的字串</returns>
        public static string DecryptAESHex(string source, string cryptoKey, string cryptoIV)
        {
            string result = string.Empty;
            if (!string.IsNullOrEmpty(source)) {
                // 將 16 進制字串 轉為 byte[] 後
                byte[] sourceBytes = GetByteArray(source);
                if (sourceBytes != null) {
                    // 使用金鑰解密後,轉回 加密前 value
                    result = Encoding.UTF8.GetString(DecryptAES(sourceBytes, cryptoKey, cryptoIV)).Trim();
                }
            }
            return result;
        }
        /// <summary>
        /// 字串加密 SHA256
        /// </summary>
        /// <param name="source">加密前字串</param>
        /// <returns>加密後字串</returns>
        public static string EncryptSHA256(string source)
        {
            string result = string.Empty;
            using (SHA256 algorithm = SHA256.Create()) {
                var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(source));
                if (hash != null) {
                    result = BitConverter.ToString(hash)?.Replace("-", string.Empty)?.ToUpper();
                }
            }
            return result;
        }
        /// <summary>
        /// 將 16 進位字串轉換為 byteArray
        /// </summary>
        /// <param name="source">欲轉換之字串</param>
        /// <returns></returns>
        public static byte[] GetByteArray(string source)
        {
            byte[] result = null;
            if (!string.IsNullOrWhiteSpace(source)) {
                var outputLength = source.Length / 2;
                var output = new byte[outputLength];
                for (var i = 0; i < outputLength; i++) {
                    output[i] = Convert.ToByte(source.Substring(i * 2, 2), 16);
                }
                result = output;
            }
            return result;
        }
        private static byte[] RemovePKCS7Padding(byte[] data)
        {
            int iLength = data[data.Length - 1];
            var output = new byte[data.Length - iLength];
            Buffer.BlockCopy(data, 0, output, 0, output.Length);
            return output;
        }
    }
}
前端 建立 "確認商品" 頁面,點選按鈕後將商品資料送到後端接收 API,夾帶登入的 token 用來取得購買者身份 (付款前)
後端建立接收使用者購買內容 API (付款前)
[HttpPost]
public IHttpActionResult SetChargeData(ChargeRequest chargeData)
{
    // Do Something ~ (相關資料檢查處理,成立訂單加入資料庫,並將訂單付款狀態設為未付款)
    // 整理金流串接資料
    // 加密用金鑰
    string hashKey = "填入生成的 HashKey";
    string hashIV = "填入生成的 HashIV";
    // 金流接收必填資料
    string merchantID = "填入商店代號";
    string tradeInfo = "";
    string tradeSha = "";
    string version = "2.0"; // 參考文件串接程式版本
    // tradeInfo 內容,導回的網址都需為 https 
    string respondType = "JSON"; // 回傳格式
    string timeStamp = ((int)(dateTimeNow - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds).ToString();
    string merchantOrderNo = timeStamp +"_"+ "訂單ID"; // 底線後方為訂單ID,解密比對用,不可重覆(規則參考文件)
    string amt = "訂單金額";
    string itemDesc = "商品資訊";
    string tradeLimit = "600"; // 交易限制秒數
    string notifyURL = @"https://" + Request.RequestUri.Host + "NotifyURL"; // NotifyURL 填後端接收藍新付款結果的 API 位置,如 : /api/users/getpaymentdata
    string returnURL = "付款完成導回頁面網址" + "/" + "訂單ID";  // 前端可用 Status: SUCCESS 來判斷付款成功,網址夾帶可拿來取得活動內容
    string email = "消費者信箱"; // 通知付款完成用
    string loginType = "0"; // 0不須登入藍新金流會員
    // 將 model 轉換為List<KeyValuePair<string, string>>
    List<KeyValuePair<string, string>> tradeData = new List<KeyValuePair<string, string>>() {
        new KeyValuePair<string, string>("MerchantID", merchantID),
        new KeyValuePair<string, string>("RespondType", respondType),
        new KeyValuePair<string, string>("TimeStamp", timeStamp),
        new KeyValuePair<string, string>("Version", version),
        new KeyValuePair<string, string>("MerchantOrderNo", merchantOrderNo),
        new KeyValuePair<string, string>("Amt", amt),
        new KeyValuePair<string, string>("ItemDesc", itemDesc),
        new KeyValuePair<string, string>("TradeLimit", tradeLimit),
        new KeyValuePair<string, string>("NotifyURL", notifyURL),
        new KeyValuePair<string, string>("ReturnURL", returnURL),
        new KeyValuePair<string, string>("Email", email),
        new KeyValuePair<string, string>("LoginType", loginType)
    };
    // 將 List<KeyValuePair<string, string>> 轉換為 key1=Value1&key2=Value2&key3=Value3...
    var tradeQueryPara = string.Join("&", tradeData.Select(x => $"{x.Key}={x.Value}"));
    // AES 加密
    tradeInfo = CryptoUtil.EncryptAESHex(tradeQueryPara, hashKey, hashIV);
    // SHA256 加密
    tradeSha = CryptoUtil.EncryptSHA256($"HashKey={hashKey}&{tradeInfo}&HashIV={hashIV}");
    // 送出金流串接用資料,給前端送藍新用
    return Ok(new
    {
        Status = true,
        PaymentData = new
        {
            MerchantID = merchantID,
            TradeInfo = tradeInfo,
            TradeSha = tradeSha,
            Version = version
        }
    });
}
前端 將後端加密回傳的資料填入後,用表單送出到藍新處理頁面 (欄位內容設為隱藏)
<!-- 用表單送給藍新 -->
<form name='Newebpay' method='post' action='https://ccore.newebpay.com/MPG/mpg_gateway'>
    <!-- 設定 hidden 可以隱藏不用給使用者看的資訊 -->
    <!-- 藍新金流商店代號 -->
    <input type='hidden' id='MerchantID' name='MerchantID' value='填入後端回傳的 MerchantID'>
    <!-- 交易資料透過 Key 及 IV 進行 AES 加密 -->
    <input type='hidden' id='TradeInfo' name='TradeInfo' value='填入後端回傳的 TradeInfo'>
    <!-- 經過上述 AES 加密過的字串,透過商店 Key 及 IV 進行 SHA256 加密 -->
    <input type='hidden' id='TradeSha' name='TradeSha' value='填入後端回傳的 TradeSha'>
    <!-- 串接程式版本 -->
    <input type='hidden' id='Version' name='Version' value='填入後端回傳的 Version'>
    <!-- 直接執行送出 -->
    <input type='submit' value='前往付款'>
</form>
測試用付款頁面,付款完成後會導回 ReturnURL,並同時將付款結果資料傳到 NotifyURL
後端建立藍新回傳資料欄位類別檔 NewebPayReturn.cs 及 解密資料欄位類別檔 PaymentResult.cs
namespace MyProject.Security
{
    public class NewebPayReturn
    {
        public string Status { get; set; }
        public string MerchantID { get; set; }
        public string Version { get; set; }
        public string TradeInfo { get; set; }
        public string TradeSha { get; set; }
    }
}
namespace MyProject.Security
{
    public class PaymentResult
    {
        public string Status { get; set; }
        public string Message { get; set; }
        public Result Result { get; set; }
    }
    public class Result
    {
        public string MerchantID { get; set; }
        public string Amt { get; set; }
        public string TradeNo { get; set; }
        public string MerchantOrderNo { get; set; }
        public string RespondType { get; set; }
        public string IP { get; set; }
        public string EscrowBank { get; set; }
        public string PaymentType { get; set; }
        public string RespondCode { get; set; }
        public string Auth { get; set; }
        public string Card6No { get; set; }
        public string Card4No { get; set; }
        public string Exp { get; set; }
        public string TokenUseStatus { get; set; }
        public string InstFirst { get; set; }
        public string InstEach { get; set; }
        public string Inst { get; set; }
        public string ECI { get; set; }
        public string PayTime { get; set; }
        public string PaymentMethod { get; set; }
    }
}
後端建立接收付款資訊內容 API (串接藍新-付款完)
[HttpPost]
public HttpResponseMessage GetPaymentData(NewebPayReturn data)
{
    // 付款失敗跳離執行
	var response = Request.CreateResponse(HttpStatusCode.OK);
    if (!data.Status.Equals("SUCCESS")) return response;
    // 加密用金鑰
    string hashKey = "填入生成的 HashKey";
    string hashIV = "填入生成的 HashIV";
    // AES 解密
    string decryptTradeInfo = CryptoUtil.DecryptAESHex(data.TradeInfo, hashKey, hashIV);
    PaymentResult result = JsonConvert.DeserializeObject<PaymentResult>(decryptTradeInfo);
    // 取出交易記錄資料庫的訂單ID
    string[] orderNo = result.Result.MerchantOrderNo.Split('_');
    int logId = Convert.ToInt32(orderNo[1]);
    // 用取得的"訂單ID"修改資料庫此筆訂單的付款狀態為 true
    // 用取得的"訂單ID"寄出付款完成訂單成立,商品準備出貨通知信
    return response;
}
前端 使用 ReturnURL 網址夾帶的"訂單ID"向後端取得訂單資料,並顯示於畫面告知用戶
Status=SUCCESS 來判斷付款有沒有成功