源码: https://github.com/aspros-luo/Qwerty.Payment/tree/develop

支付宝支付:参考支付宝sdk及文档,https://docs.open.alipay.com/194

前言:

目前实现支付宝Native支付,手机网站支付,App支付,支付回调,退款申请,退款查询

Native支付及手机支付是由前端加基础数据传入后端,后端加签拼装成html以二维码或form表单呈现

APP支付由后端加签,返回加签结果给app,app直接调用sdk完成支付

1:设置支付需要的config信息,考虑到会有不同appId,所以需要设置appId,私钥和公钥

public static class AliPayConfig
{
public static void Init(string appId, string privateKey, string aliPublicKey, string returnUrl, string notifyUrl)
{
AppId = appId;
PrivateKey = privateKey;
AliPublicKey = aliPublicKey;
ReturnUrl = string.IsNullOrWhiteSpace(returnUrl) ? notifyUrl : returnUrl;
NotifyUrl = notifyUrl;
} public static string AppId { get; private set; }
//public static string Gateway { get; private set; } = "https://openapi.alipay.com/gateway.do";
internal static string Gateway { get; private set; } = "https://openapi.alipaydev.com/gateway.do";
public static string PrivateKey { get; private set; }
public static string AliPublicKey { get; private set; }
public static string ReturnUrl { get; private set; }
public static string NotifyUrl { get; private set; }
}

2:通用方法,组装数据,排序,加签,验签等

2.1:拼装数据类

主要用于字典排序拼装

public static string BuildParamStr(Dictionary<string, string> param)
{
if (param == null || param.Count == 0)
{
return "";
}
var ascDic = param.OrderBy(o => o.Key).ToDictionary(o => o.Key, p => p.Value);
var sb = new StringBuilder();
foreach (var item in ascDic)
{
if (!string.IsNullOrEmpty(item.Value))
{
sb.Append(item.Key).Append("=").Append(item.Value).Append("&");
}
}
return sb.ToString().Substring(0, sb.ToString().Length - 1);
}

主要用于扫码,jsapi手机网站支付拼装成一个form表单,(支付宝sdk中也有)

public static string BuildHtmlRequest(IDictionary<string, string> sParaTemp, string strMethod, string strButtonValue)
{
//待请求参数数组
var dicPara = sParaTemp;
var sbHtml = new StringBuilder();
//sbHtml.Append("<head><meta http-equiv=\"Content-Type\" content=\"text/html\" charset= \"" + charset + "\" /></head>");
sbHtml.Append("<form id='alipaysubmit' name='alipaysubmit' action='"+AliPayConfig.Gateway+"?charset=utf-8' method='" + strMethod + "'>");
foreach (var temp in dicPara)
{
sbHtml.Append("<input name='" + temp.Key + "' value='" + temp.Value + "'/>");
}
//submit按钮控件请不要含有name属性
sbHtml.Append("<input type='submit' value='" + strButtonValue + "' style='display:none;'></form>");
// sbHtml.Append("<input type='submit' value='" + strButtonValue + "'></form></div>");
//表单实现自动提交
sbHtml.Append("<script>document.forms['alipaysubmit'].submit();</script>");
return sbHtml.ToString();
}

2.2:签名类,RSA256签名,支付宝推荐rsa256签名方式,私钥是java格式,这里引用一个nuget包,转换成dotnet格式私钥

Org.BouncyCastle

/// <summary>
/// Rsa 工具类
/// </summary>
internal static class GenerateRsaAssist
{
/// <summary>
/// 加签
/// </summary>
/// <returns></returns>
public static string RasSign(string content, string privateKey, SignType signType)
{
var singerType = "";
if (signType == SignType.Rsa2)
{
singerType = "SHA256WithRSA";
}
if (signType == SignType.Rsa)
{
singerType = "SHA1withRSA";
}
var signer = SignerUtilities.GetSigner(singerType);
var privateKeyParam = (RsaPrivateCrtKeyParameters)PrivateKeyFactory.CreateKey(Convert.FromBase64String(privateKey));
signer.Init(true, privateKeyParam);
var plainBytes = Encoding.UTF8.GetBytes(content);
signer.BlockUpdate(plainBytes, 0, plainBytes.Length);
var signBytes = signer.GenerateSignature();
return Convert.ToBase64String(signBytes);
}
/// <summary>
/// 验签
/// </summary>
/// <returns></returns>
public static bool VerifySign(string content, string publicKey, string signData, SignType signType)
{
var singerType = "";
if (signType == SignType.Rsa2)
{
singerType = "SHA256WithRSA";
}
if (signType == SignType.Rsa)
{
singerType = "SHA1withRSA";
}
var signer = SignerUtilities.GetSigner(singerType);
var publicKeyParam = (RsaKeyParameters)PublicKeyFactory.CreateKey(Convert.FromBase64String(publicKey));
signer.Init(false, publicKeyParam);
var signBytes = Convert.FromBase64String(signData);
var plainBytes = Encoding.UTF8.GetBytes(content);
signer.BlockUpdate(plainBytes, 0, plainBytes.Length);
var ret = signer.VerifySignature(signBytes);
return ret;
}
}

