简述
.NET WebAPI+微信支付在网上资源较少疑抑或过于复杂对于小白不太友好,基于本身开发故而撰写本篇文章。
首先需要准备注册好以及通过认证的微信小程序如下图
备案需要营业执照
微信认证是通过第三方认证,23年12月以后关闭以后只能通过官方认证。
以及商户号
其实代码反倒更简单,最重要的是进行小程序的配置以及商户号绑定,绑定以后不可解绑,注意坑,故需先商量好,比如为官方机构开发,推荐直接开两个号,因为测试的时候使用官方的大家都懂的。
大致分为以下几部分
- 注册小程序账号
- 小程序认证
- 备案
- 开通支付
- 开通商户号
- 设置api v3
- 商户号与小程序绑定
- 保存商户号,V3 API 密钥,商户证书序列号,商户证书文件内容,AppId,AppSecret
材料:营业执照,营业执照法人身份证,本人微信,电话邮箱等等(成功办理以后可将管理员设置为自己),餐饮类需要食品安全许可证才能更新小程序版本,更新版本也有坑
其中需要多张手机号,还有邮箱,验证,审核,过程相当,,,嗯 。
一.配置(官方扣的,但是不准)
1.配置API key
API v3密钥主要用于平台证书解密、回调信息解密,具体使用方式可参见接口规则文档中证书和回调报文解密https://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证书具体使用说明可参见接口规则文档中私钥和证书https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_1.shtml章节
商户可登录微信商户平台https://pay.weixin.qq.com/,在【账户中心】->【API安全】目录下载证书
以下为具体下载步骤:
-
1从2018年底开始,微信支付新入驻机构及商户都将使用CA签发证书,在证书申请页面上点击“申请证书”。
-
2在弹出窗口中点击“确定”。
-
3在弹出窗口内点击“下载证书工具”按钮下载证书工具。
-
4安装证书工具并打开,选择证书需要存储的路径后点击“申请证书”。
-
5在证书工具中,将复制的商户信息粘贴并点击“下一步”。
-
6获取请求串
-
7生成证书串
步骤1 在【商户平台】-“复制证书串”环节,点击“复制证书串”按钮后;
步骤2 在【证书工具】-“复制请求串”环节,点击“下一步”按钮进入“粘贴证书串”环节;
步骤3 在【证书工具】-“粘贴证书串”环节,点击“粘贴”按钮后;
步骤4 点击“下一步”按钮,进入【证书工具】-“生成证书”环节
-
8在【证书工具】-“生成证书”环节,已完成申请证书流程,点击“查看证书文件夹”,查看已生成的证书文件。
- 9微信开发文档并未写完(实际还需解密获取商户证书序列号)这一步很关键啊,大家注意一下,这个序列号不是微信支付平台的,而是apiv3里面解析获得的
- 需要前往证书查看 (myssl.com)
https://myssl.com/cert_decode.html处解析获取
2.配置应用
账号申请指引
1、申请小程序开发者账号,进行微信认证,获取appid登录微信公众平台https://mp.weixin.qq.com/ 注册一个小程序的开发者账号。小程序账号申请指引
https://mp.weixin.qq.com/debug/wxadoc/introduction/index.html
你看微信文档,备案那些都没做,就让你开支付
2、小程序开通微信支付,即申请或复用微信支付商户号,申请完小程序后,登录小程序后台。点击左侧导航栏的微信支付,在页面中进行开通。
点击开通按钮后,有2种方式可以获取微信支付能力,新申请微信支付商户号或绑定一个已有的微信支付商户号,请根据你的业务需要和具体情况选择,只能二选一,肯定选新的呀,老表,你都来看我博客了。
二.skit引用以及skit支付代码文件引入
三.配置
控制器内引用
注:少数为编者数据操作层,注意甄别
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
中间仍有很多细节,暂时无法补充。