
企业微信小程序支付流程
注册微信公众号
首先通过 https://mp.weixin.qq.com/ 链接进行企业级的小程序注册。
然后打开自己的邮箱,点击收到的邮件的链接进行继续注册。
需要填写如下信息:
- 实体类型
- 公司类型
- 公司名称
- 营业执照注册号
- 身份证上的管理员姓名
- 身份证上号码
- 管理员的手机号码
- 短信验证码
- 小程序的付费验证300元
通过这些还点击继续,这里我就不继续了。
我已经注册好了
申请微信支付商户
我们登录好后点击微信支付—>接入微信支付—>点击申请接入(也就是这里:https://pay.weixin.qq.com/index.php/core/home/login )
点击成为微信支付商户号
然后就开始填写商户信息,主要有下面:
- 姓名
- 手机号
- 法人
- 营业执照
- 企业银行卡
这个只要慢慢弄一步步弄就好了。
然后关联我们的APP ID
也就是我们小程序中的AppID,复制到这里并进行提交就可以了。
开发配置
通过小程序登录后 https://mp.weixin.qq.com/ ,找到开发配置
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#/
主要开通证书的时候,具体可以看官网的流程:https://kf.qq.com/faq/161222NneAJf161222U7fARv.html (证书重要的在于证书序列号,和privatekey)
开通APIv3密钥的时候可以查看这个教程:https://kf.qq.com/faq/180830E36vyQ180830AZFZvu.html
创建项目
创建一个.net6的项目,添加相关的包。
<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>
在appsettings.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"
}
}
接下来我们要做三件事:
1.通过openid获取AccessToken去获取用户的openid,让我们知道对谁发起支付
2.后台自动刷新相关AccessToken和支付需要的相关证书
3.创建相关控制器
刷新AccessToken
通过微信接收相关参数。
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;
}
}
}
}
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;
}
}
}
}
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)));
在Models
文件夹下面创建WechatAccessTokenEntity
类,用于装AccessToken的类。
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; }
}
创建分布式锁,便于获取AccessToken
时进行锁住其他请求来获取AccessToken
。
public interface IDistributedLockFactory
{
IDistributedLock Create(string lockName);
}
internal class DistributedLockFactory : IDistributedLockFactory
{
private readonly DirectoryInfo _lockFileDirectory = new DirectoryInfo(Environment.CurrentDirectory);
public IDistributedLock Create(string lockName)
{
// NOTICE:
// 单机演示基于文件实现分布式锁,生产项目请替换成其他实现。
return new FileDistributedLock(_lockFileDirectory, lockName);
}
}
// 注入分布式锁
builder.Services.AddSingleton<IDistributedLockFactory, DistributedLockFactory>();
写一个简单的内存仓储,用于存储WechatAccessTokenEntity
。
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; }
}
public interface IWechatAccessTokenEntityRepository : IEnumerable<Models.WechatAccessTokenEntity>
{
void Insert(Models.WechatAccessTokenEntity entity);
void Update(Models.WechatAccessTokenEntity entity);
void Delete(Models.WechatAccessTokenEntity entity);
}
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();
}
}
// 注入仓储类
builder.Services.AddSingleton<IWechatAccessTokenEntityRepository, WechatAccessTokenEntityRepository>();
创建相关微信API请求工厂接口并进行依赖注入。
public interface IWechatApiClientFactory
{
WechatApiClient Create(string appId);
}
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;
}
}
// 注入工厂 HTTP 客户端
builder.Services.AddHttpClient();
builder.Services.AddSingleton<Services.HttpClients.IWechatApiClientFactory, Services.HttpClients.Implements.WechatApiClientFactory>();
自动创建后台刷新AccessToken的服务
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);
}
}
builder.Services.AddHostedService<Services.BackgroundServices.WechatAccessTokenRefreshingBackgroundService>();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
创建获取微信用户OpenId的控制器
[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);
}
}
微信小程序支付与退款
创建支付类和退款类。
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; }
}
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; }
}
创建支付需要的证书管理
public interface IWechatTenpayCertificateManagerFactory
{
ICertificateManager Create(string merchantId);
}
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());
}
}
创建支付需要的证书请求的请求工厂类。
public interface IWechatTenpayClientFactory
{
WechatTenpayClient Create(string merchantId);
}
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;
}
}
builder.Services.AddHostedService<Services.BackgroundServices.TenpayCertificateRefreshingBackgroundService>();
创建跨域设置
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);
}
}
// 跨域设置
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();
创建支付控制器
[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);
}
}
创建退款控制器
[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);
}
}
创建接收通知的控制器
[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 = "成功" });
}
}
可通过DockerFile进行发布与支持。
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"]
创建docker-compose.yml
,通过docker-compose build
来进行生成容器
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"
并通过docker-compse up -d
来运行代码
前端验证代码
首先在新创建的app.js
,这里写入自己网站相关配置。
// app.js
App({
onLaunch() {
},
globalData: {
//MY_Url: "https://localhost:5001/",
MY_Url: "域名",
MY_MerchantId: "商户号",
MY_AppId: "企业Appid",
MY_Secret: "企业Secret",
}
})
然后我通过我使用vant框架,所以我们需要初始化npm并安装该插件。
npm init
npm i @vant/weapp -S --production
安装好后在app.json
中添加我们的button组件信息。
"usingComponents": {
"van-button": "@vant/weapp/button/index"
},
修改index.js
// 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
});
}
})
}
})
}
})
}
})
修改index.wxml
文件,实现简单登陆、支付和退款功能。
<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>
欢迎加群讨论技术,1群:677373950(满了,可以加,但通过不了),2群:656732739


青春年华
优秀