3:基础类

通用方法基本完成后剩下的只要基础类来组装数据了

3.1:添加公共请求参数类

internal class AliPayCommonModel
{
public string app_id { get; private set; } = AliPayConfig.AppId;
public string method { get; private set; }
public string format { get; private set; } = "JSON";
public string return_url { get; private set; } = AliPayConfig.ReturnUrl;
public string charset { get; private set; } = "utf-8";
public string sign_type { get; private set; } = "RSA2";
public string timestamp { get; private set; } = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss}";
public string version { get; private set; } = "1.0";
public string notify_url { get;private set; }= AliPayConfig.NotifyUrl;
public string biz_content { get; private set; }
/// <summary>
/// 设置支付方式
/// </summary>
/// <param name="payMethod"></param>
internal void SetMethod(string payMethod)
{
method = payMethod;
}
/// <summary>
/// 设置支付主题内容
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="pay"></param>
internal void SetBizContent<T>(T pay)
{
var str = pay.GetType().GetProperties().OrderBy(o=>o.Name).Aggregate("", (current, item) => current + $"\"{item.Name}\":\"{item.GetValue(pay)}\",");
biz_content ="{"+ str.Substring(0,str.Length-1)+"}";
} //public void SetBizContent(AliRefundModel refund)
//{
// var str = refund.GetType().GetProperties().OrderBy(o => o.Name).Aggregate("", (current, item) => current + $"\"{item.Name}\":\"{item.GetValue(refund)}\",");
// biz_content = "{" + str.Substring(0, str.Length - 1) + "}";
//} //public void SetBizContent(AliRefundQueryModel refundQuery)
//{
// var str = refundQuery.GetType().GetProperties().OrderBy(o => o.Name).Aggregate("", (current, item) => current + $"\"{item.Name}\":\"{item.GetValue(refundQuery)}\",");
// biz_content = "{" + str.Substring(0, str.Length - 1) + "}";
//} }

公共参数类里包含两个方法,设置不同支付方式及设置支付主体

3.2:添加支付主体类

    public class AliPayModel
{
/// <summary>
/// 商户交易订单号
/// </summary>
public string out_trade_no { get; set; }
/// <summary>
/// 支付类型
/// </summary>
public string product_code { get; private set; } = "FAST_INSTANT_TRADE_PAY";
/// <summary>
/// 支付金额
/// </summary>
public string total_amount { get; set; }
/// <summary>
/// 标题
/// </summary>
public string subject { get; set; }
/// <summary>
/// 有效时间
/// </summary>
public string timeout_express { get; set; } = "30m";
/// <summary>
/// 设置支付方式
/// </summary>
/// <param name="code"></param>
internal void SetProductCode(string code)
{
product_code = code;
}
}

支付主体里也有一个方法设置销售产品码,用于设置商家和支付宝签约的产品码

4:添加网络请求类,没什么特别的,将键值数据传入支付宝请求网关,主要用于退款申请,及退款查询,native支付及手机网站支付都只是在后端加签sign后拼装成html输出的

    internal class HttpUtil
{
//private static readonly string _defaultUserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)"; //private static bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
//{
// return true; //总是接受
//} internal static async Task<HttpResponseMessage> CreatePostHttpResponse(string url, IDictionary<string, string> parameters)
{
var listKeyValues = parameters.Keys.Select(key => new KeyValuePair<string, string>(key, parameters[key])).ToList();
using (var client = new HttpClient())
{
var httpContent = new FormUrlEncodedContent(listKeyValues);
var response = await client.PostAsync(url, httpContent);
return response;
}
}
}

5:接口,及实现类

定义支付方式接口,目前涵盖native,手机网站,app支付,退款申请,退款查询,当面付功能暂未实现

