.NET 前后分离 Web API 蓝新金流串接

本文将介绍在前后分离的状况下,后端如何与前端配合製作 Web API ,串接蓝新金流服务。

.NET Framework 4.7.2

开发环境

Visual Studio 2019AspNet.Mvc version=”5.2.7"AspNet.WebApi version=”5.2.7"EntityFramework version="6.1.3"

Web API 蓝新金流串接

蓝新资料

测试用帐号注册 : https://cwww.newebpay.com/ (证件可以随意传)实际用帐号注册 : https://www.newebpay.com/ (实际上线再使用)蓝新金流 API 多功能收款 MPG 文件 : https://cwww.newebpay.com/website/Page/content/download_api

参考资料

[C#] 智付通SPGateway(蓝新)金流串接 (@后端实作主要参考)

蓝新注册及设定

至蓝新测试帐号页面注册个人会员并登入,于会员中心⇒商店管理⇒开立商店,开立"网路商店"

开立商店完成后,回到商店资料设定,进入商店的详细资料页,于金流特约商店设定中,启用的项目的支付方式于本範例只启用 信用卡一次付清 及 WebATM,其它项目先设为不启用

http://img2.58codes.com/2024/20139487PJAtjEV8b1.jpg

点选生成 API串接金钥

http://img2.58codes.com/2024/201394878gKSLzi486.jpg

上传商店 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

测试状态下,付款选 WebATM 直接点选银行后送出即可进行付款测试状态下,付款选 信用卡一次付清 文件提供卡号 : 4000-2211-1111-1111,效期填大于今天,验证码 3 码乱填

后端建立蓝新回传资料栏位类别档 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 + MerchantID + Version + TradeInfo + TradeSha所以前端可用 Status=SUCCESS 来判断付款有没有成功

实际流程

http://img2.58codes.com/2024/201394871haWaGe4iD.png


关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章