一、前言
最近也是为了新产品忙得起飞,博客都更新的慢了。新产品为了方便用户支付,需要支付宝扫码接入。这活落到了我的身上。产品是Windows系统下的桌面软件,通过软件生成二维码支付。界面以原生的MVVM编写,下面叙述一下基本的过程,做过的老司机可以直接点关闭了。
二、申请接口
申请接口是第一步,首先有这么几件事:
- 公司具有支付宝账户
- 公司具有营业资质(废话)
- 创建应用,签约电脑网站支付,手机支付,App支付。
- 创建私钥、公钥、支付宝公钥
- 配置网关及回调地址
需要注意的是以下几点:
- 创建应用时,名称不要带有“支付”、“pay”等字样,图片建议高清
- 创建应用时,签约支付需要一些申请材料,如:营业资质照片,公司照片4张,应用的介绍(名称,下载地址,公司网站是否有该应用,该应用出现支付宝支付的界面样式)
- 签约后需要审核,大致一天,(阿里确实快,腾讯微信要4天),审核通过会发送一份邮件,里面有链接,点击链接完成签约
- 创建私钥、公钥、支付宝公钥,在支付宝接口网站上有官方工具,下载使用即可
- 网关与回调地址要与公司网站形成关联,比如是二级域名;如果网关、回调地址与公司网站没什么联系,恐怕不行。
三、代码流程
有三个构成元素。客户端软件,商户服务器后台,支付宝后台
客户端软件点击“获取支付二维码”去获得一个可支付的二维码:
封装客户端的一些必要信息发送给商户服务器后台形成一个商户订单
/// <summary> /// 获取二维码信息 /// </summary> /// <param name="packageClientInfo">封装信息</param> /// <param name="serverAddress">商户产品服务器地址</param> /// <returns></returns> public static void GetQRCodeInfo(string packageClientInfo, string serverAddress, Action<string> getQRCodeAction) { if (!string.IsNullOrEmpty(packageClientInfo)) { try { HttpClient httpsClient = new HttpClient { BaseAddress = new Uri(serverAddress), Timeout = TimeSpan.FromMinutes(20) }; if (DsClientOperation.ConnectionTest(httpsClient)) { StringContent strData = new StringContent( packageClientInfo, Encoding.UTF8, RcCommonNames.JasonMediaType); string PostUrl = httpsClient.BaseAddress + "api/AlipayForProduct/GetQRCodeString"; Uri address = new Uri(PostUrl); Task<HttpResponseMessage> response = httpsClient.PostAsync(address, strData); response.ContinueWith( (postTask) => { if (postTask.IsFaulted) { throw postTask.Exception; } HttpResponseMessage postResponse = postTask.Result; postResponse.EnsureSuccessStatusCode(); var result = postResponse.Content.ReadAsStringAsync().Result; getQRCodeAction(JsonConvert.DeserializeObject<string>(result)); //注意这个委托 return result; }); } } catch { // ignored } } }
这里的委托方法是用来生成二维码的,当你从这个“api/AlipayForProduct/GetQRCodeString”返回一些字符串(result),比如返回的是:
"http://xxx.xxx.com/AlipayForProduct/SendInfoToAlipay?ordernumber=" + $"{orderNumber}";(orderNumber为商户订单号)
然后使用ThoughtWorks.QRCode.dll去生成二维码
/// <summary> /// 根据字符串得到相应的二维码 /// </summary> /// <param name="qrInfo"></param> /// <param name="productName"></param> /// <param name="version"></param> /// <returns></returns> public static Image CreateQRCodeImage(string qrInfo, string productName, string version) { try { if (!string.IsNullOrEmpty(qrInfo)) { QRCodeEncoder encoder = new QRCodeEncoder { QRCodeEncodeMode = QRCodeEncoder.ENCODE_MODE.BYTE, QRCodeScale = 4, QRCodeVersion = 0, QRCodeErrorCorrect = QRCodeEncoder.ERROR_CORRECTION.M }; //编码方式(注意:BYTE能支持中文,ALPHA_NUMERIC扫描出来的都是数字) //大小(值越大生成的二维码图片像素越高) //版本(注意:设置为0主要是防止编码的字符串太长时发生错误) //错误效验、错误更正(有4个等级) Image image = encoder.Encode(qrInfo, Encoding.GetEncoding("utf-8")); string filename = $"{productName}_{version}.png"; var userLocalPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); var docPath = Path.Combine(userLocalPath, @"YourProductQRCode"); if (!Directory.Exists(docPath)) { Directory.CreateDirectory(docPath); } string filepath = Path.Combine(docPath, filename); using (FileStream fs = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write)) { image.Save(fs, System.Drawing.Imaging.ImageFormat.Png); fs.Close(); image.Dispose(); } return image; } } catch (Exception) { return null; } return null; }
这样就产生了二维码,说白了,就是把一个服务的api由字符串变成了图片,当用户使用支付宝app去扫这个二维码时,会去请求这个api:
"http://xxx.xxx.com/AlipayForProduct/SendInfoToAlipay?ordernumber=" + $"{orderNumber}";(orderNumber为商户订单号)
public string SendInfoToAlipay() { string orderNumber = Request["orderNumber"]; if (!string.IsNullOrEmpty(orderNumber)) { var matchedItem = db.OrderInfoForProduct.FirstOrDefault(x => x.OrderNumber == orderNumber); //验证下商户服务器后台有没有这个订单 if (matchedItem != null && matchedItem.IsPaid == false) { //公共参数 var alipayServerURL = "https://openapi.alipay.com/gateway.do"; var app_id = appID; var privateKeyPem = applicationPrivateKey; var format = "json"; var version = "1.0"; var signType = "RSA2"; //请求参数 var out_trade_no = orderNumber; //商家订单号 var product_code = "FAST_INSTANT_TRADE_PAY"; //销售产品码 var total_amount = "Your Money"; //订单总金额 var subject = "Your Title"; //订单标题 var body = "Your Body"; //订单描述 IAopClient client = new DefaultAopClient( alipayServerURL, app_id, privateKeyPem, format, version, signType); var returnurl = $"http://xxx.xxx.com/AlipayForProduct/AlipayResult"; var notifyurl = $"http://xxx.xxx.com/AlipayForProduct/UpdatePayStatus";
AlipayTradeWapPayRequest requestWap = new AlipayTradeWapPayRequest { BizContent = "{" + " "body":"" + body + ""," + " "subject":"" + subject + ""," + " "out_trade_no":"" + out_trade_no + ""," + " "total_amount":" + total_amount + "," + " "product_code":"" + product_code + """ + " }" }; requestWap.SetNotifyUrl(notifyurl); //异步请求 requestWap.SetReturnUrl(returnurl); //同步请求 AlipayTradeWapPayResponse responseWap = client.pageExecute(requestWap); string divNone = "<div style='display:none'>" + responseWap.Body + "</div>"; return divNone; } } return string.Empty; }
异步请求一般需要做这么几件事:
- 用户扫码支付完之后,支付宝后台会把所有需要验证的信息发给你,除了一个参数不需要验签完,其余都需要验签;
- 如果验签成功且支付状态也是成功交易后,你需要更新商户服务器后台关于此条商户订单的状态,比如将其支付状态变成已支付,填充支付时间等等;
/// <summary> /// 回调函数 /// </summary> [HttpPost] public void UpdatePayStatus() { SortedDictionary<string, string> sPara = GetRequestPost(); if (sPara.Count > 0) { //非验签参数 var sign_type = Request.Form["sign_type"]; //接收参数并排序 var seller_id = Request.Form["seller_id"]; //卖家支付宝用户号 var trade_status = Request.Form["trade_status"]; //交易状态 var notify_time = Request.Form["notify_time"]; //通知时间 var app_id = Request.Form["app_id"]; //开发者AppId var out_trade_no = Request.Form["out_trade_no"]; //交易订单号 var total_amount = Request.Form["total_amount"]; //订单金额 var receipt_amount = Request.Form["receipt_amount"]; //实收金额 var invoice_amount = Request.Form["invoice_amount"]; //开票金额 var buyer_pay_amount = Request.Form["buyer_pay_amount"]; //付款金额 var body = Request.Form["body"]; //商品描述 var gmt_payment = Request.Form["gmt_payment"]; //交易付款时间 var tradeGuid = new Guid(out_trade_no); //验签 try { var isVerfied = AlipaySignature.RSACheckV1(sPara, alipayPublicKey, "utf-8", sign_type, false); if (isVerfied) { if (app_id == appID && seller_id == sellerID) {
var isTradeSuccess = string.Equals(trade_status, "TRADE_SUCCESS") || string.Equals(trade_status, "TRADE_FINISHED"); if (isTradeSuccess) { //商户验证逻辑及数据更新逻辑 } } } } catch (Exception) { } } else { } }
/// <summary>
/// 参数排序字典
/// </summary>
/// <returns></returns>
private SortedDictionary<string, string> GetRequestPost()
{
SortedDictionary<string, string> sArray = new SortedDictionary<string, string>();
NameValueCollection coll = Request.Form;
String[] requestItem = coll.AllKeys;
foreach (string t in requestItem)
{
sArray.Add(t, Request.Form[t]);
}
return sArray;
}
同步请求一般需要做这么几件事:
1. 当异步调用完后,如果支付成功而且商户服务器后台对此条订单号处理也正确的话;同步请求可以再做一次验证
2. 如果验证成功,跳转支付成功页面;如果失败,跳转支付失败页面。
public ActionResult AlipayResult() { SortedDictionary<string, string> sPara = GetRequestGet(); if (sPara.Count > 0) { //非验签参数 var sign_type = Request.QueryString["sign_type"]; //接收参数并排序 var seller_id = Request.QueryString["seller_id"]; //卖家支付宝用户号 var app_id = Request.QueryString["app_id"]; //开发者AppId var out_trade_no = Request.QueryString["out_trade_no"]; //交易订单号 var orderNumberGuid = new Guid(out_trade_no); try { var isVerfied = AlipaySignature.RSACheckV1(sPara, alipayPublicKey, "utf-8", sign_type, false); if (isVerfied) { if (app_id == appID && seller_id == sellerID) { //你的支付成功页面 } } } catch { //你的支付失败页面 } } else { //你的支付失败页面 } return View(); } /// <summary> /// 参数排序字典 /// </summary> /// <returns></returns> private SortedDictionary<string, string> GetRequestGet() { SortedDictionary<string, string> sArray = new SortedDictionary<string, string>(); NameValueCollection coll = Request.QueryString; String[] requestItem = coll.AllKeys; foreach (string t in requestItem) { sArray.Add(t, Request.QueryString[t]); } return sArray; }
四、结尾
参加工作以来一直主攻WPF,偶尔搞搞Web还是挺有趣的!