public interface IAliPayService
{
/// <summary>
/// page支付,支付信息组成页面表单数据,用于pc支付
/// </summary>
/// <returns></returns>
AliPayRequest NativePay(AliPayModel payModel);
/// <summary>
/// app支付,app端发送加签字段,返回签名数据
/// </summary>
/// <returns></returns>
AliPayRequest AppPay(string preSign);
/// <summary>
/// jsapi支付,用于网页支付
/// </summary>
/// <returns></returns>
AliPayRequest JsApiPay(AliPayModel payModel);
/// <summary>
/// 退款申请接口,用户发起退款申请
/// </summary>
/// <returns></returns>
Task<AliRefundResponse> AliRefund(AliRefundModel refundModel);
/// <summary>
/// 退款查询接口,用于确认退款是否成功
/// </summary>
/// <returns></returns>
Task<AliRefundQueryResponse> AliRefundQuery(AliRefundQueryModel refundQueryModel);
/// <summary>
/// 支付回掉接口
/// </summary>
/// <returns></returns>
AliNotifyRequest AliNotify(Stream aliReturnData);
}

实现接口类(敲黑板,画重点)

   public class AliPayService : IAliPayService
{
public AliPayRequest NativePay(AliPayModel payModel)
{
payModel.SetProductCode("FAST_INSTANT_TRADE_PAY"); var common = new AliPayCommonModel();
common.SetMethod("alipay.trade.page.pay");
common.SetBizContent(payModel); var parameters = common.GetType().GetProperties().OrderBy(o => o.Name).ToDictionary(item => item.Name, item => item.GetValue(common).ToString());
var str = BuildData.BuildParamStr(parameters); var sign = GenerateRsaAssist.RasSign(str, AliPayConfig.PrivateKey, SignType.Rsa2);
parameters.Add("sign", sign); try
{
var from = BuildData.BuildHtmlRequest(parameters, "post", "post");
return new AliPayRequest { IsSuccess = true, PreSign = str, Sign = sign, Result = from };
}
catch (Exception e)
{
return new AliPayRequest { IsSuccess = false, PreSign = str, Sign = sign, Result = e.Message };
}
} public AliPayRequest AppPay(string preSign)
{
try
{
//payModel.SetProductCode("QUICK_MSECURITY_PAY");
//var common = new AliPayCommonModel();
//common.SetMethod("alipay.trade.app.pay");
//common.SetBizContent(payModel);
//var parameters = common.GetType().GetProperties().OrderBy(o => o.Name).ToDictionary(item => item.Name, item => item.GetValue(common).ToString());
//var str = BuildData.BuildParamStr(parameters);
var sign = GenerateRsaAssist.RasSign(preSign, AliPayConfig.PrivateKey, SignType.Rsa2);
//return UrlEncoder.Default.Encode(str)+$"&sign={sign}";
sign = UrlEncoder.Default.Encode(sign);
return new AliPayRequest { IsSuccess = true, PreSign = preSign, Sign = sign, Result = sign };
}
catch (Exception e)
{
return new AliPayRequest { IsSuccess = false, PreSign = preSign, Sign = "", Result = e.Message };
} } public AliPayRequest JsApiPay(AliPayModel payModel)
{
payModel.SetProductCode("QUICK_WAP_WAY"); var common = new AliPayCommonModel();
common.SetMethod("alipay.trade.wap.pay");
common.SetBizContent(payModel); var parameters = common.GetType().GetProperties().OrderBy(o => o.Name).ToDictionary(item => item.Name, item => item.GetValue(common).ToString());
var str = BuildData.BuildParamStr(parameters); var sign = GenerateRsaAssist.RasSign(str, AliPayConfig.PrivateKey, SignType.Rsa2);
parameters.Add("sign", sign); try
{
var from = BuildData.BuildHtmlRequest(parameters, "post", "post");
return new AliPayRequest { IsSuccess = true, PreSign = str, Sign = sign, Result = from };
}
catch (Exception e)
{
return new AliPayRequest { IsSuccess = false, PreSign = str, Sign = sign, Result = e.Message };
}
} public async Task<AliRefundResponse> AliRefund(AliRefundModel refundModel)
{
var common = new AliPayCommonModel();
common.SetMethod("alipay.trade.refund");
common.SetBizContent(refundModel); var parameters = common.GetType().GetProperties().OrderBy(o => o.Name).ToDictionary(item => item.Name, item => item.GetValue(common).ToString());
var str = BuildData.BuildParamStr(parameters); var sign = GenerateRsaAssist.RasSign(str, AliPayConfig.PrivateKey, SignType.Rsa2);
parameters.Add("sign", sign); var response = await HttpUtil.CreatePostHttpResponse(AliPayConfig.Gateway, parameters);
var result = await response.Content.ReadAsStringAsync(); var jsonResult = JsonConvert.DeserializeObject<AliRefundResponse>(result);
return jsonResult;
} public async Task<AliRefundQueryResponse> AliRefundQuery(AliRefundQueryModel refundQueryModel)
{
var common = new AliPayCommonModel();
common.SetMethod("alipay.trade.fastpay.refund.query");
common.SetBizContent(refundQueryModel);
var parameters = common.GetType().GetProperties().OrderBy(o => o.Name).ToDictionary(item => item.Name, item => item.GetValue(common).ToString());
var str = BuildData.BuildParamStr(parameters);
var sign = GenerateRsaAssist.RasSign(str, AliPayConfig.PrivateKey, SignType.Rsa2);
parameters.Add("sign", sign);
var response = await HttpUtil.CreatePostHttpResponse(AliPayConfig.Gateway, parameters);
var result = await response.Content.ReadAsStringAsync();
var jsonResult = JsonConvert.DeserializeObject<AliRefundQueryResponse>(result);
return jsonResult;
} public AliNotifyRequest AliNotify(Stream aliReturnData)
{
try
{
//获取回调参数
var s = aliReturnData;
int count;
var buffer = new byte[1024];
var builder = new StringBuilder();
while ((count = s.Read(buffer, 0, 1024)) > 0)
{
builder.Append(Encoding.UTF8.GetString(buffer, 0, count));
}
s.Flush();
s.Dispose();
//request 接收的字符串含有urlencode,这里需要decode一下
var alipayReturnData = builder.ToString().Split('&').ToDictionary(a => a.Split('=')[0], a => System.Net.WebUtility.UrlDecode(a.Split('=')[1]));
//获取sign
var sign = alipayReturnData["sign"];
//去除sign及signtype
alipayReturnData.Remove("sign");
alipayReturnData.Remove("sign_type");
//获取支付宝订单号及商户交易订单号
var tradeNo = alipayReturnData["trade_no"];
var tradeIds = alipayReturnData["out_trade_no"]; var dic = alipayReturnData.ToDictionary(d => d.Key, d => d.Value); var preSign = BuildData.BuildParamStr(dic);
//验签
var result = GenerateRsaAssist.VerifySign(preSign, AliPayConfig.AliPublicKey, sign, SignType.Rsa2); return result
?
new AliNotifyRequest { IsVerify = true, PayNo = tradeNo, TradeIds = tradeIds, PayTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), Sign = sign, Content = preSign }
:
new AliNotifyRequest { IsVerify = false, PayNo = tradeNo, TradeIds = "", PayTime = "", Sign = sign, Content = preSign };
}
catch (Exception e)
{
return new AliNotifyRequest { IsVerify = false, PayNo = "", TradeIds = "", PayTime = "", Sign = "", Content = e.Message };
}
}
}

