.NET WebAPI+微信支付

简述

        .NET WebAPI+微信支付在网上资源较少疑抑或过于复杂对于小白不太友好,基于本身开发故而撰写本篇文章。

        首先需要准备注册好以及通过认证的微信小程序如下图

备案需要营业执照

微信认证是通过第三方认证,23年12月以后关闭以后只能通过官方认证。

以及商户号

其实代码反倒更简单,最重要的是进行小程序的配置以及商户号绑定,绑定以后不可解绑,注意坑,故需先商量好,比如为官方机构开发,推荐直接开两个号,因为测试的时候使用官方的大家都懂的。

大致分为以下几部分

  1. 注册小程序账号
  2. 小程序认证
  3. 备案
  4. 开通支付
  5. 开通商户号
  6. 设置api v3
  7. 商户号与小程序绑定
  8. 保存商户号,V3 API 密钥,商户证书序列号,商户证书文件内容,AppId,AppSecret

材料:营业执照,营业执照法人身份证,本人微信,电话邮箱等等(成功办理以后可将管理员设置为自己),餐饮类需要食品安全许可证才能更新小程序版本,更新版本也有坑

其中需要多张手机号,还有邮箱,验证,审核,过程相当,,,嗯 。

一.配置(官方扣的,但是不准)

1.配置API key

API v3密钥主要用于平台证书解密、回调信息解密,具体使用方式可参见接口规则文档中证书和回调报文解密icon-default.png?t=N7T8https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_2.shtml章节。

请根据以下步骤配置API key:


1登录微信商户平台https://pay.weixin.qq.com/,进入【账户中心 > API安全 】目录,设置APIV3密钥。​

  • 2在弹出窗口中点击“已沟通”。

  • 3输入API密钥,内容为32位字符,包括数字及大小写字母。点击获取短信验证码。

  • 4输入短信验证码,点击“确认”即设置成功。

  • 完成

下载并配置商户证书

商户API证书具体使用说明可参见接口规则文档中私钥和证书icon-default.png?t=N7T8https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_1.shtml章节

商户可登录微信商户平台icon-default.png?t=N7T8https://pay.weixin.qq.com/,在【账户中心】->【API安全】目录下载证书

以下为具体下载步骤:

  • 1从2018年底开始,微信支付新入驻机构及商户都将使用CA签发证书,在证书申请页面上点击“申请证书”。

  • 2在弹出窗口中点击“确定”。

  • 3在弹出窗口内点击“下载证书工具”按钮下载证书工具。

  • 4安装证书工具并打开,选择证书需要存储的路径后点击“申请证书”。

  • 5在证书工具中,将复制的商户信息粘贴并点击“下一步”。

  • 6获取请求串

  • 7生成证书串

    步骤1 在【商户平台】-“复制证书串”环节,点击“复制证书串”按钮后;

    步骤2 在【证书工具】-“复制请求串”环节,点击“下一步”按钮进入“粘贴证书串”环节;

    步骤3 在【证书工具】-“粘贴证书串”环节,点击“粘贴”按钮后;

    步骤4 点击“下一步”按钮,进入【证书工具】-“生成证书”环节

  • 8在【证书工具】-“生成证书”环节,已完成申请证书流程,点击“查看证书文件夹”,查看已生成的证书文件。

  • 9微信开发文档并未写完(实际还需解密获取商户证书序列号)这一步很关键啊,大家注意一下,这个序列号不是微信支付平台的,而是apiv3里面解析获得的
  • 需要前往证书查看 (myssl.com)icon-default.png?t=N7T8https://myssl.com/cert_decode.html处解析获取

2.配置应用

账号申请指引

1、申请小程序开发者账号,进行微信认证,获取appid登录微信公众平台icon-default.png?t=N7T8https://mp.weixin.qq.com/      注册一个小程序的开发者账号。小程序账号申请指引icon-default.png?t=N7T8https://mp.weixin.qq.com/debug/wxadoc/introduction/index.html

你看微信文档,备案那些都没做,就让你开支付

2、小程序开通微信支付,即申请或复用微信支付商户号,申请完小程序后,登录小程序后台。点击左侧导航栏的微信支付,在页面中进行开通。

点击开通按钮后,有2种方式可以获取微信支付能力,新申请微信支付商户号或绑定一个已有的微信支付商户号,请根据你的业务需要和具体情况选择,只能二选一,肯定选新的呀,老表,你都来看我博客了。

二.skit引用以及skit支付代码文件引入

