前言
最近公司要求支持银联支付,首先就会去参考官方api文档,银联支付官方文档,看完后发现是.net版本的示例文档,版无.netcore版本,于是网上搜索了一下netcore版本,发现讲解的都不全或者没有netcore版本的示例,于是决定将netcore版本的对接过程记录下来
步骤
此示例未银联支付在线网关支付模式
引用github封装好的SDK处理银联支付
Nuget安装SDK
Install-Package Essensoft.AspNetCore.Payment.UnionPay -Version 2.4.3
appsettings.json 配置项
"UnionPay": {
"MerId": "ab7290058192dd0",
"SignCert": "certs/acp_test_sign.pfx",
"SignCertPassword": "000000",
"EncryptCert": "certs/acp_test_enc.cer",
"MiddleCert": "certs/acp_test_middle.cer",
"RootCert": "certs/acp_test_root.cer",
"TestMode": true,
"NotifyUrl": "http://www.xxx.com/api/PayNotify/UnionPayNotify"
},
新增配置实体类
using Essensoft.AspNetCore.Payment.UnionPay;
public class UnionPayOptionsExtend: UnionPayOptions
{
/// <summary>
/// 回调地址
/// </summary>
public string NotifyUrl { get; set; }
}
新增支付实体类
/// <summary>
/// 银联支付
/// </summary>
public class UnionPayModel
{
/// <summary>
/// 商户订单号,8-32位数字字母,不能含“-”或“_”,此处默认取demo演示页面传递的参数,可以自行定制规则
/// </summary>
[Required]
[Display(Name = "orderId")]
public string OrderId { get; set; }
/// <summary>
/// 商户发送交易时间,格式:yyyyMMddHHmmss
/// </summary>
[Required]
[Display(Name = "txnTime")]
public string TxnTime { get; set; }
/// <summary>
/// 交易金额,单位:分
/// </summary>
[Required]
[Display(Name = "txnAmt")]
public string TxnAmt { get; set; }
//[Required]
//[Display(Name = "currencyCode")]
//public string CurrencyCode { get; set; }
/// <summary>
/// 订单超时时间。
/// 超过此时间后,除网银交易外,其他交易银联系统会拒绝受理,提示超时。 跳转银行网银交易如果超时后交易成功,会自动退款,大约5个工作日金额返还到持卡人账户。
/// 此时间建议取支付时的北京时间加15分钟。
/// 超过超时时间调查询接口应答origRespCode不是A6或者00的就可以判断为失败。
/// 支付超时时间, 格式:yyyyMMddHHmmss
/// </summary>
[Display(Name = "payTimeout")]
public string PayTimeout { get; set; }
/// <summary>
/// 前台通知url,点击返回商户跳转
/// </summary>
[Display(Name = "frontUrl")]
public string FrontUrl { get; set; }
/ <summary>
/ 异步通知地址
/ </summary>
//[Required]
//[Display(Name = "backUrl")]
//public string BackUrl { get; set; }
/// <summary>
/// 保留域
/// </summary>
[JsonProperty("reserved")]
public string Reserved { get; set; }
}
/// 查询实体
public class UnionPayQueryModel
{
/// <summary>
/// 订单号
/// </summary>
[Required]
[Display(Name = "orderId")]
public string OrderId { get; set; }
/// <summary>
/// 订单交易时间 格式:yyyyMMddHHmmss
/// </summary>
public string TxnTime { get; set; }
}
新增UnionPaymentService
using Essensoft.AspNetCore.Payment.UnionPay;
using Essensoft.AspNetCore.Payment.UnionPay.Request;
using Essensoft.AspNetCore.Payment.UnionPay.Response;
using Sup.Essensoft.Service.Options;
using Sup.Essensoft.Service.UnionPay.Model;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
public class UnionPaymentService: IUnionPaymentService
{
private readonly IUnionPayClient _client;
public IOptions<UnionPayOptionsExtend> _optionsAccessor { get; set; }
private readonly ILogger _logger;
private readonly ITracer _tracer;
public UnionPaymentService(IOptions<UnionPayOptionsExtend> optionsAccessor,
ILogger<UnionPaymentService> logger,
IUnionPayClient client,
ITracer tracer)
{
_optionsAccessor = optionsAccessor;
_logger = logger;
_client = client;
_tracer = tracer;
}
/// <summary>
/// 银联网关支付
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
public async Task<UnionPayNullResponse> UnionPayGatewayPay(UnionPayModel model)
{
IScope spanScope = null;
try
{
spanScope = _tracer.BuildSpan("UnionPayGatewayPay")
.WithTag(Tags.Component, nameof(UnionPaymentService))
.WithBusinessId(model.OrderId)
.StartActive();
_logger.LogInformation($"银联支付入参:{model.ToJson()}");
var request = new UnionPayGatewayPayFrontConsumeRequest()
{
TxnType = "01",//交易类型
TxnSubType = "01",//交易子类
BizType = "000201",//业务类型
ChannelType = "07",//渠道类型
CurrencyCode = "156",//交易币种
OrderId = model.OrderId,
TxnAmt = model.TxnAmt,
TxnTime = model.TxnTime,
PayTimeOut = model.PayTimeout,
FrontUrl = model.FrontUrl,
BackUrl = _optionsAccessor.Value.NotifyUrl,
};
_logger.LogInformation($"银联入参:{request.ToJson()}");
var response = await _client.PageExecuteAsync(request, _optionsAccessor.Value);
_logger.LogInformation($"银联支付出参:{response.ToJson()}");
return response;
}
catch (Exception ex)
{
_logger.LogInformation($"银联支付异常:{ex.Message},{ex.StackTrace}");
throw ex;
}
finally
{
spanScope?.Dispose();
}
}
/// <summary>
/// 银联支付查询
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
public async Task<UnionPayGatewayPayQueryResponse> UnionPayQuery(UnionPayQueryModel model)
{
IScope spanScope = null;
try
{
spanScope = _tracer.BuildSpan("UnionPayQuery")
.WithTag(Tags.Component, nameof(UnionPaymentService))
.WithBusinessId(model.OrderId)
.StartActive();
var request = new UnionPayGatewayPayQueryRequest
{
TxnType = "00",//交易类型
TxnSubType = "00",//交易子类
BizType = "000201",//业务类型
OrderId = model.OrderId,
TxnTime = model.TxnTime,
};
_logger.LogInformation($"银联支付查询入参:{request.ToJson()}");
var response = await _client.ExecuteAsync(request, _optionsAccessor.Value);
_logger.LogInformation($"银联支付查询出参:{response.ToJson()}");
return response;
}
catch (Exception ex)
{
_logger.LogInformation($"银联支付查询异常:{ex.Message},{ex.StackTrace}");
throw ex;
}
finally
{
spanScope?.Dispose();
}
}
}
新增IUnionPaymentService
public interface IUnionPaymentService
{
/// <summary>
/// 微信Native支付
/// 微信后台系统返回链接参数code_url,商户后台系统将code_url值生成二维码图片,用户使用微信客户端扫码后发起支付。注意:code_url有效期为2小时,过期后扫码不能再发起支付
/// https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_5&index=3
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
Task<UnionPayNullResponse> UnionPayGatewayPay(UnionPayModel model);
/// <summary>
/// 银联支付查询
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
Task<UnionPayGatewayPayQueryResponse> UnionPayQuery(UnionPayQueryModel model);
}
新增PayController,处理发起支付入口
private readonly IUnionPaymentService _unionPaymentService;
/// <summary>
/// 银联网关支付
/// </summary>
/// <param name="viewMode"></param>
/// <returns></returns>
[HttpPost("UnionPayGatewayPay")]
[ProducesResponseType(typeof(string), 200)]
[Trace("orderId", "aid")]
public async Task<IActionResult> UnionPayGatewayPay([FromBody] UnionPayModel viewMode)
{
var data = await _unionPaymentService.UnionPayGatewayPay(viewMode);
if (!string.IsNullOrEmpty(data?.Body))
{
return Ok(ResponseResult.Execute(data?.Body));
}
return Ok(ResponseResult.Execute("-1", $"银联支付异常"));
}
/// <summary>
/// 银联支付查询
/// </summary>
/// <param name="viewMode"></param>
/// <returns></returns>
[HttpGet("UnionPayQuery")]
[ProducesResponseType(typeof(DataResponse), 200)]
[Trace("out_trade_no", "aid")]
public async Task<IActionResult> UnionPayQuery([FromQuery] UnionPayQueryModel viewMode)
{
var data = await _unionPaymentService.UnionPayQuery(viewMode);
if (data.RespCode == "00" && data.OrigRespCode == "00")
{
return Ok(ResponseResult.Execute(data));
}
return Ok(ResponseResult.Execute("-1", $"银联查询交易失败:{data?.RespCode},{data?.RespMsg}"));
}
新增PayNotifyController,处理异步通知
private readonly IUnionPaymentService _unionPaymentService;
/// <summary>
/// 微信支付结果通知
/// </summary>
/// <returns></returns>
[HttpPost("UnionPayNotify")]
public async Task<IActionResult> UnionPayNotify()
{
var result = UnionPayNotifyResult.Failure;
try
{
var notify = await _unionPayNotifyClient.ExecuteAsync<UnionPayGatewayPayFrontConsumeNotify>(Request, _unionPayOptionsAccessor.Value);
_logger.LogInformation("银联支付回调参数: " + notify?.ToJson());
if (notify == null)
{
_logger.LogInformation($"银联支付回调通知为空");
return NoContent();
}
_tracer.ActiveSpan.SetTag("aid", notify?.OrderId);
_logger.LogInformation("银联支付回调订单号: " + notify.OrderId);
if ("00" == notify.RespCode || "A6" == notify.RespCode)
{
_logger.LogInformation($"银联支付成功:{notify.OrderId}");
result = UnionPayNotifyResult.Success;
}
return result;
}
catch (Exception ex)
{
_logger.LogInformation($"银联支付回调通知异常:{ex.ToString()}");
}
return NoContent();
}
PostMan测试结果
{
"data": "<form id='submit' name='submit' action='https://gateway.test.95516.com/gateway/api/frontTransReq.do' method='post' style='display:none;'><input name='bizType' value='000201'/><input name='txnTime' value='20210706090137'/><input name='backUrl' value='http://m344739968.vicp.cc/api/PayNotify/UnionPayNotify'/><input name='currencyCode' value='156'/><input name='txnAmt' value='800'/><input name='txnType' value='01'/><input name='txnSubType' value='01'/><input name='channelType' value='07'/><input name='orderId' value='10221062810212809421'/><input name='frontUrl' value='http://www.baidu.com'/><input name='payTimeout' value='20210706100137'/><input name='version' value='5.1.0'/><input name='encoding' value='UTF-8'/><input name='signMethod' value='01'/><input name='accessType' value='0'/><input name='merId' value='777290058192030'/><input name='certId' value='69629715588'/><input name='signature' value='mXI/atwmc7qsErWv7ZusQma86Msl4bYle4vy/8Tr9fBYDEuljamdtzIXsR590FiMGrPaBXVK2xkIBypqc6RsbILIx9FRawbMRwAYF6GOU1aCPYOFmweOT9JgP9C31jwY5E/ooZR7w/o8rCWMuHPkxyDTmdXsQBTifvE9ac30qD5PZq0EyBB0FKX3k03j9s9190n4Q/UTwh4Xsj5uBBaflGUGF611iKvxkhF1++7WHUsrR98fXMlPbpSDq++nYiNLl6jKP/j5msxJU2mrffV2ia2o4TzuHHjoYHUhzjKvpecCoXmnkX5eqlYn6UAPBlw7u2HR9fPk4EbhLGtjNCzKow=='/><input type='submit' style='display:none;'></form><script>document.forms['submit'].submit();</script>",
"code": "0",
"message": "发起支付成功",
"messageType": 1
}
前端拿到返回结果,发起提交表单,将会跳转到银联支付收银台页面,完成支付后等待银联支付通知即可
总结
- 以上代码仅仅是核心源代码,仅供参考