一:前言
由于公司新项目(商城App)要实现银联、支付宝、微信等手机端的支付; 发现能使用的.NetCore 支付框架也还不少(跨平台和开源的威力很强大,https://github.com/search?q=alipay),Down了几个星数较多的来看,最终选用了 Roc 的 Essensoft.AspNetCore.Payment 来实现支付,
github地址为:https://github.com/Essensoft/Payment,目前的最新版本为:V2.30,选用Essensoft.AspNetCore.Payment的原因如下:
1 相对于其它几个支付应用框架 EssensoftPayment很精简、构架上类的职责很合理,使用上配置灵活。
2 基于异步的请求实现支付应用框架;
3 支持.NetCore目前最新版本;
4: 作者的支持力度很强大
基本上保持和支付平台的官方同步更新。并且在1.9X版本时对一起Request进行了重构,在使用过程中发现的一些小问题或合理建议,反馈给作者,作者都用抽空快速的解决了,这要比选用几年都不更新的框架好吧。
特别是像微信与银联之类的支付平台,支付接口变动是很正常的。比如说阿里支付2018年新申请的商户停用Md5使用RSA
5 支持国内的多种支付渠道:目前支持:支付宝(Alipay)、微信支付(WeChatPay)、QQ钱包(QPay)、京东支付(JDPay)、连连支付(LianLianPay)、银联支付(UnionPay)
二:如何用使用 Essensoft/Payment
2.1 Essensoft/Payment的下载与配置
首先建立一个基于netCore的Web项目,通过Nuget里查询Essensoft/Payment 并下载最新的版本;说明下:
Essensoft.AspNetCore.Payment.Security:用于几种支付接口里的加密解决等 (必须引用到项目中)
由于命名规则是基于Essensoft.AspNetCore.Payment 起头的,Essensoft.AspNetCore.Payment.UnionPay表示银联的支付,在使用过程中我们根据自己的需要引入相关的Nuget包
我这里引入了Essensoft.AspNetCore.Payment.UnionPay、Essensoft.AspNetCore.Payment.Alipay、Essensoft.AspNetCore.Payment.WeChatPay及Essensoft.AspNetCore.Payment.Security
2.2 建立银联支付控制器可以是Web 控制器,也可以是WebApi的控制器,我使用的类名为:UnionPayController
2.3 配置银联支付参数说明
Essensoft.AspNetCore.Payment 支持 构造函数方式和配置选项方式配置:
本文以:Option的方式在appsettings.json中配置:内容如下:
"UnionPay": {
"MerId": "8000000000001",
"AccessType": "0",
"Version": "5.1.0",
"Encoding": "UTF-8",
"SignMethod": "01",
"SignCert": "Testcert/mycret1.pfx",
"SignCertPassword": "112233",
"EncryptCert": "Testcert/acp_prod_enc.cer",
"MiddleCert": "Testcert/acp_prod_middle.cer",
"RootCert": "Testcert/acp_prod_root.cer",
"SecureKey": "112233",
"TestMode": "true"
},
"Alipay": {
"AppId": "201811XXXXXXXXXX",
"EncyptKey": "lf/XXXXXXXXXXX",
"RsaPublicKey": "MiXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"RsaPrivateKey": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
},
以上配置是测试模式的配置:银联相关的证书文件在官方DEMO有提供下载:测试账号登录地址:open.unionpay.com
生产账号登录地址:https://merchant.unionpay.com/
需要注意的时:在使用支付宝RSA验签工具时:生成的商户使用私钥和商户应用公钥要保存好,需要到银联平台上去通过“商户应用公钥”生成支付宝公钥,
即生成我们配置参数中的。RsaPublicKey,而RsaPrivateKey则是我们用支付宝RSA验签生成 商户私钥
以下是支付宝控制器的代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using BusinessServer.Order;
using Essensoft.AspNetCore.Payment.Alipay;
using Essensoft.AspNetCore.Payment.Alipay.Domain;
using Essensoft.AspNetCore.Payment.Alipay.Notify;
using Essensoft.AspNetCore.Payment.Alipay.Request;
using log4net;
using Microsoft.AspNetCore.Mvc;
using MobileWebApi.Base;
using MobileWebApi.Base.dto;
using MobileWebApi.Base.tools;
namespace PaymentGateWay
{
public class AliPayController : Controller
{
private readonly IAlipayClient _client;
private readonly IAlipayNotifyClient _notifyClient;
private IOrderServer _orderServer;
private ILog log; //Log4组件
public AliPayController(IAlipayClient client, IAlipayNotifyClient notifyClient, IOrderServer orderServer)
{
_orderServer = orderServer;
_client = client;
_notifyClient = notifyClient;
this.log = LogManager.GetLogger(Startup.repository.Name, typeof(UnionPayController));
}
/// <summary>
/// 发起支付请求:由ApiColud调用
/// </summary>
/// <param name="dto">手机端传过来的Dto包括订单编号,用户ID,token信息</param>
/// <returns></returns>
[HttpPost] //[FromBody]OrderPayDto dto
public async Task<HttpResponseMessage> AppPay(OrderPayDto dto)
{
var result = new APITipResult();//返回对象
string Msg = "";
var order = _orderServer.GetModel(new DoMain.Request.Order.OrderInfoRequest() { order_id = dto.OrderId });
var orderState = OrderCheckTools.CheckOrderState(dto, order, out Msg);//检查订单是否可以支付
if (!string.IsNullOrWhiteSpace(Msg) && !orderState) //如果orderState=false 并且出错消息不为空时提示客户端不能支付
{
result.message = Msg;
result.code = 1;
order = null;
return WebApiHelper.ToJson(result);
}
decimal payMoney = order.order_price * 100; //支付金额:按分来计算
var model = new AlipayTradeAppPayModel()
{
OutTradeNo = dto.OrderId,
Subject = dto.OrderId + "订单支付",
ProductCode = "QUICK_MSECURITY_PAY",
TotalAmount = order.order_price.ToString(),
Body = "XXXX订单支付",
};
var req = new AlipayTradeAppPayRequest();
req.SetBizModel(model);
req.SetNotifyUrl("http://Engine1024.nat123.net/AliPay/AsynNotify");
req.SetReturnUrl("http://Engine1024.nat123.net/AliPay/AsynNotify");
var response = await _client.SdkExecuteAsync(req);
result.data = response.Body;
return WebApiHelper.ToJson(result);
}
/// <summary>
/// 支付回调
/// </summary>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> AsynNotify()
{
try
{
var notify = await _notifyClient.ExecuteAsync<AlipayTradeAppPayNotify>(Request);
string json = Newtonsoft.Json.JsonConvert.SerializeObject(notify, Newtonsoft.Json.Formatting.Indented);
log.Info("Resp=" + json);
decimal order_price = decimal.Parse(notify.BuyerPayAmount);
string out_trade_no = notify.OutTradeNo; //商户订单
string trade_status = notify.TradeStatus; // "支付状态"
string trade_no = notify.TradeNo; // 支付宝交易号
if (notify.TradeStatus == "TRADE_SUCCESS")
{
//这里写支付成功的业务逻辑
}
else
{
//这里写支付失败的业务处理
}
}
catch(SystemException ex)
{
string json = Newtonsoft.Json.JsonConvert.SerializeObject(ex, Newtonsoft.Json.Formatting.Indented);
log.Error("Errors=" + json);
}
return null;
}
}
}
以下银联支付的源码
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using BusinessServer.Order;
using DoMain.Entity.Order;
using Essensoft.AspNetCore.Payment.UnionPay;
using Essensoft.AspNetCore.Payment.UnionPay.Notify;
using Essensoft.AspNetCore.Payment.UnionPay.Request;
using Essensoft.AspNetCore.Payment.UnionPay.Response;
using log4net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using MobileWebApi.Base;
using MobileWebApi.Base.dto;
using MobileWebApi.Base.tools;
namespace PaymentGateWay
{
public class UnionPayController : Controller
{
private readonly IUnionPayClient _client;
private readonly IUnionPayNotifyClient _notifyClient;
private IOrderServer _orderServer;
private ILog log;
public UnionPayController(IUnionPayClient client, IUnionPayNotifyClient notifyClient, IOrderServer orderServer)
{
_orderServer = orderServer;
_client = client;
_notifyClient = notifyClient;
this.log = LogManager.GetLogger(Startup.repository.Name, typeof(UnionPayController));
}
/// <summary>
/// 支付 [FromBody]OrderPayDto dto
/// </summary>
/// <param name="viewModel"></param>
/// <returns></returns>
[HttpPost]
public async Task<HttpResponseMessage> AppPay(OrderPayDto dto)
{
var result = new APITipResult();
string Msg = "";
try
{
var order = _orderServer.GetModel(new DoMain.Request.Order.OrderInfoRequest() { order_id = dto.OrderId });
var orderState = OrderCheckTools.CheckOrderState(dto, order, out Msg);
if (Msg != "" && !orderState)
{
result.message = Msg;
result.code = 1;
order = null;
return WebApiHelper.ToJson(result);
}
decimal payMoney =order.order_price*100;//支付金额
var request = new UnionPayMobileControlPayConsumeRequest
{
TxnType = "01",
TxnSubType = "01",
BizType = "000201",
ChannelType = "08",
OrderId = dto.OrderId,
TxnTime = System.DateTime.Now.ToString("yyyyMMddhhmmss"),
TxnAmt = ((double)(payMoney)).ToString(),
CurrencyCode = "156",
FrontUrl = "http://Engine1024.nat123.net/Payment/Unionpay/AsynNotify",
BackUrl = "http://Engine1024.nat123.net/Payment/Unionpay/AsynNotify"
};
var response = await _client.ExecuteAsync(request);
if (response.RespCode == "00" && response.Tn != "")
{
result.data = response.Tn;
}
else
{
result.data = "";
}
return WebApiHelper.ToJson(result);
}
catch (System.Exception ex)
{
string json = Newtonsoft.Json.JsonConvert.SerializeObject(ex, Newtonsoft.Json.Formatting.Indented);
log.Error("Error:" + json);
return WebApiHelper.ToJson(result);
}
}
/// <summary>
/// 网关页面支付 - 同步跳转 6226090000000048
/// </summary>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> AsynNotify()
{
try
{
var notify = await _notifyClient.ExecuteAsync<UnionPayMobileControlPayConsumeNotify>(Request);
string json = Newtonsoft.Json.JsonConvert.SerializeObject(notify, Newtonsoft.Json.Formatting.Indented);
decimal order_price = decimal.Parse(notify.TxnAmt)/100;
log.Info("order_price==" + order_price + " pay RespCode " + notify.RespCode + " queryId =" + notify.QueryId + " orderId =" + notify.OrderId + " txnAmt=" + notify.TxnAmt);
if (notify.RespCode== "00")
{
//成付成功
}
else
{
//成付失败
}
ViewData["response"] = "支付成功";
return View();
}
catch(System.Exception ex)
{
string json = Newtonsoft.Json.JsonConvert.SerializeObject(ex, Newtonsoft.Json.Formatting.Indented);
ViewData["response"] = "出现错误";
return View();
}
}
}
}
setup.cs配置:
public class Startup
{
public static ILoggerRepository repository { get; set; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
repository = LogManager.CreateRepository("NETCoreRepository");
XmlConfigurator.Configure(repository, new FileInfo("log4net.config"));
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddSingleton<IConfiguration>(Configuration);
services.AddTransient<IDapper, DapperBase>();
services.AddTransient<IOrderServer, OrderServer>();
services.AddTransient<IOrderOrderRepository<OrderInfo>, OrderOrderRepository>();
services.AddRouting();
// 引入HttpClient
services.AddHttpClient();
// 引入Payment 依赖注入
services.AddAlipay();
services.AddUnionPay();
services.AddWeChatPay();
// 在 appsettings.json 中 配置选项
services.Configure<AlipayOptions>(Configuration.GetSection("Alipay"));
services.Configure<UnionPayOptions>(Configuration.GetSection("UnionPay"));
services.Configure<WeChatPayOptions>(Configuration.GetSection("WeChatPay"));
services.AddWebEncoders(opt =>
{
opt.TextEncoderSettings = new TextEncoderSettings(UnicodeRanges.All);
});
services.AddMvc(o => o.InputFormatters.Insert(0, new HandleRequestBodyFormatter())).AddWebApiConventions().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
//AddWebApiConventions :返回HttpResponseMessage时:需要安装 nuget 包 Microsoft.AspNetCore.Mvc.WebApiCompatShim
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env )
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}