地址为开源微信支付 v3 版 .Net SDK(支持 .NET Core / Framework,完整封装全部 v3 API) | 微信开放社区 (qq.com)icon-default.png?t=N7T8https://developers.weixin.qq.com/community/develop/article/doc/00020aadc384a0a5f01c3526b56813

三.配置

控制器内引用

注:少数为编者数据操作层,注意甄别

using Framework.Wechat.HttpClients;
using Framework.Wechat.Repositories;
using Framework.WePay.Services.HttpClients;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using SKIT.FlurlHttpClient.Wechat.Api.Models;
using SKIT.FlurlHttpClient.Wechat.Api;
using Framework.Wechat.Options;
using Framework.WePay.Options;
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Models;
using SKIT.FlurlHttpClient.Wechat.TenpayV3;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using Newtonsoft.Json;
using static SKIT.FlurlHttpClient.Wechat.Api.Models.WxaSecOrderUploadCombinedShippingInfoRequest.Types.SubOrder.Types;
using Microsoft.AspNetCore.Authorization;
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Events;
using System.Net;
using System.Text;
using NMemory.Data;
using CQIE.FC.NOS.DataAccess.Interfaces;
using Microsoft.Extensions.Logging;
using Org.BouncyCastle.Bcpg;
using System.Net.Http;
using System.Threading.Tasks;
using System.Security.Cryptography;
using Newtonsoft.Json.Linq;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.OpenSsl;
using System.Security.Cryptography.X509Certificates;
using static SKIT.FlurlHttpClient.Wechat.Api.Models.ComponentTCBBatchCreateContainerServiceVersionRequest.Types;
using System.Globalization;

