参考文档
盛派微信支付V3帮助文档(第一种对接方式)
一、Native支付简单介绍
1.简介与应用场景
简介:Native支付是指商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式。
应用场景:Native支付适用于PC网站、实体店单品或订单、媒体广告支付等场景
2.支付流程
简单流程:用户在登录PC网站进入商品列表点击商品选择支付,生成支付二维码,用户使用手机微信app”扫一扫“功能,扫描支付二维码唤起微信支付
二、实战案例
备注:(我这里是引用的furion框架,具体的不详细介绍了可以看看官方文档),我提供两种的对接微信支付方法,第一个是用盛派的sdk,第二个是不用盛派的sdk
1.盛派sdk对接方式
管理Nuget包安装(Senparc.Weixin、Senparc.Weixin.TenPayV3)
Startup里注册微信缓存与盛派微信服务
services.AddMemoryCache();
services.AddSenparcWeixinServices(config);
Configure里配置
//启用微信配置
var senparcSetting = App.GetOptions<SenparcSetting>();
var senparcWeixinSetting = App.GetOptions<SenparcWeixinSetting>();
var registerService = app.UseSenparcGlobal(env,
senparcSetting,
globalRegister => { })
.UseSenparcWeixin(senparcWeixinSetting, (weixinRegister, weixinSetting) =>
{
//weixinRegister.RegisterMpAccount(senparcWeixinSetting, "测试公众号");//公众号配置!可以不用管
weixinRegister.RegisterTenpayApiV3(weixinSetting, "微信支付V3");
});
以上是furion框架写法,如果不用furion可以参考盛派帮助文档,如下:
在appsettings.json中添加:(这里面的appid、密钥、商户id等都在微信支付后台获取,我这里就不做演示了,网上教程很多)
"SenparcWeixinSetting": {
"IsDebug": true,
//公众号
"Token": "#{Token}#",
"EncodingAESKey": "#{EncodingAESKey}#",
"WeixinAppId": "#{WeixinAppId}#",
"WeixinAppSecret": "#{WeixinAppSecret}#",
//微信支付V3
"TenPayV3_AppId": "#{TenPayV3_AppId}#",//appid
"TenPayV3_AppSecret": "#{TenPayV3_AppSecret}#",//app密钥
"TenPayV3_SubAppId": "#{TenPayV3_SubAppId}#",
"TenPayV3_SubAppSecret": "#{TenPayV3_SubAppSecret}#",
"TenPayV3_MchId": "#{TenPayV3_MchId}#",//商户id
"TenPayV3_SubMchId": "#{TenPayV3_SubMchId}#", //子商户,没有可留空
"TenPayV3_Key": "#{TenPayV3_Key}#",
"TenPayV3_TenpayNotify": "#{TenPayV3_TenpayNotify}#", //http://YourDomainName/TenpayApiV3/PayNotifyUrl
/* 支付证书私钥
* 1、支持明文私钥(无换行字符)
* 2、私钥文件路径(如:~/App_Data/cert/apiclient_key.pem),注意:必须放在 App_Data 等受保护的目录下,避免泄露
*/
"TenPayV3_PrivateKey": "#{TenPayV3_PrivateKey}#", //(新)证书私钥
"TenPayV3_SerialNumber": "#{TenPayV3_SerialNumber}#", //(新)证书序列号
"TenPayV3_ApiV3Key": "#{TenPayV3_APIv3Key}#", //(新)APIv3 密钥
}
新增一个控制器WXAppService添加如下代码
上面初始化添加
/// <summary>
/// 用于初始化BasePayApis
/// </summary>
private readonly BasePayApis _basePayApis;
private readonly ISenparcWeixinSettingForTenpayV3 _tenpayV3Setting;
构造函数WXAppService()里添加
_tenpayV3Setting = Senparc.Weixin.Config.SenparcWeixinSetting.TenpayV3Setting;
_basePayApis = new BasePayApis(_tenpayV3Setting);
然后就是主接口NativePayCode()
/// <summary>
/// (盛派sdk)使用 Native 支付下单
/// </summary>
/// <param name="productId"></param>
/// <param name="hc"></param>
/// <returns></returns>
public async Task<string> NativePayCode()//可以根据自己的业务传商品id等
{
string MchId = App.Configuration["SenparcWeixinSetting:TenPayV3_MchId"];//获取appsettings.json中的TenPayV3_MchId的商户id
string AppId = App.Configuration["SenparcWeixinSetting:TenPayV3_AppId"];//获取appsettings.json中的TenPayV3_AppId的APPid
var price = 100;//这里是1:100,一块钱等于一百
var name = "测试微信支付V3Native支付";
var sp_billno = string.Format("{0}{1}{2}", MchId/*10位*/, SystemTime.Now.ToString("yyyyMMddHHmmss"),TenPayV3Util.BuildRandomStr(6));//自生成的订单号传入微信支付接口,方便后续回调处理
var notifyUrl = "这里填的是你的支付回调地址(比如你的控制器是WXpayController,回调接口是PayNotifyUrl(),那回调地址就是你服务器的IP或域名加上WXpay/PayNotifyUrl),注意!Native支付的回调还需要在微信支付后台配置,微信支付-首页-产品中心-开发配置";
TransactionsRequestData requestData = new(AppId, MchId, name, sp_billno, new TenpayDateTime(DateTime.Now.AddHours(1)), null,
notifyUrl, null, new() { currency = "CNY", total = price }, null, null, null, null);
BasePayApis basePayApis = new();
var result = await basePayApis.NativeAsync(requestData);//调用微信Native支付
//进行安全签名验证
if (result.VerifySignSuccess == true)
{
//成功的业务,我是把链接直接返回给前端让前端生成二维码
return result.code_url;
}
else
{
//如果二维码链接生成失败的业务
}
return result.code_url;
}
上面接口里只是最简单的支付案例,里面可以根据自己的业务添加逻辑代码
接下来测试一下效果,重新生成一下运行
复制一下data里面的链接,打开浏览器在线二维码图片生成器_二维码扫描软件下载_联图二维码
把链接放入框内生成二维码(当然.net也有很多二维码生成程序包,只不过我懒,不想写)
如下图:
打开手机微信app扫一扫
很好,很成功,接下来就是回调方法了
备注:我翻了微信V3支付的文档,没有找到测试环境,所以我没办法测试回调,以下回调方法仅供参考!!
/// <summary>
/// (盛派sdk)weixin支付回调地址(在下单接口中设置的 notify_url)
/// </summary>
/// <returns></returns>
public async Task PayNotifyUrl()
{
try
{
//获取微信服务器异步发送的支付通知信息
var resHandler = new TenPayNotifyHandler(_httpContextAccessor.HttpContext);
var orderReturnJson = await resHandler.AesGcmDecryptGetObjectAsync<OrderReturnJson>();
//获取支付状态
string trade_state = orderReturnJson.trade_state;
//验证请求是否从微信发过来(安全)
NotifyReturnData returnData = new();
//验证可靠的支付状态
if (orderReturnJson.VerifySignSuccess == true && trade_state == "SUCCESS")
{
returnData.code = "SUCCESS";//正确的订单处理
Console.WriteLine(orderReturnJson.out_trade_no);//打印订单号
//orderReturnJson里还有很多返回的参数,可根据需求自己拿取
/* 提示:
* 1、直到这里,才能认为交易真正成功了,可以进行数据库操作,但是别忘了返回规定格式的消息!
* 2、上述判断已经具有比较高的安全性以外,还可以对访问 IP 进行判断进一步加强安全性。
* 3、下面演示的是发送支付成功的模板消息提示,非必须。
*/
}
else
{
returnData.code = "FAILD";//错误的订单处理
returnData.message = "验证失败";
//此处可以给用户发送支付失败提示等
}
}
catch (Exception ex)
{
WeixinTrace.WeixinExceptionLog(new WeixinException(ex.Message, ex));
throw;
}
}
2.OSSClientsPay对接方式
参考文档:OSS.Clients.Pay
这个也是我偶然发现的
nuget下安装命令:
Install-Package OSS.Clients.Pay.Wechat
V3版接口采用fluent方式简化了调用流程,方便扩展,只需要继承基础类BasePostReq<TReq, TResp>,和 BaseGetReq<TReq, TResp> ,使用时调用SendAsync 扩展方法,内部已经完成了需要的加密验签等处理操作
我继续延用我上面盛派写的appsettings.json里的值了
新增接口OSSWXNativePayment()
/// <summary>
/// (OSS.PayCenter)使用Native支付下单
/// </summary>
/// <returns></returns>
public async Task<string> OSSWXNativePayment()
{
string MchId = App.Configuration["SenparcWeixinSetting:TenPayV3_MchId"];//商户id
string AppId = App.Configuration["SenparcWeixinSetting:TenPayV3_AppId"];//APPid
string TenPayV3_Key = App.Configuration["SenparcWeixinSetting:TenPayV3_Key"];//V3key
string out_trade_no = SystemTime.Now.ToString("yyyyMMddHHmmss") + "123456";//订单号
WechatPayHelper.pay_config = new WechatPayConfig()
{
app_id = AppId,
mch_id = MchId,
api_v3_key = TenPayV3_Key,
cert_path = "D:\\publish\\TutorGPT\\1649499980_20230727_cert\\apiclient_cert.p12",//这个地方是微信支付后台配置的商户证书下载下来存在你本地的地址
cert_password = MchId
};
var nResp = await new WechatNativePayReq()
{
amount = new WechatPayAmount()
{
total = 1//同样为1:100
},
description = "OSS.PayCenter测试商品",
out_trade_no = out_trade_no,
notify_url = "回调地址"
}
// .SetContextConfig(new WechatPayConfig(){}) // 可以设置当前上下文的配置信息,设置后本次请求将使用此配置,方便多应用的用户
//.AddOptionalBodyPara("attach","附加数据") // 添加可选参数
.SendAsync();
if (nResp.code!="0"&&nResp.code_url==null)//失败
{
//失败逻辑
}
return nResp.code_url;//成功返回链接
}
上面cert_path参数商户证书是在微信支付后台配置,证书密码如果没在后台配置的话默认为商户id,具体的可以看微信支付文档教程,如下图
测试效果,重新生成一下运行:
复制一下data里面的链接,打开浏览器在线二维码图片生成器_二维码扫描软件下载_联图二维码
扫码如下图:
也成功了,然后就是回调了,这部分的回调是我自己捣鼓的,也是目前公司项目正在使用的
/// <summary>
/// (OSS.PayCenter)微信支付回调接口
/// </summary>
/// <returns></returns>
[AllowAnonymous]
[UnitOfWork(true)]
public async Task PostWxPayNotify()
{
try
{
string message = string.Empty;
_httpContextAccessor.Request.EnableBuffering();
_httpContextAccessor.Request.Body.Seek(0, SeekOrigin.Begin);
Log.Information(_httpContextAccessor.Request.Method);//日志
using (var reader = new StreamReader(_httpContextAccessor.Request.Body, Encoding.UTF8))
{
message = reader.ReadToEndAsync().Result;
}
//序列化为WeiXinResultDto实体类
var weiXinResult = JsonConvert.DeserializeObject<WeiXinResultDto>(message);
Log.Information($"微信支付回调通知信息为: {JsonConvert.SerializeObject(weiXinResult)}");
//按照微信的规则密文解密返回数据
var resourcestr = Dtos.AesGcm.AesGcmDecrypt(weiXinResult.resource.associated_data, weiXinResult.resource.nonce, weiXinResult.resource.ciphertext);
Log.Information($"解密后信息为: {resourcestr}");
var resourceResult = JsonConvert.DeserializeObject<resourceClass>(resourcestr);
Log.Information($"微信支付回调订单信息为: {JsonConvert.SerializeObject(resourceResult)}");
if (resourceResult.trade_state.Contains("SUCCESS"))
{
//支付成功的逻辑...
//微信支付可能返回的状态类型
//SUCCESS:支付成功
//REFUND:转入退款
//NOTPAY:未支付
//CLOSED:已关闭
//REVOKED:已撤销(付款码支付)
//USERPAYING:用户支付中(付款码支付)
//PAYERROR:支付失败(其他原因,如银行返回失败)
}
if (resourceResult.trade_state.Contains("PAYERROR"))
{
//支付失败的逻辑...
}
}
catch (Exception ex)
{
//报错记录日志
Log.Error($"回调报错信息为:{ex.Message}");
}
}
其中有<WeiXinResultDto>实体类、AesGcm解密方法类、<resourceClass>实体类,我在下方贴出代码
<WeiXinResultDto>实体类、<resourceClass>实体类、还有一些在此回调方法需要用到的实体类都在下面
/// <summary>
/// 微信支付后返回通知参数
/// </summary>
public class WeiXinResultDto
{
/// <summary>
/// 通知id(由微信生成)
/// </summary>
public string id { get; set; }
/// <summary>
/// 通知创建时间
/// </summary>
public string create_time { get; set; }
/// <summary>
/// 通知类型
/// </summary>
public string resource_type { get; set; }
/// <summary>
/// 通知数据类型
/// </summary>
public string event_type { get; set; }
/// <summary>
/// 回调摘要(示例值:支付成功)
/// </summary>
public string summary { get; set; }
/// <summary>
/// 通知数据
/// </summary>
public resource resource { get; set; }
}
/// <summary>
/// 通知数据
/// </summary>
public class resource
{
/// <summary>
/// 原始类型
/// </summary>
public string original_type { get; set; }
/// <summary>
/// 加密算法类型
/// </summary>
public string algorithm { get; set; }
/// <summary>
/// 数据密文(需要解密才能获得下方的resourceClass数据)
/// </summary>
public string ciphertext { get; set; }
/// <summary>
/// 附加数据
/// </summary>
public string associated_data { get; set; }
/// <summary>
/// 随机串
/// </summary>
public string nonce { get; set; }
}
/// <summary>
/// 测试解密串Dto
/// </summary>
public class resourcenew
{
public string ciphertext { get; set; }
public string associated_data { get; set; }
public string nonce { get; set; }
}
/// <summary>
/// ciphertext解密后的Dto
/// </summary>
public class resourceClass
{
/// <summary>
/// 商户id
/// </summary>
public string mchid { get; set; }
/// <summary>
/// appid
/// </summary>
public string appid { get; set; }
/// <summary>
/// 订单号
/// </summary>
public string out_trade_no { get; set; }
/// <summary>
/// 微信订单号(微信生成)
/// </summary>
public string transaction_id { get; set; }
/// <summary>
/// 支付方式类型
/// </summary>
public string trade_type { get; set; }
/// <summary>
/// 支付结果状态
/// </summary>
public string trade_state { get; set; }
/// <summary>
/// 支付结果摘要
/// </summary>
public string trade_state_desc { get; set; }
/// <summary>
///
/// </summary>
public string bank_type { get; set; }
/// <summary>
///
/// </summary>
public string attach { get; set; }
/// <summary>
/// 支付时间
/// </summary>
public string success_time { get; set; }
/// <summary>
/// payer(openid)
/// </summary>
public payer payer { get; set; }
/// <summary>
/// 订单信息
/// </summary>
public amount amount { get; set; }
}
/// <summary>
/// 支付人员信息
/// </summary>
public class payer
{
/// <summary>
/// openid
/// </summary>
public string openid { get; set; }
}
/// <summary>
/// 订单信息
/// </summary>
public class amount
{
/// <summary>
/// 金额
/// </summary>
public int total { get; set; }
public int payer_tota { get; set; }
/// <summary>
/// 货币种(CNY)
/// </summary>
public string currency { get; set; }
public string payer_currency { get; set; }
}
AesGcm解密方法类:
这个之前在NuGet安装Portable.BouncyCastle包
public class AesGcm
{
private static string ALGORITHM = "AES/GCM/NoPadding";
private static int TAG_LENGTH_BIT = 128;
private static int NONCE_LENGTH_BYTE = 12;
private static string AES_KEY =App.Configuration["SenparcWeixinSetting:TenPayV3_Key"];//APIv3密钥
public static string AesGcmDecrypt(string associatedData, string nonce, string ciphertext)
{
GcmBlockCipher gcmBlockCipher = new GcmBlockCipher(new AesEngine());
AeadParameters aeadParameters = new AeadParameters(
new KeyParameter(Encoding.UTF8.GetBytes(AES_KEY)),
128,
Encoding.UTF8.GetBytes(nonce),
Encoding.UTF8.GetBytes(associatedData));
gcmBlockCipher.Init(false, aeadParameters);
byte[] data = Convert.FromBase64String(ciphertext);
byte[] plaintext = new byte[gcmBlockCipher.GetOutputSize(data.Length)];
int length = gcmBlockCipher.ProcessBytes(data, 0, data.Length, plaintext, 0);
gcmBlockCipher.DoFinal(plaintext, length);
return Encoding.UTF8.GetString(plaintext);
}
}
第一次写博客如有不足请见谅,希望多多指导
如有问题可以在下方留言