.net6开发企业微信小程序支付流程 电脑版发表于:2024/4/11 15:21  >#企业微信小程序支付流程 [TOC] 注册微信公众号 ------------ tn2>首先通过 https://mp.weixin.qq.com/ 链接进行企业级的小程序注册。      tn2>然后打开自己的邮箱,点击收到的邮件的链接进行继续注册。 需要填写如下信息: * 实体类型 * 公司类型 * 公司名称 * 营业执照注册号 * 身份证上的管理员姓名 * 身份证上号码 * 管理员的手机号码 * 短信验证码 * 小程序的付费验证300元  tn2>通过这些还点击继续,这里我就不继续了。 我已经注册好了 申请微信支付商户 ------------ tn2>我们登录好后点击微信支付-->接入微信支付-->点击申请接入(也就是这里:https://pay.weixin.qq.com/index.php/core/home/login )   tn2>点击成为微信支付商户号  tn2>然后就开始填写商户信息,主要有下面: * 姓名 * 手机号 * 法人 * 营业执照 * 企业银行卡  tn2>这个只要慢慢弄一步步弄就好了。 然后关联我们的APP ID  tn2>也就是我们小程序中的AppID,复制到这里并进行提交就可以了。  开发配置 ------------ tn2>通过小程序登录后 https://mp.weixin.qq.com/ ,找到开发配置  tn2>1.需要APPID 2.生成AppSecret 3.配置服务器合法域名(前提服务器有证书,并且域名可访问) 4.添加业务域名(添加时,通过保存微信提供的txt) 5.添加消息推送(获取token和EncodingAESKey,如果提交时有系统错误就算了,这个不太重要) 6.查看商户号https://pay.weixin.qq.com/index.php/extend/pay_setting 7.开通申请API证书和APIv3密钥https://pay.weixin.qq.com/index.php/core/cert/api_cert#/  tn>主要开通证书的时候,具体可以看官网的流程:https://kf.qq.com/faq/161222NneAJf161222U7fARv.html (证书重要的在于证书序列号,和privatekey) 开通APIv3密钥的时候可以查看这个教程:https://kf.qq.com/faq/180830E36vyQ180830AZFZvu.html 创建项目 ------------ tn2>创建一个.net6的项目,添加相关的包。 ```bash <ItemGroup> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" /> <PackageReference Include="Autofac" Version="8.0.0" /> <PackageReference Include="Autofac.Extensions.DependencyInjection" Version="9.0.0" /> <PackageReference Include="SKIT.FlurlHttpClient.Wechat.TenpayV3" Version="3.1.0" /> <PackageReference Include="SKIT.FlurlHttpClient.Wechat.Api" Version="3.0.0" /> <PackageReference Include="NMemory" Version="3.1.6" /> <PackageReference Include="DistributedLock.Core" Version="1.0.6" /> <PackageReference Include="DistributedLock.FileSystem" Version="1.0.2" /> </ItemGroup> ``` tn2>在`appsettings.json`添加相关配置。 ```json { "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "TenpayOptions": { "Merchants": [ { "MerchantId": "商户号", "SecretV3": "APIv3密钥", "CertificateSerialNumber": "微信API证书的序列号", "CertificatePrivateKey": "微信API证书的apiclient_key.pem文件" } ], "SucceedNotifyUrl": "https:/你的域名/api/order/ReceiveSucceedMessage", "FailNotifyUrl": "https://你的域名/api/order/RefundSucceedMessage" }, "WechatOptions": { "Accounts": [ { "AppId": "企业微信的AppID", "AppSecret": "企业微信的AppSecret" } ], "CallbackEncodingAESKey": "企业微信的消息推送EncodingAESKey", "CallbackToken": "企业微信的消息推送token" } } ``` tn2>接下来我们要做三件事: 1.通过openid获取AccessToken去获取用户的openid,让我们知道对谁发起支付 2.后台自动刷新相关AccessToken和支付需要的相关证书 3.创建相关控制器 ### 刷新AccessToken tn2>通过微信接收相关参数。  ```csharp namespace IPayAI.WeChat.MINIPay.OptionTenpays { public partial class WechatOptions : IOptions<WechatOptions> { WechatOptions IOptions<WechatOptions>.Value => this; public Types.WechatAccount[] Accounts { get; set; } = Array.Empty<Types.WechatAccount>(); public string CallbackEncodingAESKey { get; set; } = string.Empty; public string CallbackToken { get; set; } = string.Empty; } public partial class WechatOptions { public static class Types { public class WechatAccount { public string? GhId { get; set; } public string AppId { get; set; } = string.Empty; public string AppSecret { get; set; } = string.Empty; } } } } ``` ```csharp namespace IPayAI.WeChat.MINIPay.OptionTenpays { public partial class TenpayOptions : IOptions<TenpayOptions> { TenpayOptions IOptions<TenpayOptions>.Value => this; public Types.WechatMerchant[] Merchants { get; set; } = Array.Empty<Types.WechatMerchant>(); public string SucceedNotifyUrl { get; set; } = string.Empty; public string FailNotifyUrl { get; set; } = string.Empty; } public partial class TenpayOptions { public static class Types { public class WechatMerchant { public string MerchantId { get; set; } = string.Empty; public string SecretV3 { get; set; } = string.Empty; public string CertificateSerialNumber { get; set; } = string.Empty; public string CertificatePrivateKey { get; set; } = string.Empty; } } } } ``` ```csharp Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); var builder = WebApplication .CreateBuilder(args); // Add services to the container. builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); builder.Services.AddControllers(); // 注入配置项(内容见 `appsettings.json` 文件) builder.Services.AddOptions(); builder.Services.Configure<TenpayOptions>(builder.Configuration.GetSection(nameof(TenpayOptions))); builder.Services.Configure<WechatOptions>(builder.Configuration.GetSection(nameof(WechatOptions))); ``` tn2>在`Models`文件夹下面创建`WechatAccessTokenEntity`类,用于装AccessToken的类。 ```csharp public class WechatAccessTokenEntity { /// <summary> /// 企业AppId /// </summary> public string AppId { get; set; } = string.Empty; /// <summary> /// AccessToken /// </summary> public string AccessToken { get; set; } = string.Empty; public long ExpireTimestamp { get; set; } public long UpdateTimestamp { get; set; } public long CreateTimestamp { get; set; } } ``` tn2>创建分布式锁,便于获取`AccessToken`时进行锁住其他请求来获取`AccessToken`。  ```csharp public interface IDistributedLockFactory { IDistributedLock Create(string lockName); } ``` ```csharp internal class DistributedLockFactory : IDistributedLockFactory { private readonly DirectoryInfo _lockFileDirectory = new DirectoryInfo(Environment.CurrentDirectory); public IDistributedLock Create(string lockName) { // NOTICE: // 单机演示基于文件实现分布式锁,生产项目请替换成其他实现。 return new FileDistributedLock(_lockFileDirectory, lockName); } } ``` ```csharp // 注入分布式锁 builder.Services.AddSingleton<IDistributedLockFactory, DistributedLockFactory>(); ``` tn2>写一个简单的内存仓储,用于存储`WechatAccessTokenEntity`。  ```csharp internal class GlobalDatabase { static GlobalDatabase() { Database db = new Database(); TableWechatAccessTokenEntity = db.Tables.Create<Models.WechatAccessTokenEntity, string>(e => e.AppId); } public static Table<WechatAccessTokenEntity, string> TableWechatAccessTokenEntity { get; } } ``` ```csharp public interface IWechatAccessTokenEntityRepository : IEnumerable<Models.WechatAccessTokenEntity> { void Insert(Models.WechatAccessTokenEntity entity); void Update(Models.WechatAccessTokenEntity entity); void Delete(Models.WechatAccessTokenEntity entity); } ``` ```csharp public class WechatAccessTokenEntityRepository : IWechatAccessTokenEntityRepository { public void Insert(Models.WechatAccessTokenEntity entity) { entity.CreateTimestamp = DateTimeOffset.Now.ToUnixTimeSeconds(); entity.UpdateTimestamp = entity.CreateTimestamp; GlobalDatabase.TableWechatAccessTokenEntity.Insert(entity); } public void Update(WechatAccessTokenEntity entity) { entity.UpdateTimestamp = DateTimeOffset.Now.ToUnixTimeSeconds(); GlobalDatabase.TableWechatAccessTokenEntity.Update(entity); } public void Delete(WechatAccessTokenEntity entity) { GlobalDatabase.TableWechatAccessTokenEntity.Delete(entity); } IEnumerator<WechatAccessTokenEntity> IEnumerable<Models.WechatAccessTokenEntity>.GetEnumerator() { return GlobalDatabase.TableWechatAccessTokenEntity.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GlobalDatabase.TableWechatAccessTokenEntity.GetEnumerator(); } } ``` ```csharp // 注入仓储类 builder.Services.AddSingleton<IWechatAccessTokenEntityRepository, WechatAccessTokenEntityRepository>(); ``` tn2>创建相关微信API请求工厂接口并进行依赖注入。 ```csharp public interface IWechatApiClientFactory { WechatApiClient Create(string appId); } ``` ```csharp internal partial class WechatApiClientFactory : IWechatApiClientFactory { private readonly IHttpClientFactory _httpClientFactory; private readonly WechatOptions _wechatOptions; public WechatApiClientFactory( IHttpClientFactory httpClientFactory, IOptions<WechatOptions> wechatOptions) { _httpClientFactory = httpClientFactory; _wechatOptions = wechatOptions.Value; } public WechatApiClient Create(string appId) { // NOTICE: // 这里的工厂方法是为了演示多租户而存在的,可根据 AppId 生成不同的 API 客户端。 // 如果你的项目只存在唯一一个租户,那么直接注入 `WechatApiClient` 即可。 var wechatAccountOptions = _wechatOptions.Accounts?.FirstOrDefault(e => string.Equals(appId, e.AppId)); if (wechatAccountOptions == null) throw new Exception("未在配置项中找到该 AppId 对应的微信账号。"); var wechatApiClientOptions = new WechatApiClientOptions() { AppId = wechatAccountOptions.AppId, AppSecret = wechatAccountOptions.AppSecret, PushEncodingAESKey = _wechatOptions.CallbackEncodingAESKey, PushToken = _wechatOptions.CallbackToken }; var wechatApiClient = WechatApiClientBuilder.Create(wechatApiClientOptions) .UseHttpClient(_httpClientFactory.CreateClient(), disposeClient: false) .Build(); return wechatApiClient; } } ``` ```csharp // 注入工厂 HTTP 客户端 builder.Services.AddHttpClient(); builder.Services.AddSingleton<Services.HttpClients.IWechatApiClientFactory, Services.HttpClients.Implements.WechatApiClientFactory>(); ``` tn2>自动创建后台刷新AccessToken的服务 ```csharp internal class WechatAccessTokenRefreshingBackgroundService : BackgroundService { private readonly ILogger _logger; private readonly WechatOptions _wechatOptions; private readonly DistributedLock.IDistributedLockFactory _distributedLockFactory; private readonly HttpClients.IWechatApiClientFactory _wechatApiClientFactory; private readonly Repositories.IWechatAccessTokenEntityRepository _wechatAccessTokenEntityRepository; public WechatAccessTokenRefreshingBackgroundService( ILoggerFactory loggerFactory, IOptions<WechatOptions> wechatOptions, DistributedLock.IDistributedLockFactory distributedLockFactory, HttpClients.IWechatApiClientFactory wechatApiClientFactory, Repositories.IWechatAccessTokenEntityRepository wechatAccessTokenEntityRepository) { _logger = loggerFactory.CreateLogger(GetType()); _wechatOptions = wechatOptions.Value; _distributedLockFactory = distributedLockFactory; _wechatApiClientFactory = wechatApiClientFactory; _wechatAccessTokenEntityRepository = wechatAccessTokenEntityRepository; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { IList<Task> tasks = new List<Task>(); foreach (var wechatAccount in _wechatOptions.Accounts) { Task task = TryRefreshWechatAccessTokenAsync(wechatAccount.AppId, stoppingToken); tasks.Add(task); } await Task.WhenAll(tasks); await Task.Delay(1 * 60 * 1000); // 每隔 1 分钟轮询刷新 } } private async Task TryRefreshWechatAccessTokenAsync(string appId, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(appId)) return; // 无效参数 var entity = _wechatAccessTokenEntityRepository.FirstOrDefault(e => e.AppId == appId); if (entity?.ExpireTimestamp > DateTimeOffset.Now.ToUnixTimeSeconds()) return; // AccessToken 未过期 var locker = _distributedLockFactory.Create("accessToken:" + appId); using var lockHandler = await locker.TryAcquireAsync(TimeSpan.FromSeconds(15), cancellationToken); if (lockHandler == null) return; // 未取得锁 var client = _wechatApiClientFactory.Create(appId); var request = new CgibinTokenRequest(); var response = await client.ExecuteCgibinTokenAsync(request, cancellationToken); if (!response.IsSuccessful()) { _logger.LogWarning( "刷新 AppId 为 {0} 微信 AccessToken 失败(状态码:{1},错误代码:{2},错误描述:{3})。", appId, response.GetRawStatus(), response.ErrorCode, response.ErrorMessage ); return; // 请求失败 } long nextExpireTimestamp = DateTimeOffset.Now .AddSeconds(response.ExpiresIn) .AddMinutes(-10) .ToUnixTimeSeconds(); // 提前十分钟过期,以便于系统能及时刷新,防止因在过期临界点时出现问题 if (entity == null) { entity = new Models.WechatAccessTokenEntity() { AppId = appId, AccessToken = response.AccessToken, ExpireTimestamp = nextExpireTimestamp }; _wechatAccessTokenEntityRepository.Insert(entity); } else { entity.AccessToken = response.AccessToken; entity.ExpireTimestamp = nextExpireTimestamp; _wechatAccessTokenEntityRepository.Update(entity); } _logger.LogInformation("刷新 AppId 为 {0} 的微信 AccessToken 成功。", appId); } } ``` ```csharp builder.Services.AddHostedService<Services.BackgroundServices.WechatAccessTokenRefreshingBackgroundService>(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); ``` tn2>创建获取微信用户OpenId的控制器 ```csharp [ApiController] [Route("api/wxuser")] public class WxUserController : ControllerBase { private readonly ILogger _logger; readonly IWechatApiClientFactory _httpfactory; private readonly WechatOptions _wechatOptions; private readonly IWechatAccessTokenEntityRepository _wechatAccessTokenEntityRepository; public WxUserController( ILoggerFactory loggerFactory, IOptions<WechatOptions> wechatOptions, IWechatApiClientFactory httpfactory, IWechatAccessTokenEntityRepository wechatAccessTokenEntityRepository ) { _wechatOptions = wechatOptions.Value; _wechatAccessTokenEntityRepository = wechatAccessTokenEntityRepository; _httpfactory = httpfactory; _logger = loggerFactory.CreateLogger(GetType()); } [HttpGet("{code}")] public async Task<IActionResult> Get(string code) { //读取微信支付配置文件 var tenpayAccountOptions = _wechatOptions.Accounts?.FirstOrDefault(); //创建微信支付客户端 var client = _httpfactory.Create(tenpayAccountOptions.AppId); var entity = _wechatAccessTokenEntityRepository.FirstOrDefault(e => e.AppId == tenpayAccountOptions.AppId); var response = await client.ExecuteSnsJsCode2SessionAsync(new SnsJsCode2SessionRequest() { JsCode = code, AccessToken = entity.AccessToken }); if (!response.IsSuccessful()) { _logger.LogWarning( "获取用户基本信息失败(状态码:{0},错误代码:{1},错误描述:{2})。", response.GetRawStatus(), response.ErrorCode, response.ErrorMessage ); } return new JsonResult(response); } } ``` ### 微信小程序支付与退款 tn2>创建支付类和退款类。 ```csharp public class CreateOrderByJsapiRequest { /// <summary> /// 商户号 /// </summary> public string MerchantId { get; set; } = default!; /// <summary> /// 企业appid /// </summary> public string AppId { get; set; } = default!; /// <summary> /// 用户Openid /// </summary> public string OpenId { get; set; } = default!; // NOTICE: // 单机演示时金额来源于客户端请求,生产项目请改为服务端计算生成,切勿依赖客户端提供的金额结果。 public int Amount { get; set; } } ``` ```csharp public class CreateRefundRequest { /// <summary> /// 商户号 /// </summary> public string MerchantId { get; set; } = default!; /// <summary> /// 订单号,商家自定义的订单号 /// </summary> public string TransactionId { get; set; } = default!; // NOTICE: // 单机演示时金额来源于客户端请求,生产项目请改为服务端计算生成,切勿依赖客户端提供的金额结果。 public int OrderAmount { get; set; } /// <summary> /// 需要退还的金额 /// </summary> public int RefundAmount { get; set; } } ``` tn2>创建支付需要的证书管理 ```csharp public interface IWechatTenpayCertificateManagerFactory { ICertificateManager Create(string merchantId); } ``` ```csharp internal partial class WechatTenpayCertificateManagerFactory : IWechatTenpayCertificateManagerFactory { private readonly ConcurrentDictionary<string, ICertificateManager> _dict; public WechatTenpayCertificateManagerFactory() { _dict = new ConcurrentDictionary<string, ICertificateManager>(); } public ICertificateManager Create(string merchantId) { return _dict.GetOrAdd(merchantId, new InMemoryCertificateManager()); } } ``` tn2>创建支付需要的证书请求的请求工厂类。 ```csharp public interface IWechatTenpayClientFactory { WechatTenpayClient Create(string merchantId); } ``` ```csharp internal partial class WechatTenpayClientFactory : IWechatTenpayClientFactory { private readonly IHttpClientFactory _httpClientFactory; private readonly TenpayOptions _tenpayOptions; private readonly IWechatTenpayCertificateManagerFactory _tenpayCertificateManagerFactory; public WechatTenpayClientFactory( IHttpClientFactory httpClientFactory, IOptions<TenpayOptions> tenpayOptions, IWechatTenpayCertificateManagerFactory tenpayCertificateManagerFactory) { _httpClientFactory = httpClientFactory; _tenpayOptions = tenpayOptions.Value; _tenpayCertificateManagerFactory = tenpayCertificateManagerFactory; } public WechatTenpayClient Create(string merchantId) { var tenpayMerchantConfig = _tenpayOptions.Merchants?.FirstOrDefault(e => string.Equals(merchantId, e.MerchantId)); if (tenpayMerchantConfig == null) throw new Exception("未在配置项中找到该 MerchantId 对应的微信商户号。"); var wechatTenpayClientOptions = new WechatTenpayClientOptions() { MerchantId = tenpayMerchantConfig.MerchantId, MerchantV3Secret = tenpayMerchantConfig.SecretV3, MerchantCertificateSerialNumber = tenpayMerchantConfig.CertificateSerialNumber, MerchantCertificatePrivateKey = tenpayMerchantConfig.CertificatePrivateKey, PlatformCertificateManager = _tenpayCertificateManagerFactory.Create(tenpayMerchantConfig.MerchantId), AutoEncryptRequestSensitiveProperty = false, AutoDecryptResponseSensitiveProperty = false }; var wechatTenpayClient = WechatTenpayClientBuilder.Create(wechatTenpayClientOptions) .UseHttpClient(_httpClientFactory.CreateClient(), disposeClient: false) .Build(); return wechatTenpayClient; } } ``` ```csharp builder.Services.AddHostedService<Services.BackgroundServices.TenpayCertificateRefreshingBackgroundService>(); ``` tn2>创建跨域设置 ```csharp public static class NetCoreExtend { public static string corsname = "MyAllowSpecificOrigins"; public static void AddMyServiceCors(this IServiceCollection services) { services.AddCors(options => { options.AddPolicy(name: corsname, builder => { builder.AllowAnyOrigin() .AllowAnyHeader() .AllowAnyMethod(); }); }); } public static void UseMyServiceCors(this WebApplication app) { app.UseCors(corsname); } } ``` ```csharp // 跨域设置 builder.Services.AddMyServiceCors(); var app = builder.Build(); app.UseSwagger(); app.UseSwaggerUI(); app.UseMyServiceCors(); app.UseHttpsRedirection(); app.UseAuthorization(); var s = File.ReadAllText(Path.Combine(app.Environment.ContentRootPath, "z3TAYpMYCs.txt")); // 验证业务域名 app.MapGet("z3TAYpMYCs.txt",content => content.Response.WriteAsync(s)); app.MapControllers(); app.Run(); ``` tn2>创建支付控制器 ```csharp [ApiController] [Route("api/order")] public class TenpayOrderController : ControllerBase { private readonly ILogger _logger; private readonly TenpayOptions _tenpayOptions; private readonly Services.HttpClients.IWechatTenpayClientFactory _wechatTenpayClientFactory; public TenpayOrderController( ILoggerFactory loggerFactory, IOptions<TenpayOptions> tenpayOptions, Services.HttpClients.IWechatTenpayClientFactory wechatTenpayClientFactory) { _logger = loggerFactory.CreateLogger(GetType()); _tenpayOptions = tenpayOptions.Value; _wechatTenpayClientFactory = wechatTenpayClientFactory; } /// <summary> /// 发起订单操作 /// </summary> /// <param name="requestModel"></param> /// <returns></returns> [HttpPost] [Route("jsapi")] public async Task<IActionResult> CreateOrderByJsapi([FromBody] Models.CreateOrderByJsapiRequest requestModel) { var client = _wechatTenpayClientFactory.Create(requestModel.MerchantId); var request = new CreatePayTransactionJsapiRequest() { OutTradeNumber = "SAMPLE_OTN_" + DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff"), AppId = requestModel.AppId, Description = "演示订单", NotifyUrl = _tenpayOptions.SucceedNotifyUrl, Amount = new CreatePayTransactionJsapiRequest.Types.Amount() { Total = requestModel.Amount }, Payer = new CreatePayTransactionJsapiRequest.Types.Payer() { OpenId = requestModel.OpenId } }; var response = await client.ExecuteCreatePayTransactionJsapiAsync(request, cancellationToken: HttpContext.RequestAborted); if (!response.IsSuccessful()) { _logger.LogWarning( "JSAPI 下单失败(状态码:{0},错误代码:{1},错误描述:{2})。", response.GetRawStatus(), response.ErrorCode, response.ErrorMessage ); return new JsonResult(response); } //传入小程序的appid及微信返回的预支付ID获取想要返回给前端的数据 var paramMap = client.GenerateParametersForJsapiPayRequest(request.AppId, response.PrepayId); return Ok(new { orderrequest = request, wxparam = paramMap }); } /// <summary> /// 查询订单的支付状态根据支付单号查询 /// </summary> /// <returns></returns> [HttpGet("{OutTradeNumber}")] public async Task<IActionResult> QueryDisposeOrderAlipayState(string OutTradeNumber) { //读取微信支付配置文件 var tenpayAccountOptions = _tenpayOptions.Merchants?.FirstOrDefault(); //创建微信支付客户端 var client = _wechatTenpayClientFactory.Create(tenpayAccountOptions.MerchantId); //创建请求数据 var request = new GetPayTransactionByOutTradeNumberRequest(); request.MerchantId = tenpayAccountOptions.MerchantId;//这里是商户号 request.OutTradeNumber = OutTradeNumber; //发起请求 var response = await client.ExecuteGetPayTransactionByOutTradeNumberAsync(request); if (response.IsSuccessful()) { var eventType = response.TradeState?.ToUpper(); //当支付支付成功 if (eventType == "SUCCESS") { //处理内部业务 //微信支付订单号 var payno = response.TransactionId; //支付时间 var paytime = ((DateTimeOffset)response.SuccessTime).DateTime; _logger.LogInformation("查询到微信支付推送的订单支付成功,支付时间:{0}", ((DateTimeOffset)response.SuccessTime).DateTime); //实际支付金额 var paymoney = (decimal)response.Amount.PayerTotal / 100; _logger.LogInformation("查询到微信支付推送的订单支付成功,支付金额 单位分:{0}", response.Amount.PayerTotal); return Ok("状态码:" + eventType + ",消息:" + response.TradeStateDescription); } else//当查询到的状态是支付不成功 { //处理系统内部业务 return Ok("状态码:" + eventType + ",消息:" + response.TradeStateDescription); } } else { return Ok("查询失败 状态码:" + response.GetRawStatus() + ",错误代码:" + response.ErrorCode + ",错误描述" + response.ErrorMessage); } } /// <summary> /// 支付成功回调接口 /// </summary> /// <param name="dto"></param> /// <returns></returns> [HttpPost("ReceiveSucceedMessage")] public async Task<IActionResult> ReceiveSucceedMessage( [FromHeader(Name = "Wechatpay-Timestamp")] string timestamp, [FromHeader(Name = "Wechatpay-Nonce")] string nonce, [FromHeader(Name = "Wechatpay-Signature")] string signature, [FromHeader(Name = "Wechatpay-Serial")] string serialNumber) { //读取微信支付配置文件 var tenpayAccountOptions = _tenpayOptions.Merchants?.FirstOrDefault(); using var reader = new StreamReader(Request.Body, Encoding.UTF8); string content = await reader.ReadToEndAsync(); _logger.LogInformation("接收到微信支付推送的数据:{0}", content); var client = _wechatTenpayClientFactory.Create(tenpayAccountOptions.MerchantId); bool valid = client.VerifyEventSignature( webhookTimestamp: timestamp, webhookNonce: nonce, webhookBody: content, webhookSignature: signature, webhookSerialNumber: serialNumber ); //验证签名 if (!valid) { // NOTICE: // 需提前注入 CertificateManager、并下载平台证书,才可以使用扩展方法执行验签操作。 // 请参考本示例项目 TenpayCertificateRefreshingBackgroundService 后台任务中的相关实现。 // 有关 CertificateManager 的完整介绍请参阅《开发文档 / 基础用法 / 如何验证回调通知事件签名?》。 // 后续如何解密并反序列化,请参阅《开发文档 / 基础用法 / 如何解密回调通知事件中的敏感数据?》。 _logger.LogInformation("验签失败", content); return new JsonResult(new { code = "FAIL", message = "验签失败" }); } //解密数据 var callbackModel = client.DeserializeEvent(content); var eventType = callbackModel.EventType?.ToUpper(); //当支付成功 if (eventType == "TRANSACTION.SUCCESS") { //处理自己系统的业务 var callbackResource = client.DecryptEventResource<TransactionResource>(callbackModel); _logger.LogInformation("接收到微信支付推送的订单支付成功通知,商户订单号:{0}", callbackResource.OutTradeNumber); _logger.LogInformation("接收到微信支付推送的订单支付成功通知,微信支付订单号:{0}", callbackResource.TransactionId); //订单号 var OutTradeNumber = callbackResource.OutTradeNumber; //微信支付订单号 var Payno = callbackResource.TransactionId; //支付时间 var Paytime = callbackResource.SuccessTime.DateTime; _logger.LogInformation("接收到微信支付推送的订单支付成功通知,支付时间:{0}", callbackResource.SuccessTime.DateTime); //实际支付金额 var Paymoney = (decimal)callbackResource.Amount.PayerTotal;//单位是分 _logger.LogInformation("接收到微信支付推送的订单支付成功通知,支付金额 单位分:{0}", callbackResource.Amount.PayerTotal); return new JsonResult(new { code = "SUCCESS", message = "支付成功" }); } else { // 其他情况略 _logger.LogInformation("支付回调发生严重错误eventType不等于TRANSACTION.SUCCESS:{0}", eventType); return new JsonResult(new { code = "FAIL", message = "eventType不等于TRANSACTION.SUCCESS" }); } } /// <summary> /// 退款成功回调接口 /// </summary> /// <param name="dto"></param> /// <returns></returns> [HttpPost("RefundSucceedMessage")] public async Task<IActionResult> RefundSucceedMessage( [FromHeader(Name = "Wechatpay-Timestamp")] string timestamp, [FromHeader(Name = "Wechatpay-Nonce")] string nonce, [FromHeader(Name = "Wechatpay-Signature")] string signature, [FromHeader(Name = "Wechatpay-Serial")] string serialNumber ) { //读取微信支付配置文件 var tenpayAccountOptions = _tenpayOptions.Merchants?.FirstOrDefault(); using var reader = new StreamReader(Request.Body, Encoding.UTF8); string content = await reader.ReadToEndAsync(); _logger.LogInformation("接收到微信支付推送的退款通知数据:{0}", content); var client = _wechatTenpayClientFactory.Create(tenpayAccountOptions.MerchantId); bool valid = client.VerifyEventSignature( webhookTimestamp: timestamp, webhookNonce: nonce, webhookBody: content, webhookSignature: signature, webhookSerialNumber: serialNumber ); //验证签名 if (!valid) { // NOTICE: // 需提前注入 CertificateManager、并下载平台证书,才可以使用扩展方法执行验签操作。 // 请参考本示例项目 TenpayCertificateRefreshingBackgroundService 后台任务中的相关实现。 // 有关 CertificateManager 的完整介绍请参阅《开发文档 / 基础用法 / 如何验证回调通知事件签名?》。 // 后续如何解密并反序列化,请参阅《开发文档 / 基础用法 / 如何解密回调通知事件中的敏感数据?》。 _logger.LogInformation("验签失败", content); return new JsonResult(new { code = "FAIL", message = "验签失败" }); } var callbackModel = client.DeserializeEvent(content); var eventType = callbackModel.EventType?.ToUpper(); //解密数据 var callbackResource = client.DecryptEventResource<RefundResource>(callbackModel); //退款成功通知 if (eventType == "REFUND.SUCCESS") { //获取状态码 var State = callbackResource.RefundStatus?.ToUpper(); //订单号 var OrderId = long.Parse(callbackResource.OutTradeNumber); //退款单号 var OrderRefId = long.Parse(callbackResource.OutRefundNumber); //微信支付的退款单号 var WxOrderRefId = callbackResource.RefundId; //当退款成功 if (State == "SUCCESS") { //处理自己系统内部业务 //退款成功时间 var SuccessTime = ((DateTimeOffset)callbackResource.SuccessTime).DateTime; //退款成功金额 单位分 var Refund = (decimal)callbackResource.Amount.Refund; _logger.LogInformation("退款成功通知:{0}", callbackResource); return new JsonResult(new { code = "SUCCESS", message = "成功" }); } else { //当不成功的时候处理业务 _logger.LogInformation("退款成功通知:{0}", callbackResource); return new JsonResult(new { code = "SUCCESS", message = "成功" }); } } else//其他情况重新查询退款单 { //其他情况自己处理内部系统业务 return new JsonResult(new { code = "SUCCESS", message = "成功" }); } } [HttpPost] [Route("getconfig")] public async Task<IActionResult> GetConfig() { return Ok(_tenpayOptions); } } ``` tn2>创建退款控制器 ```csharp [ApiController] [Route("api/refund")] public class TenpayRefundController : ControllerBase { private readonly ILogger _logger; private readonly TenpayOptions _tenpayOptions; private readonly Services.HttpClients.IWechatTenpayClientFactory _wechatTenpayClientFactory; public TenpayRefundController( ILoggerFactory loggerFactory, IOptions<TenpayOptions> tenpayOptions, Services.HttpClients.IWechatTenpayClientFactory wechatTenpayClientFactory) { _logger = loggerFactory.CreateLogger(GetType()); _tenpayOptions = tenpayOptions.Value; _wechatTenpayClientFactory = wechatTenpayClientFactory; } [HttpPost] [Route("")] public async Task<IActionResult> CreateRefund([FromBody] Models.CreateRefundRequest requestModel) { var client = _wechatTenpayClientFactory.Create(requestModel.MerchantId); var request = new CreateRefundDomesticRefundRequest() { // TransactionId = requestModel.TransactionId, OutTradeNumber = requestModel.TransactionId, OutRefundNumber = "SAMPLE_ORN_" + DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff"), Amount = new CreateRefundDomesticRefundRequest.Types.Amount() { Total = requestModel.OrderAmount, Refund = requestModel.RefundAmount }, Reason = "示例退款", NotifyUrl = _tenpayOptions.FailNotifyUrl }; var response = await client.ExecuteCreateRefundDomesticRefundAsync(request, cancellationToken: HttpContext.RequestAborted); if (!response.IsSuccessful()) { _logger.LogWarning( "申请退款失败(状态码:{0},错误代码:{1},错误描述:{2})。", response.GetRawStatus(), response.ErrorCode, response.ErrorMessage ); } return new JsonResult(response); } } ``` tn2>创建接收通知的控制器 ```csharp [ApiController] [Route("api/notify")] public class TenpayNotifyController : ControllerBase { private readonly ILogger _logger; private readonly Services.HttpClients.IWechatTenpayClientFactory _wechatTenpayClientFactory; public TenpayNotifyController( ILoggerFactory loggerFactory, Services.HttpClients.IWechatTenpayClientFactory wechatTenpayClientFactory) { _logger = loggerFactory.CreateLogger(GetType()); _wechatTenpayClientFactory = wechatTenpayClientFactory; } [HttpPost] [Route("m-{merchant_id}/message-push")] public async Task<IActionResult> ReceiveMessage( [FromRoute(Name = "merchant_id")] string merchantId, [FromHeader(Name = "Wechatpay-Timestamp")] string timestamp, [FromHeader(Name = "Wechatpay-Nonce")] string nonce, [FromHeader(Name = "Wechatpay-Signature")] string signature, [FromHeader(Name = "Wechatpay-Serial")] string serialNumber) { using var reader = new StreamReader(Request.Body, Encoding.UTF8); string content = await reader.ReadToEndAsync(); _logger.LogInformation("接收到微信支付推送的数据:{0}", content); var client = _wechatTenpayClientFactory.Create(merchantId); bool valid = client.VerifyEventSignature( webhookTimestamp: timestamp, webhookNonce: nonce, webhookBody: content, webhookSignature: signature, webhookSerialNumber: serialNumber ); if (!valid) { // NOTICE: // 需提前注入 CertificateManager、并下载平台证书,才可以使用扩展方法执行验签操作。 // 请参考本示例项目 TenpayCertificateRefreshingBackgroundService 后台任务中的相关实现。 // 有关 CertificateManager 的完整介绍请参阅《开发文档 / 基础用法 / 如何验证回调通知事件签名?》。 // 后续如何解密并反序列化,请参阅《开发文档 / 基础用法 / 如何解密回调通知事件中的敏感数据?》。 return new JsonResult(new { code = "FAIL", message = "验签失败" }); } var callbackModel = client.DeserializeEvent(content); var eventType = callbackModel.EventType?.ToUpper(); switch (eventType) { case "TRANSACTION.SUCCESS": { var callbackResource = client.DecryptEventResource<SKIT.FlurlHttpClient.Wechat.TenpayV3.Events.TransactionResource>(callbackModel); _logger.LogInformation("接收到微信支付推送的订单支付成功通知,商户订单号:{0}", callbackResource.OutTradeNumber); // 后续处理略 } break; default: { // 其他情况略 } break; } return new JsonResult(new { code = "SUCCESS", message = "成功" }); } } ``` tn2>可通过DockerFile进行发布与支持。 ```bash FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base WORKDIR /app EXPOSE 80 FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build WORKDIR /src COPY ["IPayAI.WeChat.MINIPay/IPayAI.WeChat.MINIPay.csproj", "IPayAI.WeChat.MINIPay/"] RUN dotnet restore "IPayAI.WeChat.MINIPay/IPayAI.WeChat.MINIPay.csproj" COPY . . WORKDIR "/src/IPayAI.WeChat.MINIPay" RUN dotnet build "IPayAI.WeChat.MINIPay.csproj" -c Release -o /app/build FROM build AS publish RUN dotnet publish "IPayAI.WeChat.MINIPay.csproj" -c Release -o /app/publish FROM base AS final WORKDIR /app COPY --from=publish /app/publish . # 设置环境变量,指定证书和密码(如果需要) ENTRYPOINT ["dotnet", "IPayAI.WeChat.MINIPay.dll"] ``` tn2>创建`docker-compose.yml`,通过`docker-compose build`来进行生成容器 ```bash version: '3.4' services: ipayai.wechat.minipay: build: context: . dockerfile: IPayAI.WeChat.MINIPay/Dockerfile image: 127.0.0.1:4443/ipay/ipayaiwechat_mini_pay environment: - ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_Kestrel__Certificates__Default__Path=/https/certificate.pfx - ASPNETCORE_Kestrel__Certificates__Default__Password=123456 - ASPNETCORE_URLS=http://+:80;https://+:443; volumes: - /home/ubuntu/aidasitop_yuming/aidasi.top.pfx:/https/certificate.pfx ports: - "80:80" - "443:443" ``` tn2>并通过`docker-compse up -d`来运行代码 ### 前端验证代码 tn2>首先在新创建的`app.js`,这里写入自己网站相关配置。 ```javascript // app.js App({ onLaunch() { }, globalData: { //MY_Url: "https://localhost:5001/", MY_Url: "域名", MY_MerchantId: "商户号", MY_AppId: "企业Appid", MY_Secret: "企业Secret", } }) ``` tn2>然后我通过我使用vant框架,所以我们需要初始化npm并安装该插件。 ```javascript npm init npm i @vant/weapp -S --production ``` tn2>安装好后在`app.json`中添加我们的button组件信息。 ```javascript "usingComponents": { "van-button": "@vant/weapp/button/index" }, ``` tn2>修改`index.js` ```javascript // index.js Page({ data: { useropenid: "", appid: "", secret: "", merchantid: "", url: "", orderinfo: {}, orderrequestinfo: {}, orderAmount: 0, refundAmount: 0, paymessage: "", refundmessage: "", }, onLoad: function(params) { const app = getApp(); var MY_AppId = app.globalData.MY_AppId var MY_MerchantId = app.globalData.MY_MerchantId var MY_Secret = app.globalData.MY_Secret var MY_Url = app.globalData.MY_Url this.setData({ appid: MY_AppId, secret: MY_Secret, merchantid: MY_MerchantId, url: MY_Url, }) }, sendrefund: function(event){ var data = { MerchantId : this.data.merchantid, TransactionId : this.data.orderrequestinfo.out_trade_no, OrderAmount : this.data.orderAmount, RefundAmount : this.data.refundAmount, } var e = this var fullurl = this.data.url + "/api/refund" wx.request({ url: fullurl, // 后端创建订单的接口 method: 'POST', data, success(res) { e.setData({ refundmessage: "msg: 退款成功 result:"+JSON.stringify(res) }); }, fail(err) { e.setData({ refundmessage: "msg: 退款失败 result:"+JSON.stringify(err) }); } }); }, sendpay: function(event){ console.log("发起支付") var openid = this.data.useropenid var fullurl = this.data.url + "/api/order/jsapi" console.log(fullurl) // 金额0.01元 var amount = 1 var e = this //前端调用后端接口创建订单并获取支付参数 wx.request({ url: fullurl, // 后端创建订单的接口 method: 'POST', data: { // 这些数据应根据实际情况获取 MerchantId: this.data.merchantid, AppId: this.data.appid, OpenId: openid, Amount: amount // 例如,1.00元 }, success(res) { var item = res.data.wxparam if (item) { // 假设prepay_id在返回的data中 // 使用返回的支付参数发起支付 wx.requestPayment({ ...item, success(payRes) { e.setData({ orderinfo: item, orderAmount: amount, refundAmount: amount, paymessage: '支付成功', orderrequestinfo: res.data.orderrequest }) console.log('支付成功', payRes); }, fail(payErr) { e.setData({ paymessage: '支付失败' }) console.log('支付失败', payErr); } }); } else { console.log('创建订单失败', res); } }, fail(err) { console.log('请求后端接口失败', err); } }); }, sendlogin: function(event){ console.log("发起登陆") var e = this wx.login({ success: function(res) { if (res.code) { e.setData({ 'useropenid': res.code }); console.log("登录成功,临时登录凭证:" +e.data.useropenid); } else { console.log('登录失败!' + res.errMsg); } } }); }, info(){ var e = this wx.getUserInfo({ //成功后会返回 success:(res)=>{ console.log(res); // 把你的用户信息存到一个变量中方便下面使用 let userInfo= res.userInfo console.log("getUserInfo:",JSON.stringify(userInfo),e.data.appid,e.data.secret) //获取openId(需要code来换取)这是用户的唯一标识符 // 获取code值 wx.login({ //成功放回 success:(res)=>{ console.log(res); let code=res.code console.log("getCode:",code) // 通过code换取openId var fullurl = this.data.url + "/api/wxuser/"+code wx.request({ url: fullurl, success:(res)=>{ console.log(res); userInfo.openid=res.data.openid console.log("getOpenid:",userInfo) console.log(userInfo.openid); e.setData({ 'useropenid': res.data.openid }); } }) } }) } }) } }) ``` tn2>修改`index.wxml`文件,实现简单登陆、支付和退款功能。 ```javascript <van-button plain type="info" bind:tap="info">登录</van-button> <view>{{ useropenid }}</view> <van-button plain type="info" bind:tap="sendpay">发起支付</van-button> <view style="margin: 10px;"></view> <view>TransactionId or prepay_id(商户号id): {{ orderinfo.package }}</view> <view>OrderAmount(订单金额):{{ orderAmount }} 分</view> <view>RefundAmount(退款金额):{{ refundAmount }} 分</view> <view>Pay Message 支付消息:{{ paymessage }}</view> <view style="margin: 10px;"></view> <van-button plain type="info" bind:tap="sendrefund">发起退款</van-button> <view>Pay Refund 退款消息:{{ refundmessage }}</view> ```  