namespace WxPayDemo.Webapi.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    [Authorize]
    [Authorize(Roles = "用户")]
    public class PaymentController : ControllerBase
    {
        private readonly ILogger<PaymentController> _logger;
        private readonly IWechatTenpayHttpClientFactory _tenpayHttpClientFactory;
        private readonly IWechatApiHttpClientFactory _wechatApiHttpClientFactory;
        private readonly IWechatAccessTokenEntityRepository _wechatAccessTokenEntityRepository;
        private readonly TenpayOptions _tenpayOptions;
        private readonly WechatOptions _wechatOptions;
        private IPaymentService _paymentService;
        private readonly HttpClient _httpClient;
        private readonly RSA _privateKey;
        private readonly string _merchantId;
        public PaymentController(ILogger<PaymentController> logger, IWechatTenpayHttpClientFactory wechatTenpayHttpClientFactory,
                                 IWechatApiHttpClientFactory wechatApiHttpClientFactory, IWechatAccessTokenEntityRepository wechatAccessTokenEntities,
                                 IOptions<TenpayOptions> tenpayOptions, IOptions<WechatOptions> wechatOptions,
                                 IPaymentService paymentService)
        {
            _logger = logger;
            _tenpayHttpClientFactory = wechatTenpayHttpClientFactory;
            _wechatApiHttpClientFactory = wechatApiHttpClientFactory;
            _wechatAccessTokenEntityRepository = wechatAccessTokenEntities;
            _tenpayOptions = tenpayOptions.Value;
            _wechatOptions = wechatOptions.Value;
            _paymentService = paymentService;
            _httpClient = new HttpClient();
            _merchantId = 配置中获取;
        }

其他内容不变,如微信传入电话,openid,金额

#region 预支付生成
public record propay(string Phone,string Openid,int Total);
[HttpPost]
public async Task<IActionResult> CreatorPreOrder([FromBody] propay propay)
{
    try
    {
        int total_fee = (int)(propay.Total);
        var client = _tenpayHttpClientFactory.Create();
        var wechatAccountOptions = _wechatOptions.Accounts?.FirstOrDefault();
        var request = new CreatePayTransactionJsapiRequest()
        {
            OutTradeNumber = OrderNumberGenerator.GenerateOrderNumber(long.Parse(propay.Phone)),
            AppId = wechatAccountOptions.AppId,
            Description = "创谷餐厅",
            NotifyUrl = _tenpayOptions.SucceedNotifyUrl,
            Amount = new CreatePayTransactionJsapiRequest.Types.Amount() { Total = total_fee },
            Payer = new CreatePayTransactionJsapiRequest.Types.Payer() { OpenId = propay.Openid }
        };
    
        var response = await client.ExecuteCreatePayTransactionJsapiAsync(request, cancellationToken: HttpContext.RequestAborted);

        if (!response.IsSuccessful())
        {
           _logger.LogWarning("JSAPI 下单失败(状态码:{0},错误代码:{1},错误描述:{2})。", response.RawStatus, response.ErrorCode, response.ErrorMessage);
           return Ok("JSAPI 下单失败");
        }
         _logger.LogWarning("JSAPI 成功 预支付交易会话标识:{0}", response.PrepayId);

         List<IDictionary<string, string>> keyValuePairs = new List<IDictionary<string, string>>();
         var paramMap = client.GenerateParametersForJsapiPayRequest(request.AppId, response.PrepayId);
         keyValuePairs.Add(paramMap);
         IDictionary<string, string> orderdic = new Dictionary<string, string> { { "orderid", request.OutTradeNumber } };
         keyValuePairs.Add(orderdic);
 
        return Ok(keyValuePairs);
    }
    catch (Exception ex)
    {

        using (StreamWriter sw = new StreamWriter(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/error.txt"), true))
        {
             sw.WriteLine(ex.ToString());
        }
        return Ok(ex.ToString());
    }
}
#endregion

结果返回到前端

前端进入支付

skit未提供获取单号故无法退款,需自己封装

需要的订单号

 #region 获取交易单号
 public record data(string outTradeNo);
 [HttpPost]
 public async Task<IActionResult> Gettransaction_id([FromBody] data data)
 {
     // outTradeNo = "23122317444300007285";

     string asd = data.outTradeNo;
     try
     {
         var request = new HttpRequestMessage(HttpMethod.Get, $"https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{asd}?mchid={_merchantId}");

         var auth = await BuildAuthAsync1(request);
         string value = $"WECHATPAY2-SHA256-RSA2048 {auth}";
         request.Headers.Add("Authorization", value);
         request.Headers.Add("Accept", "application/json");
         request.Headers.Add("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)");

         var response = await _httpClient.SendAsync(request);

         if (response.IsSuccessStatusCode)
         {
             var content = await response.Content.ReadAsStringAsync();
             // _logger.LogInformation("订单查询成功:{0}", content);
             return Ok(content);
         }
         else
         {
             var errorContent = await response.Content.ReadAsStringAsync();
             //_logger.LogError("订单查询失败:{0}", errorContent);
             return BadRequest(errorContent);
         }
     }
     catch (Exception ex)
     {
         using (StreamWriter sw = new StreamWriter(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/error.txt"), true))
         {
             sw.WriteLine("newpay" + ex.Message);
         }
         // _logger.LogError("订单查询发生异常:{0}", ex.Message);
         return StatusCode(500);
     }
 }

 private async Task<string> BuildAuthAsync1(HttpRequestMessage request)
 {
     string method = request.Method.ToString();
     string body = "";
     if (method == "POST" || method == "PUT" || method == "PATCH")
     {
         var content = request.Content;
         body = await content.ReadAsStringAsync();
     }

     string uri = request.RequestUri.PathAndQuery;
     var timestamp = DateTimeOffset.Now.ToUnixTimeSeconds();
     string nonceStr = Guid.NewGuid().ToString("N");

     string message = BuildMessage1(method, uri, timestamp, nonceStr, body);
     string signature = Sign(message);
     //自己填写或注入时读取
     string serial_no = "";
     return $"mchid=\"{_merchantId}\",nonce_str=\"{nonceStr}\",timestamp=\"{timestamp}\",serial_no=\"{serial_no}\",signature=\"{signature}\"";
 }


 protected string Sign(string message)
 {
     // 也可从配置文件读取
     // NOTE: 私钥不包括私钥文件起始的-----BEGIN PRIVATE KEY-----
     //        亦不包括结尾的-----END PRIVATE KEY-----
     string privateKey = "";

     byte[] keyData = Convert.FromBase64String(privateKey);

     var rsa = RSA.Create();
     rsa.ImportPkcs8PrivateKey(keyData, out _);
     byte[] data = System.Text.Encoding.UTF8.GetBytes(message);
     return Convert.ToBase64String(rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1));
 }
 private string BuildMessage1(string method, string uri, long timestamp, string nonceStr, string body)
 {
     string canonicalUrl = uri;
     if (!string.IsNullOrEmpty(Request.QueryString.Value))
     {
         canonicalUrl += Request.QueryString.Value;
     }

     return $"{method}\n{canonicalUrl}\n{timestamp}\n{nonceStr}\n{body}\n";
 }
 #endregion

中间仍有很多细节,暂时无法补充。

  • 20
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值