a.接口参数主体为支付主体类,方法实现前,主体设置销售产品码

native支付:FAST_INSTANT_TRADE_PAY

手机网站支付:QUICK_WAP_WAY(旧版手机网站支付未涵盖)

b.设置公共参数,设置接口名

native支付接口名:alipay.trade.page.pay

手机网站支付接口名:alipay.trade.wap.pay

c.app支付比较特殊,需要Ios或Android将数据拼接完成后传入后端加签返回给app,再由app支付sdk调起

需要注意的一个问题是, var sign = GenerateRsaAssist.RasSign(preSign, AliPayConfig.PrivateKey, SignType.Rsa2);

加签得到的sign需要urlencode一下再返回,不然app调起会出错

sign = UrlEncoder.Default.Encode(sign);

退款神器,及退款查询也需要添加主体类,这里就不再赘述

下一篇应该补上微信支付

支付宝支付如果有遗漏内容,请告知

如果发现任何问题也烦请指正。

long may the sunshine ~!

.net core 支付宝,微信支付 一的更多相关文章

  1. iOS开发集成支付宝支付、支付宝&微信支付

    支付宝支付: 参考链接:https://www.jianshu.com/p/60175e525c0e https://blog.csdn.net/zhonggaorong/article/detail ...

  2. Android 支付宝/微信支付结果判断

    微信支付结果码 private static final int PAY_OK = 0; //交易成功 private static final int PAY_ERR = -1; //交易失败 pr ...

  3. ThinkPHP 5 整合支付宝微信支付(支付宝H5,微信H5、APP支付、公众号支付)

    因项目没有PC站所以没有写电脑网站支付. Pay.php支付控制器 <?php // +----------------------------------------------------- ...

  4. easyswoole对接支付宝,微信支付

    在easyswoole中,已经开发好了相关的支付组件,只需要引入即可: composer require easyswoole/pay pay组件支持协程 支付宝 支付方法 支付宝支付目前支持 7 种 ...

  5. .NET Core之微信支付之公众号、H5支付篇

    前言 本篇主要记录微信支付中公众号及H5支付全过程. 准备篇 公众号或者服务号(并开通微信支付功能).商户平台中开通JSAPI支付.H5支付. 配置篇 公众号或者服务号中 -------开发----- ...

  6. .NET CORE 获取微信支付回调

    1.获取微信支付的回调的数据 Stream stream = HttpContext.Request.Body; byte[] buffer = new byte[HttpContext.Reques ...

  7. 支付宝web支付

    过程 1. 用户下单 2. 商户后台产生订单 3. 请求支付宝web支付页面(将订单信息返回给用户---放在form里面---隐藏起来-----并通过脚本自动提交此form到支付宝web支付页) 4. ...

  8. .net core 支付宝,微信支付 三

    支付回调: 获取HttpRequest的body内容,之前使用Request.Form有时候数据请求不到(可能是跟.net core 版本有关?) var s = HttpRequest.Body; ...

  9. .net core 支付宝,微信支付 二

    源码: https://github.com/aspros-luo/Qwerty.Payment/tree/develop 今天开始微信支付 微信支付坑比较多,支付流程也不太一样,微信支付需要先生成预 ...

随机推荐

  1. gcc 版本

    $ gcc --versiongcc (Ubuntu 5.4.0-6kord1~16.04.4k2) 5.4.0 20160609Copyright (C) 2015 Free Software Fo ...

  2. 058.Python前端Django与Ajax

    一 Ajax简介 AJAX(Asynchronous Javascript And XML)翻译成中文就是"异步Javascript和XML".即使用Javascript语言与服务 ...

  3. 分布式存储ceph---ceph常用命令(3)

    1.查看ceph集群配置信息 ceph daemon /var/run/ceph/ceph-mon.$(hostname -s).asok config show 2.在部署节点修改了ceph.con ...

  4. 解决SecureCRTPortable和SecureFXPortable的中文乱码问题

    我们使用客户端连接Linux服务器时会出现中文乱码的问题,解决方法如下: 一.修改SecureCRTPortable的相关配置 步骤一:[选项]->[全局选项] 步骤二:[常规]->[默认 ...

  5. STM32 SWD下载口无法下载的原因和解决办法

    1.SWD的下载口在程序中被禁用,IO口被设置为普通IO口 2.芯片被锁,原因有可能是程序执行了不正确的访问导致芯片被锁 3.供电不正常 4.SWD烧了 解锁原因: 在下载程序的时候有时候会发生错误导 ...

  6. python基础之psutil模块和发邮件(smtplib和yagmail)

    除了内建的模块外,Python还有大量的第三方模块. 基本上,所有的第三方模块都会在PyPI - the Python Package Index上注册,只要找到对应的模块名字,即可用pip安装. 此 ...

  7. 6.2 gzip:压缩或解压文件

    gzip命令 用于将一个大的文件通过压缩算法(Lempel-Ziv coding(LZ77))变成一个小的文件.gzip命令不能直接压缩目录,因此目录需要先用tar打包成一个文件,然后tar再调用gz ...

  8. SSH远程主机秘钥失效的解决方法

    一.问题描述: 远程主机的SSH秘钥发生了变化,在使用SSH远程登录的时候,提示如下 [root@localhost ~]# ssh root@172.16.48.10 @@@@@@@@@@@@@@@ ...

  9. JDK5.0新特性1

    目录 静态导入 自动装箱/拆箱 for-each循环 可变参数 枚举 JDK 5.0 新特性简介 JDK 5.0 的一个重要主题就是通过新增一些特性来简化开发,这些特性包括: 静态导入 自动装箱/拆箱 ...

  10. 通过CSS设计模式搭建自己系统的CSS架构

    theme: qklhk-chocolate 传统的CSS书写风格是随意命名,堆叠样式,造成了混乱不堪的结果,复杂页面的样式书写通常会出现几百行甚至上千行的代码,CSS设计模式在实际应用中的横空出世拯 ...