介绍
微信服务商模式,可以替商户完成收款功能,商户无需自己研发即可完成收款功能。基于saas企业开发可以采用此种模式替商户完成支付流程
引入如下Nuget包依赖
<PackageReference Include="Essensoft.AspNetCore.Payment.WeChatPay" Version="2.4.6" />
<PackageReference Include="SKIT.FlurlHttpClient.Wechat.TenpayV3" Version="1.8.0" />
支付宝子商户配置
"WechatPayPartnerPayConfig": { // 微信服务商配置
"AppId": "wx*************", // 服务商appid
"MerchantId": "*********", //服务商编号
"MerchantV3Secret": "***********",
"MerchantCertSerialNumber": "***********",
"MerchantCertPrivateKey": "***********",
"MerchantAppId": "wxf***********", // 服务商公众号appid
"MerchantAppSecret": "***********", //公众号秘钥
"PayExpireTime": "35", //支付过期时间(单位:分)
"GoldPlanReceiptLink": "https://xxxxx.xxx.com/succeed" // 点金小票前端地址
},
代码实现
微信支付入口,H5支付和JSAPI支付
//微信支付
var wechatPayRequest = new WechatPayCreateRequest {
MemberCode = memberCode,
OutTradeNo = orderInfo.OutOrderId,
Body = orderInfo.ProductName,
TotalFee = (int)(orderInfo.OrderAmount * 100),
CallbackUrl = $"{_apiUrlConfigOption.NotifyUrl}/api/Cashier/wechatPayCallback",
ReturnUrl = request.ReturnUrl,
OpenId = request.OpenId,
PayType = request.PayType,
SubAppId = request.SubAppId,
};
if (request.PayType == 20) {
// 微信h5页面
wechatPayRequest.ReturnUrl += "?out_trade_no=" + orderInfo.OutOrderId;
}
_logger.LogInformation($"微信支付,调服务层基础服务接口,入参:{wechatPayRequest.ToJson()}");
var rsWechat = request.PayType == 20
? await _partnerPaymentService.WechatH5PayCreate(wechatPayRequest)
: await _partnerPaymentService.WechatJsApiPayCreate(wechatPayRequest);
_logger.LogInformation($"微信支付,调服务层基础服务接口,结果:{rsWechat?.ToJson()}");
if (rsWechat == null || rsWechat.Code != "0") {
return (false, rsWechat?.Message ?? "微信支付失败");
}
return (true, rsWechat.Data);
PartnerPaymentService.cs 服务商支付模式核心代码
/// <summary>
/// 服务商模式支付
/// </summary>
public class PartnerPaymentService : IPartnerPaymentService
{
/// <summary>
/// 实体映射
/// </summary>
protected IMapper _mapper;
private readonly ILogger _logger;
private readonly ITracer _tracer;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IAliPaymentService _aliPaymentService;
private readonly IOptions<AlipayOptions> _optionsAccessor;
private readonly IOptions<WechatPayPartnerPayConfigOption> _wechatPayPartnerPayConfig;
private readonly IOptions<AlipayPartnerPayConfigOption> _AlipayPartnerPayConfig;
private readonly IProxyHttpClient _proxyHttpClient;
private readonly AppSettings _appSettings;
private readonly ApiUrlConfigOption _apiUrlConfigOption;
/// <summary>
/// 初始化一个<see cref="PartnerPaymentService"/>类型的实例
/// </summary>
/// <param name="mapper">实体映射</param>
/// <param name="logger"></param>
/// <param name="tracer"></param>
/// <param name="aliPaymentService"></param>
/// <param name="optionsAccessor"></param>
/// <param name="apiEndpoints"></param>
/// <param name="httpContextAccessor"></param>
public PartnerPaymentService(IMapper mapper, ILogger<PartnerPaymentService> logger, ITracer tracer, IHttpContextAccessor httpContextAccessor,
IOptions<WechatPayPartnerPayConfigOption> wechatPayPartnerPayConfig,
IOptions<AlipayPartnerPayConfigOption> alipayPartnerPayConfig,
IAliPaymentService aliPaymentService,
IOptions<AlipayOptions> optionsAccessor,
IOptions<ApiUrlConfigOption> apiUrlConfigOption,
IProxyHttpClient proxyHttpClient,
AppSettings appSettings) {
_mapper = mapper;
_logger = logger;
_tracer = tracer;
_httpContextAccessor = httpContextAccessor;
_wechatPayPartnerPayConfig = wechatPayPartnerPayConfig;
_AlipayPartnerPayConfig = alipayPartnerPayConfig;
_aliPaymentService = aliPaymentService;
_optionsAccessor = optionsAccessor;
_proxyHttpClient = proxyHttpClient;
_appSettings = appSettings;
_apiUrlConfigOption = apiUrlConfigOption.Value;
}
#region 微信
/// <summary>
/// 微信h5支付
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public async Task<DataResponse<string>> WechatH5PayCreate(WechatPayCreateRequest request) {
_logger.LogInformation($"微信h5支付,入参:{request.ToJson()}");
var payConfig = await GetWechatPayConfig("WechatPayPartnerPay", request.MemberCode);
/* 平台证书管理器,具体用法请参见文档 */
var certManager = new InMemoryCertificateManager();
/* 仅列出必须配置项。也包含一些诸如超时时间、UserAgent 等的配置项 */
var options = new WechatTenpayClientOptions() {
MerchantId = payConfig.MerchantId,
MerchantV3Secret = payConfig.MerchantV3Secret,
MerchantCertSerialNumber = payConfig.MerchantCertSerialNumber,
MerchantCertPrivateKey = payConfig.MerchantCertPrivateKey,
CertificateManager = certManager
};
var client = new WechatTenpayClient(options);
var ip = _httpContextAccessor.HttpContext.Connection.RemoteIpAddress.MapToIPv4().ToString();
if (ip == "127.0.0.1") {
string hostName = Dns.GetHostName();
IPHostEntry iPHostEntry = Dns.GetHostEntry(hostName);
var addressV = iPHostEntry.AddressList.FirstOrDefault(q => q.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork);//ip4地址
ip = addressV?.ToString();
}
var payRequest = new CreatePayPartnerTransactionH5Request {
AppId = payConfig.AppId,
SubMerchantId = payConfig.SubMerchantId,
OutTradeNumber = request.OutTradeNo,
Description = request.Body,
ExpireTime = DateTime.Now.AddMinutes(payConfig.PayExpireTime),
NotifyUrl = request.CallbackUrl,
Amount = new CreatePayPartnerTransactionH5Request.Types.Amount {
Total = request.TotalFee
},
Scene = new CreatePayPartnerTransactionH5Request.Types.Scene {
ClientIp = ip,
H5 = new CreatePayTransactionH5Request.Types.Scene.Types.H5 {
Type = "Wap"
}
}
};
var response = await client.ExecuteCreatePayPartnerTransactionH5Async(payRequest);
if (response.IsSuccessful()) {
var redirectUrl = request.ReturnUrl;
if (redirectUrl.Contains("%2F")) {
redirectUrl = HttpUtility.UrlDecode(redirectUrl);
}
var responseResult = $"{response.H5Url}&redirect_url={HttpUtility.UrlEncode(request.ReturnUrl)}";
_logger.LogInformation($"微信h5支付,结果:{responseResult}");
return ResponseResult.Execute(responseResult);
}
_logger.LogInformation($"微信h5支付,结果:{response.ErrorCode}_{response.ErrorMessage}");
return new DataResponse<string> { Code = "-1", Message = response.ErrorMessage };
}
/// <summary>
/// 微信JsApi支付
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public async Task<DataResponse<string>> WechatJsApiPayCreate(WechatPayCreateRequest request) {
_logger.LogInformation($"微信JsApi支付,入参:{request.ToJson()}");
var (options, wechatConfig) = await GetWechatPayOptions(request.MemberCode);
var client = new WechatTenpayClient(options);
var payRequest = new CreatePayPartnerTransactionJsapiRequest {
AppId = wechatConfig.AppId,
SubMerchantId = wechatConfig.SubMerchantId,
OutTradeNumber = request.OutTradeNo,
Description = request.Body,
ExpireTime = DateTime.Now.AddMinutes(wechatConfig.PayExpireTime),
NotifyUrl = request.CallbackUrl,
Amount = new CreatePayPartnerTransactionJsapiRequest.Types.Amount() {
Total = request.TotalFee
},
Payer = new CreatePayPartnerTransactionJsapiRequest.Types.Payer() {
OpenId = request.OpenId
}
};
// 微信小程序支付
if (request.PayType == 40) {
payRequest.Payer.OpenId = null;
payRequest.SubAppId = request.SubAppId;
payRequest.Payer.SubOpenId = request.OpenId;
}
var response = await client.ExecuteCreatePayPartnerTransactionJsapiAsync(payRequest);
if (response.IsSuccessful()) {
IDictionary<string, string> parameter = new Dictionary<string, string>();
if (request.PayType == 40) {
// https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_sl_api.php?chapter=7_7&index=5
// 服务商模式下应为当前调起支付小程序的appid
parameter = client.GenerateParametersForJsapiPayRequest(request.SubAppId, response.PrepayId);
} else {
parameter = client.GenerateParametersForJsapiPayRequest(wechatConfig.AppId, response.PrepayId);
}
var responseResult = JsonConvert.SerializeObject(parameter);
_logger.LogInformation($"微信JsApi支付,结果:{responseResult}");
return ResponseResult.Execute(responseResult);
}
_logger.LogInformation($"微信JsApi支付,结果:{response.ErrorCode}_{response.ErrorMessage}");
return new DataResponse<string> { Code = "-1", Message = response.ErrorMessage };
}
/// <summary>
/// 微信支付查询
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public async Task<DataResponse<PayQueryResponse>> WechatPayQuery(WechatPayQueryRequest request) {
_logger.LogInformation($"微信支付查询,入参:{request.ToJson()}");
var (options, wechatConfig) = await GetWechatPayOptions(request.MemberCode);
var client = new WechatTenpayClient(options);
var payRequest = new GetPayPartnerTransactionByOutTradeNumberRequest {
SubMerchantId = wechatConfig.SubMerchantId,
OutTradeNumber = request.OutTradeNo
};
var response = await client.ExecuteGetPayPartnerTransactionByOutTradeNumberAsync(payRequest);
if (response.IsSuccessful()) {
var responseResult = new PayQueryResponse {
TradeState = response.TradeState,
PayNumber = response.TransactionId
};
_logger.LogInformation($"微信支付查询,结果:{responseResult.ToJson()}");
return ResponseResult.Execute(responseResult);
}
_logger.LogInformation($"微信支付查询,结果:{response.ErrorCode}_{response.ErrorMessage}");
return new DataResponse<PayQueryResponse> { Code = "-1", Message = response.ErrorMessage };
}
/// <summary>
/// 微信支付退款
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public async Task<DataResponse<bool>> WechatPayRefund(WechatPayRefundRequest request) {
_logger.LogInformation($"微信支付退款,入参:{request.ToJson()}");
var (options, wechatConfig) = await GetWechatPayOptions(request.MemberCode);
var client = new WechatTenpayClient(options);
var payRequest = new CreateRefundDomesticRefundRequest {
SubMerchantId = wechatConfig.SubMerchantId,
OutTradeNumber = request.OutTradeNo,
OutRefundNumber = $"{request.MemberCode}" + DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff"),
Amount = new CreateRefundDomesticRefundRequest.Types.Amount() {
Total = request.TotalFee,
Refund = request.RefundFee
},
//Reason = request.RefundDesc
};
var response = await client.ExecuteCreateRefundDomesticRefundAsync(payRequest);
if (response.IsSuccessful()) {
var responseResult = response.Status;
_logger.LogInformation($"微信支付退款,结果:{responseResult}");
return ResponseResult.Execute(true);
}
_logger.LogInformation($"微信支付退款,结果:{response.ErrorCode}_{response.ErrorMessage}");
return new DataResponse<bool> { Code = "-1", Message = response.ErrorMessage };
}
/// <summary>
/// 获得微信公众号静默登录后的openid
/// </summary>
/// <returns></returns>
public async Task<DataResponse<string>> GetWechatOpenId(GetWechatOpenIdRequest request) {
_logger.LogInformation($"获得得微信公众号静默登录后的openid流程开始");
try {
_logger.LogInformation($"获得得微信公众号静默登录后的openid,入参:{request.ToJson()}");
var payConfig = await this.GetWechatPayConfig("WechatPayPartnerPay");
var openId = string.Empty;
//从微信接口获得opeanId
var url =
$"https://api.weixin.qq.com/sns/oauth2/access_token?appid={payConfig.MerchantAppId}&secret={payConfig.MerchantAppSecret}&code={request.Code}&grant_type=authorization_code";
var proxyHttpClient = _proxyHttpClient.Create(url, HttpMethod.Get);
var rs = await proxyHttpClient.SendAsString(false);
_logger.LogInformation($"获得得微信公众号静默登录后的openid,调微信获取结果:{rs?.ToJson()}");
if (rs.Contains("openid")) {
var callData = rs.ToObject<GetWechatOpenIdModel>();
openId = callData.Openid;
}
_logger.LogInformation($"获得得微信公众号静默登录后的openid,获取的opeanId为:{openId}");
if (string.IsNullOrWhiteSpace(openId)) {
return ResponseResult.Execute(string.Empty, "-1", "没有获取到opeanId");
}
return ResponseResult.Execute(openId);
} catch (Exception e) {
_logger.LogInformation($"获得得微信公众号静默登录后的openid,系统异常:{e.Message}");
return ResponseResult.Execute(string.Empty, "-1", $"系统异常:{e.Message}");
} finally {
_logger.LogInformation("获得得微信公众号静默登录后的openid流程结束");
}
}
#endregion
#region 获取支付配置
/// <summary>
/// 支付宝服务商支付options
/// </summary>
/// <param name="tenantId"></param>
/// <returns></returns>
private async Task<(AlipayOptions, AlipayPayPartnerPayConfigModel)> GetAlipayPayOptions(string tenantId = null) {
var payConfig = await GetAlipayPayConfig("AlipayPartnerPay", tenantId);
var options = new AlipayOptions {
AppId = payConfig.AppId,
AlipayPublicKey = payConfig.AlipayPublicKey,
AppPrivateKey = payConfig.AppPrivateKey
};
return (options, payConfig);
}
/// <summary>
/// 微信服务商支付options
/// </summary>
/// <param name="tenantId"></param>
/// <returns></returns>
private async Task<(WechatTenpayClientOptions, WechatPayPartnerPayConfigModel)> GetWechatPayOptions(string tenantId = null) {
var payConfig = await GetWechatPayConfig("WechatPayPartnerPay", tenantId);
/* 平台证书管理器,具体用法请参见文档 */
var certManager = new InMemoryCertificateManager();
/* 仅列出必须配置项。也包含一些诸如超时时间、UserAgent 等的配置项 */
var options = new WechatTenpayClientOptions() {
MerchantId = payConfig.MerchantId,
MerchantV3Secret = payConfig.MerchantV3Secret,
MerchantCertSerialNumber = payConfig.MerchantCertSerialNumber,
MerchantCertPrivateKey = payConfig.MerchantCertPrivateKey,
CertificateManager = certManager
};
return (options, payConfig);
}
/// <summary>
/// 获取微信支付配置
/// </summary>
/// <param name="parameterCode">系统配置名称</param>
/// <param name="memberCode">租户编号</param>
/// <returns></returns>
public async Task<WechatPayPartnerPayConfigModel> GetWechatPayConfig(string parameterCode, string memberCode = null) {
var payConfig = new WechatPayPartnerPayConfigModel {
AppId = _wechatPayPartnerPayConfig.Value.AppId,
MerchantId = _wechatPayPartnerPayConfig.Value.MerchantId,
MerchantV3Secret = _wechatPayPartnerPayConfig.Value.MerchantV3Secret,
MerchantCertSerialNumber = _wechatPayPartnerPayConfig.Value.MerchantCertSerialNumber,
MerchantCertPrivateKey = _wechatPayPartnerPayConfig.Value.MerchantCertPrivateKey,
MerchantAppId = _wechatPayPartnerPayConfig.Value.MerchantAppId,
MerchantAppSecret = _wechatPayPartnerPayConfig.Value.MerchantAppSecret,
PayExpireTime = _wechatPayPartnerPayConfig.Value.PayExpireTime,
GoldPlanReceiptLink = _wechatPayPartnerPayConfig.Value.GoldPlanReceiptLink,
};
if (!string.IsNullOrWhiteSpace(memberCode)) {
payConfig.SubMerchantId = "******"; // 子商户号
}
return payConfig;
}
#endregion
}
WechatPaymentService.cs底层微信支付核心代码
using System;
using System.Threading.Tasks;
using Essensoft.AspNetCore.Payment.WeChatPay;
using Essensoft.AspNetCore.Payment.WeChatPay.Request;
using Essensoft.AspNetCore.Payment.WeChatPay.Response;
using ICH.Util.Extension;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Payment.WeChatPay.Model;
namespace Payment.WeChatPay
{
public class WechatPaymentService: IWechatPaymentService
{
private readonly IWeChatPayClient _client ;
public IOptions<WeChatPayOptions> OptionsAccessor { get; set; }
private readonly ILogger _logger;
public WechatPaymentService(IOptions<WeChatPayOptions> optionsAccessor, ILogger<WechatPaymentService> logger, IWeChatPayClient client)
{
OptionsAccessor = optionsAccessor;
_logger = logger;
_client = client;
}
public async Task<WeChatPayUnifiedOrderResponse> H5Pay(WeChatPayH5PayModel model)
{
_logger.LogInformation($"微信H5支付入参:{model.ToJson()}");
var request = new WeChatPayUnifiedOrderRequest()
{
Body = model.Body,
OutTradeNo = model.OutTradeNo,
TotalFee = model.TotalFee,
SpBillCreateIp = model.SpbillCreateIp,
NotifyUrl = model.NotifyUrl,
Attach = model.Attach,
TradeType = "MWEB",
TimeStart = DateTime.Now.ToString("yyyyMMddHHmmss"),
TimeExpire = DateTime.Now.AddMinutes(15).ToString("yyyyMMddHHmmss")
};
var response = await _client.ExecuteAsync(request, OptionsAccessor.Value);
_logger.LogInformation($"WeChatPayUnifiedOrderH5Pay:{response.ToJson()}");
return response;
}
public async Task<WeChatPayUnifiedOrderResponse> NativePay(WeChatPayH5PayModel model)
{
_logger.LogInformation($"微信 Native支付入参:{model.ToJson()}");
var request = new WeChatPayUnifiedOrderRequest()
{
Body = model.Body,
OutTradeNo = model.OutTradeNo,
TotalFee = model.TotalFee,
SpBillCreateIp = model.SpbillCreateIp,
NotifyUrl = model.NotifyUrl,
Attach = model.Attach,
TradeType = "NATIVE",
TimeStart = DateTime.Now.ToString("yyyyMMddHHmmss"),
TimeExpire = DateTime.Now.AddMinutes(15).ToString("yyyyMMddHHmmss")
};
var response = await _client.ExecuteAsync(request, OptionsAccessor.Value);
_logger.LogInformation($"WeChatPayUnifiedOrderNativePay:{response.ToJson()}");
return response;
}
public async Task<string> AppPay(WeChatPayAppPayModel model)
{
_logger.LogInformation($"微信APP支付入参:{model.ToJson()}");
var request = new WeChatPayUnifiedOrderRequest()
{
Body = model.Body,
OutTradeNo = model.OutTradeNo,
TotalFee = model.TotalFee,
SpBillCreateIp = model.SpbillCreateIp,
NotifyUrl = model.NotifyUrl,
TradeType = "APP",
TimeStart = DateTime.Now.ToString("yyyyMMddHHmmss"),
TimeExpire = DateTime.Now.AddMinutes(15).ToString("yyyyMMddHHmmss")
};
var response = await _client.ExecuteAsync(request, OptionsAccessor.Value);
_logger.LogInformation($"wechatAPPPayResponse:{response.ToJson()}");
if (response.ReturnCode == "SUCCESS" && response.ResultCode == "SUCCESS")
{
var req = new WeChatPayAppSdkRequest()
{
PrepayId = response.PrepayId,
};
var parameter = await _client.ExecuteAsync(req, OptionsAccessor.Value);
return JsonConvert.SerializeObject(parameter);
}
return "";
}
public async Task<(WeChatPayUnifiedOrderResponse, string)> AppletPay(WeChatPayAppletPayModel model)
{
_logger.LogInformation($"微信Applet支付入参:{model.ToJson()}");
var request = new WeChatPayUnifiedOrderRequest()
{
OpenId = model.OpenId,
Body = model.Body,
OutTradeNo = model.OutTradeNo,
TotalFee = model.TotalFee,
SpBillCreateIp = model.SpbillCreateIp,
NotifyUrl = model.NotifyUrl,
Attach = model.Attach,
TradeType = "JSAPI",
TimeStart = DateTime.Now.ToString("yyyyMMddHHmmss"),
TimeExpire = DateTime.Now.AddMinutes(15).ToString("yyyyMMddHHmmss")
};
var response = await _client.ExecuteAsync(request, OptionsAccessor.Value);
_logger.LogInformation($"wechatAppletPayResponse:{response.ToJson()}");
if (response.ReturnCode == "SUCCESS" && response.ResultCode == "SUCCESS")
{
var req = new WeChatPayJsApiSdkRequest()
{
Package = $"prepay_id={response.PrepayId}"
};
var parameter = await _client.ExecuteAsync(req, OptionsAccessor.Value);
return (response, JsonConvert.SerializeObject(parameter));
}
return (response, string.Empty);
}
public async Task<(bool, WeChatPayRefundResponse)> Refund(WeChatPayRefundModel model)
{
_logger.LogInformation($"微信退款申请入参:{model.ToJson()}");
var request = new WeChatPayRefundRequest()
{
OutRefundNo = model.OutRefundNo,
TransactionId = model.TransactionId,
OutTradeNo = model.OutTradeNo,
TotalFee = model.TotalFee,
RefundFee = model.RefundFee,
RefundDesc = model.RefundDesc,
NotifyUrl = model.NotifyUrl,
};
var response = await _client.ExecuteAsync(request, OptionsAccessor.Value);
_logger.LogInformation($"wechatRefundResponse:{response.ToJson()}");
return (response.ReturnCode == "SUCCESS" && response.ResultCode == "SUCCESS", response);
}
public async Task<WeChatPayOrderQueryResponse> Query(WeChatPayOrderQueryModel model)
{
var request = new WeChatPayOrderQueryRequest
{
TransactionId = model.TransactionId,
OutTradeNo = model.OutTradeNo
};
_logger.LogInformation($"微信查询:{request.ToJson()}");
var response = await _client.ExecuteAsync(request, OptionsAccessor.Value);
_logger.LogInformation($"wechatQueryResponse:{response.ToJson()}");
return response;
}
public async Task<WeChatPayRefundQueryResponse> RefundQuery(WeChatPayRefundQueryModel model)
{
var request = new WeChatPayRefundQueryRequest
{
RefundId = model.RefundId,
OutRefundNo = model.OutRefundNo,
TransactionId = model.TransactionId,
OutTradeNo = model.OutTradeNo
};
_logger.LogInformation($"微信退款查询:{request.ToJson()}");
var response = await _client.ExecuteAsync(request, OptionsAccessor.Value);
_logger.LogInformation($"微信退款查询返回:{response.ToJson()}");
return response;
}
}
}