docs: 更新示例项目

This commit is contained in:
Fu Diwei 2025-06-04 23:53:34 +08:00
parent ac925f87cf
commit 56ed274c40
45 changed files with 800 additions and 492 deletions

View File

@ -20,7 +20,7 @@
- 验证并接收微信服务器推送数据;
- 一个根据 OpenId 获取用户信息的 API。
- 一个根据 OpenId 获取用户信息的 WebAPI。
---

View File

@ -2,7 +2,7 @@
---
示例项目基于 .NET Framework 4.7 和 .NET 6.0 实现,分别位于 _samples/SKIT.FlurlHttpClient.Wechat.Sample_NetFramework47_ 和 _samples/SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_Net6_。
示例项目基于 .NET Framework 4.8 和 .NET 6.0 实现,分别位于 _samples/SKIT.FlurlHttpClient.Wechat.Sample_NetFramework48_ 和 _samples/SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_Net6_。
示例项目依赖以下第三方库:
@ -18,7 +18,7 @@
- 验证并接收微信服务器推送数据;
- 一个 JSAPI 下单和退款的 API。
- 一个 JSAPI 下单和退款的 WebAPI。
---

View File

@ -25,6 +25,10 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Options
public string CertificateSerialNumber { get; set; } = string.Empty;
public string CertificatePrivateKey { get; set; } = string.Empty;
public string? PlatformPublicKeyId { get; set; }
public string? PlatformPublicKey { get; set; }
}
}
}

View File

@ -30,7 +30,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Services.BackgroundService
{
// NOTICE:
// 2024-10-01 后微信支付新增了基于微信支付公钥的验证身份方式,
// 如果你已切换至使用平台公钥,则不再需要下载平台证书,可忽略此定时任务。
// 如果你已切换至使用平台公钥,则不再需要下载平台证书,可删除此定时任务。
while (!stoppingToken.IsCancellationRequested)
{
foreach (var tenpayMerchantOptions in _tenpayOptions.Merchants)

View File

@ -0,0 +1,9 @@
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Services.HttpClients
{
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings;
public interface IWechatTenpayPublicKeyManagerFactory
{
IPublicKeyManager Create(string merchantId);
}
}

View File

@ -5,20 +5,25 @@ using Microsoft.Extensions.Options;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Services.HttpClients.Implements
{
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings;
internal partial class WechatTenpayClientFactory : IWechatTenpayClientFactory
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly Options.TenpayOptions _tenpayOptions;
private readonly IWechatTenpayCertificateManagerFactory _tenpayCertificateManagerFactory;
private readonly IWechatTenpayPublicKeyManagerFactory _tenpayPublicKeyManagerFactory;
public WechatTenpayClientFactory(
IHttpClientFactory httpClientFactory,
IOptions<Options.TenpayOptions> tenpayOptions,
IWechatTenpayCertificateManagerFactory tenpayCertificateManagerFactory)
IWechatTenpayCertificateManagerFactory tenpayCertificateManagerFactory,
IWechatTenpayPublicKeyManagerFactory tenpayPublicKeyManagerFactory)
{
_httpClientFactory = httpClientFactory;
_tenpayOptions = tenpayOptions.Value;
_tenpayCertificateManagerFactory = tenpayCertificateManagerFactory;
_tenpayPublicKeyManagerFactory = tenpayPublicKeyManagerFactory;
}
public WechatTenpayClient Create(string merchantId)
@ -27,19 +32,26 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Services.HttpClients.Imple
// 这里的工厂方法是为了演示多租户而存在的,可根据商户号生成不同的 API 客户端。
// 如果你的项目只存在唯一一个租户,那么直接注入 `WechatTenpayClient` 即可。
var tenpayMerchantConfig = _tenpayOptions.Merchants?.FirstOrDefault(e => string.Equals(merchantId, e.MerchantId));
if (tenpayMerchantConfig == null)
var tenpayMerchantOptions = _tenpayOptions.Merchants?.FirstOrDefault(e => string.Equals(merchantId, e.MerchantId));
if (tenpayMerchantOptions == 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),
MerchantId = tenpayMerchantOptions.MerchantId,
MerchantV3Secret = tenpayMerchantOptions.SecretV3,
MerchantCertificateSerialNumber = tenpayMerchantOptions.CertificateSerialNumber,
MerchantCertificatePrivateKey = tenpayMerchantOptions.CertificatePrivateKey,
AutoEncryptRequestSensitiveProperty = false,
AutoDecryptResponseSensitiveProperty = false
AutoDecryptResponseSensitiveProperty = false,
// 基于平台证书的认证方式还需设置以下参数:
PlatformAuthScheme = PlatformAuthScheme.Certificate,
PlatformCertificateManager = _tenpayCertificateManagerFactory.Create(tenpayMerchantOptions.MerchantId),
// 基于平台公钥的认证方式还需设置以下参数:
//PlatformAuthScheme = PlatformAuthScheme.PublicKey,
//PlatformPublicKeyManager = _tenpayPublicKeyManagerFactory.Create(tenpayMerchantOptions.MerchantId)
};
var wechatTenpayClient = WechatTenpayClientBuilder.Create(wechatTenpayClientOptions)
.UseHttpClient(_httpClientFactory.CreateClient(), disposeClient: false)

View File

@ -0,0 +1,41 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Net.Http;
using Microsoft.Extensions.Options;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Services.HttpClients.Implements
{
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings;
internal partial class WechatTenpayPublicKeyManagerFactory : IWechatTenpayPublicKeyManagerFactory
{
private readonly ConcurrentDictionary<string, IPublicKeyManager> _dict;
private readonly Options.TenpayOptions _tenpayOptions;
public WechatTenpayPublicKeyManagerFactory(
IOptions<Options.TenpayOptions> tenpayOptions)
{
_dict = new ConcurrentDictionary<string, IPublicKeyManager>();
_tenpayOptions = tenpayOptions.Value;
}
public IPublicKeyManager Create(string merchantId)
{
// NOTICE:
// 这里的工厂方法是为了演示多租户而存在的,可根据商户号生成不同的公钥管理器。
// 如果你的项目只存在唯一一个租户,那么直接注入 `PublicKeyManager` 即可。
var tenpayMerchantOptions = _tenpayOptions.Merchants?.FirstOrDefault(e => string.Equals(merchantId, e.MerchantId));
if (tenpayMerchantOptions == null)
throw new Exception("未在配置项中找到该 MerchantId 对应的微信商户号。");
return _dict.GetOrAdd(merchantId, (_) =>
{
var manager = new InMemoryPublicKeyManager();
manager.AddEntry(new PublicKeyEntry(PublicKeyEntry.ALGORITHM_TYPE_RSA, tenpayMerchantOptions.PlatformPublicKeyId!, tenpayMerchantOptions.PlatformPublicKey!));
return manager;
});
}
}
}

View File

@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@ -25,6 +25,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample
// 注入工厂 HTTP 客户端
services.AddHttpClient();
services.AddSingleton<Services.HttpClients.IWechatTenpayCertificateManagerFactory, Services.HttpClients.Implements.WechatTenpayCertificateManagerFactory>();
services.AddSingleton<Services.HttpClients.IWechatTenpayPublicKeyManagerFactory, Services.HttpClients.Implements.WechatTenpayPublicKeyManagerFactory>();
services.AddSingleton<Services.HttpClients.IWechatTenpayClientFactory, Services.HttpClients.Implements.WechatTenpayClientFactory>();
// 注入后台任务

View File

@ -15,7 +15,9 @@
"MerchantId": "填写商户号",
"SecretV3": "填写商户 V3 API 密钥",
"CertificateSerialNumber": "填写商户证书序列号",
"CertificatePrivateKey": "填写商户证书文件内容"
"CertificatePrivateKey": "填写商户证书文件内容",
"PlatformPublicKeyId": "填写平台公钥 ID仅当基于平台公钥的认证方式时需要",
"PlatformPublicKey": "填写平台公钥内容(仅当基于平台公钥的认证方式时需要)"
}
],
"NotifyUrl": "https://localhost:5001"

View File

@ -1,10 +0,0 @@
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample
{
public class RouteRegistration
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
}
}
}

View File

@ -1,43 +0,0 @@
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Controllers
{
[RoutePrefix("api/order")]
public class TenpayOrderController : ApiController
{
private readonly Services.HttpClients.IWechatTenpayClientFactory _wechatTenpayClientFactory;
public TenpayOrderController(
Services.HttpClients.IWechatTenpayClientFactory wechatTenpayClientFactory)
{
_wechatTenpayClientFactory = wechatTenpayClientFactory;
}
[HttpPost]
[Route("jsapi")]
public async Task<IHttpActionResult> CreateOrderByJsapi([FromBody] Models.CreateOrderByJsapiRequest requestModel, CancellationToken cancellationToken)
{
if (requestModel == null)
return BadRequest();
using var client = _wechatTenpayClientFactory.Create(requestModel.MerchantId);
var request = new CreatePayTransactionJsapiRequest()
{
OutTradeNumber = "SAMPLE_OTN_" + DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff"),
AppId = requestModel.AppId,
Description = "演示订单",
NotifyUrl = Options.TenpayOptions.Instance.Value.NotifyUrl,
Amount = new CreatePayTransactionJsapiRequest.Types.Amount() { Total = requestModel.Amount },
Payer = new CreatePayTransactionJsapiRequest.Types.Payer() { OpenId = requestModel.OpenId }
};
var response = await client.ExecuteCreatePayTransactionJsapiAsync(request, cancellationToken);
if (!response.IsSuccessful())
{
Debug.WriteLine(
"JSAPI 下单失败(状态码:{0},错误代码:{1},错误描述:{2})。",
response.GetRawStatus(), response.ErrorCode, response.ErrorMessage
);
}
return Json(response);
}
}
}

View File

@ -1,46 +0,0 @@
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Controllers
{
[RoutePrefix("api/refund")]
public class TenpayRefundController : ApiController
{
private readonly Services.HttpClients.IWechatTenpayClientFactory _wechatTenpayClientFactory;
public TenpayRefundController(
Services.HttpClients.IWechatTenpayClientFactory wechatTenpayClientFactory)
{
_wechatTenpayClientFactory = wechatTenpayClientFactory;
}
[HttpPost]
[Route("")]
public async Task<IHttpActionResult> CreateRefund([FromBody] Models.CreateRefundRequest requestModel, CancellationToken cancellationToken)
{
if (requestModel == null)
return BadRequest();
using var client = _wechatTenpayClientFactory.Create(requestModel.MerchantId);
var request = new CreateRefundDomesticRefundRequest()
{
TransactionId = requestModel.TransactionId,
OutRefundNumber = "SAMPLE_ORN_" + DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff"),
Amount = new CreateRefundDomesticRefundRequest.Types.Amount()
{
Total = requestModel.OrderAmount,
Refund = requestModel.RefundAmount
},
Reason = "示例退款",
NotifyUrl = Options.TenpayOptions.Instance.Value.NotifyUrl
};
var response = await client.ExecuteCreateRefundDomesticRefundAsync(request, cancellationToken);
if (!response.IsSuccessful())
{
Debug.WriteLine(
"申请退款失败(状态码:{0},错误代码:{1},错误描述:{2})。",
response.GetRawStatus(), response.ErrorCode, response.ErrorMessage
);
}
return Json(response);
}
}
}

View File

@ -1 +0,0 @@
<%@ Application Codebehind="Global.asax.cs" Inherits="SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.WebApiApplication" Language="C#" %>

View File

@ -1,33 +0,0 @@
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample
{
public class WebApiApplication : HttpApplication
{
private Hangfire.BackgroundJobServer _backgroundJobServer;
protected void Application_Start()
{
// 注册 WebAPI 区域
AreaRegistration.RegisterAllAreas();
// 注册 WebAPI 配置
GlobalConfiguration.Configure(WebApiConfiguration.Register);
// 注册全局过滤器
FilterRegistration.RegisterGlobalFilters(GlobalFilters.Filters);
// 注册路由表
RouteRegistration.RegisterRoutes(RouteTable.Routes);
// 初始化 Autofac 依赖注入容器
AutofacInitializer.Init();
// 初始化 Hangfire 定时任务
_backgroundJobServer = HangfireInitializer.Init();
}
protected void Application_End()
{
_backgroundJobServer?.Dispose();
}
}
}

View File

@ -1,16 +0,0 @@

[assembly: AssemblyTitle("SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_NetFramework47")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_NetFramework47")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
[assembly: Guid("41c93671-99b5-44ad-b0d4-6e2d8f5688ec")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -1,54 +0,0 @@
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Services.BackgroundJobs
{
internal class TenpayCertificateRefreshingBackgroundJob
{
private readonly HttpClients.IWechatTenpayClientFactory _wechatTenpayClientFactory;
public TenpayCertificateRefreshingBackgroundJob(
HttpClients.IWechatTenpayClientFactory wechatTenpayClientFactory)
{
_wechatTenpayClientFactory = wechatTenpayClientFactory;
}
public async Task ExecuteAsync()
{
// NOTICE:
// 2024-10-01 后微信支付新增了基于微信支付公钥的验证身份方式,
// 如果你已切换至使用平台公钥,则不再需要下载平台证书,可忽略此定时任务。
foreach (var tenpayMerchantOptions in Options.TenpayOptions.Instance.Value.Merchants)
{
try
{
const string ALGORITHM_TYPE = "RSA";
using var client = _wechatTenpayClientFactory.Create(tenpayMerchantOptions.MerchantId);
var request = new QueryCertificatesRequest() { AlgorithmType = ALGORITHM_TYPE };
var response = await client.ExecuteQueryCertificatesAsync(request);
if (response.IsSuccessful())
{
// NOTICE:
// 如果构造 Client 时启用了 `AutoDecryptResponseSensitiveProperty` 配置项,则无需再执行下面一行的手动解密方法:
response = client.DecryptResponseSensitiveProperty(response);
foreach (var certificate in response.CertificateList)
{
client.CertificateManager.AddEntry(CertificateEntry.Parse(ALGORITHM_TYPE, certificate));
}
Debug.WriteLine("刷新微信商户平台证书成功。");
}
else
{
Debug.WriteLine(
"刷新微信商户平台证书失败(状态码:{0},错误代码:{1},错误描述:{2})。",
response.GetRawStatus(), response.ErrorCode, response.ErrorMessage
);
}
}
catch (Exception ex)
{
Debug.WriteLine("刷新微信商户平台证书遇到异常。\r\n{0}", ex);
}
}
}
}
}

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<system.web>
</system.web>
</configuration>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<system.web>
<compilation xdt:Transform="RemoveAttributes(debug)" />
</system.web>
</configuration>

View File

@ -1,36 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Autofac" version="6.5.0" targetFramework="net472" />
<package id="Autofac.Mvc5" version="6.1.0" targetFramework="net472" />
<package id="Autofac.WebApi2" version="6.1.1" targetFramework="net472" />
<package id="Hangfire.AspNet" version="0.2.0" targetFramework="net472" />
<package id="Hangfire.Autofac" version="2.3.1" targetFramework="net472" />
<package id="Hangfire.Core" version="1.7.33" targetFramework="net472" />
<package id="Hangfire.InMemory" version="0.3.6" targetFramework="net472" />
<package id="Microsoft.AspNet.Mvc" version="5.2.9" targetFramework="net472" />
<package id="Microsoft.AspNet.Razor" version="3.2.9" targetFramework="net472" />
<package id="Microsoft.AspNet.WebApi" version="5.2.9" targetFramework="net472" />
<package id="Microsoft.AspNet.WebApi.Client" version="5.2.9" targetFramework="net472" />
<package id="Microsoft.AspNet.WebApi.Core" version="5.2.9" targetFramework="net472" />
<package id="Microsoft.AspNet.WebApi.WebHost" version="5.2.9" targetFramework="net472" />
<package id="Microsoft.AspNet.WebHelpers" version="3.2.9" targetFramework="net472" />
<package id="Microsoft.AspNet.WebPages" version="3.2.9" targetFramework="net472" />
<package id="Microsoft.AspNet.WebPages.Data" version="3.2.9" targetFramework="net472" />
<package id="Microsoft.AspNet.WebPages.WebData" version="3.2.9" targetFramework="net472" />
<package id="Microsoft.AspNetCore.Html.Abstractions" version="2.2.0" targetFramework="net472" />
<package id="Microsoft.AspNetCore.Razor" version="2.2.0" targetFramework="net472" />
<package id="Microsoft.Bcl.AsyncInterfaces" version="6.0.0" targetFramework="net472" />
<package id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="3.6.0" targetFramework="net472" />
<package id="Microsoft.Owin" version="4.2.2" targetFramework="net472" />
<package id="Microsoft.Owin.Host.SystemWeb" version="4.2.2" targetFramework="net472" />
<package id="Microsoft.Web.Infrastructure" version="2.0.0" targetFramework="net472" />
<package id="Newtonsoft.Json" version="13.0.3" targetFramework="net472" />
<package id="Owin" version="1.0" targetFramework="net472" />
<package id="System.Buffers" version="4.5.1" targetFramework="net472" />
<package id="System.Diagnostics.DiagnosticSource" version="6.0.0" targetFramework="net472" />
<package id="System.Memory" version="4.5.5" targetFramework="net472" />
<package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net472" />
<package id="System.Runtime.CompilerServices.Unsafe" version="6.0.0" targetFramework="net472" />
<package id="System.Text.Encodings.Web" version="6.0.0" targetFramework="net472" />
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net472" />
</packages>

View File

@ -1,6 +1,13 @@
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample
using System.Reflection;
using System.Web.Mvc;
using Autofac;
using Autofac.Integration.Mvc;
using Autofac.Integration.WebApi;
using Hangfire;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample
{
public class AutofacInitializer
public class AutofacConfig
{
public static void Init()
{
@ -13,6 +20,9 @@
builder.RegisterType<Services.HttpClients.Implements.WechatTenpayCertificateManagerFactory>()
.As<Services.HttpClients.IWechatTenpayCertificateManagerFactory>()
.SingleInstance();
builder.RegisterType<Services.HttpClients.Implements.WechatTenpayPublicKeyManagerFactory>()
.As<Services.HttpClients.IWechatTenpayPublicKeyManagerFactory>()
.SingleInstance();
builder.RegisterType<Services.HttpClients.Implements.WechatTenpayClientFactory>()
.As<Services.HttpClients.IWechatTenpayClientFactory>()
.SingleInstance();

View File

@ -1,6 +1,8 @@
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample
using System.Web.Mvc;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample
{
public class FilterRegistration
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{

View File

@ -1,10 +1,8 @@
using System;
using System.Collections.Generic;
using Hangfire;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample
{
public class HangfireInitializer
public class HangfireConfig
{
public static BackgroundJobServer Init()
{

View File

@ -0,0 +1,19 @@
using System.Web.Mvc;
using System.Web.Routing;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
}

View File

@ -1,6 +1,8 @@
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample
using System.Web.Http;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample
{
public static class WebApiConfiguration
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{

View File

@ -1,5 +1,15 @@
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Controllers
{
using SKIT.FlurlHttpClient.Wechat.TenpayV3;
[RoutePrefix("api/notify")]
public class TenpayNotifyController : ApiController
{
@ -19,10 +29,10 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Controllers
using (var stream = await Request.Content.ReadAsStreamAsync())
using (var reader = new StreamReader(stream, Encoding.UTF8))
{
string timestamp = Request.Headers.TryGetValues("Wechatpay-Timestamp", out _) ? Request.Headers.GetValues("Wechatpay-Timestamp").First() : null;
string nonce = Request.Headers.TryGetValues("Wechatpay-Nonce", out _) ? Request.Headers.GetValues("Wechatpay-Nonce").First() : null;
string signature = Request.Headers.TryGetValues("Wechatpay-Signature", out _) ? Request.Headers.GetValues("Wechatpay-Signature").First() : null;
string serialNumber = Request.Headers.TryGetValues("Wechatpay-Serial", out _) ? Request.Headers.GetValues("Wechatpay-Serial").First() : null;
string timestamp = Request.Headers.Contains("Wechatpay-Timestamp") ? Request.Headers.GetValues("Wechatpay-Timestamp").First() : null;
string nonce = Request.Headers.Contains("Wechatpay-Nonce") ? Request.Headers.GetValues("Wechatpay-Nonce").First() : null;
string signature = Request.Headers.Contains("Wechatpay-Signature") ? Request.Headers.GetValues("Wechatpay-Signature").First() : null;
string serialNumber = Request.Headers.Contains("Wechatpay-Serial") ? Request.Headers.GetValues("Wechatpay-Serial").First() : null;
string content = await reader.ReadToEndAsync();
Debug.WriteLine("接收到微信支付推送的数据:{0}", content);

View File

@ -0,0 +1,54 @@
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Controllers
{
using SKIT.FlurlHttpClient.Wechat.TenpayV3;
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Models;
[RoutePrefix("api/order")]
public class TenpayOrderController : ApiController
{
private readonly Services.HttpClients.IWechatTenpayClientFactory _wechatTenpayClientFactory;
public TenpayOrderController(
Services.HttpClients.IWechatTenpayClientFactory wechatTenpayClientFactory)
{
_wechatTenpayClientFactory = wechatTenpayClientFactory;
}
[HttpPost]
[Route("jsapi")]
public async Task<IHttpActionResult> CreateOrderByJsapi([FromBody] Models.CreateOrderByJsapiRequest requestModel, CancellationToken cancellationToken)
{
if (requestModel == null)
return BadRequest();
using (var client = _wechatTenpayClientFactory.Create(requestModel.MerchantId))
{
var request = new CreatePayTransactionJsapiRequest()
{
OutTradeNumber = "SAMPLE_OTN_" + DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff"),
AppId = requestModel.AppId,
Description = "演示订单",
NotifyUrl = Options.TenpayOptions.Instance.Value.NotifyUrl,
Amount = new CreatePayTransactionJsapiRequest.Types.Amount() { Total = requestModel.Amount },
Payer = new CreatePayTransactionJsapiRequest.Types.Payer() { OpenId = requestModel.OpenId }
};
var response = await client.ExecuteCreatePayTransactionJsapiAsync(request, cancellationToken);
if (!response.IsSuccessful())
{
Debug.WriteLine(
"JSAPI 下单失败(状态码:{0},错误代码:{1},错误描述:{2})。",
response.GetRawStatus(), response.ErrorCode, response.ErrorMessage
);
}
return Json(response);
}
}
}
}

View File

@ -0,0 +1,57 @@
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Controllers
{
using SKIT.FlurlHttpClient.Wechat.TenpayV3;
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Models;
[RoutePrefix("api/refund")]
public class TenpayRefundController : ApiController
{
private readonly Services.HttpClients.IWechatTenpayClientFactory _wechatTenpayClientFactory;
public TenpayRefundController(
Services.HttpClients.IWechatTenpayClientFactory wechatTenpayClientFactory)
{
_wechatTenpayClientFactory = wechatTenpayClientFactory;
}
[HttpPost]
[Route("")]
public async Task<IHttpActionResult> CreateRefund([FromBody] Models.CreateRefundRequest requestModel, CancellationToken cancellationToken)
{
if (requestModel == null)
return BadRequest();
using (var client = _wechatTenpayClientFactory.Create(requestModel.MerchantId))
{
var request = new CreateRefundDomesticRefundRequest()
{
TransactionId = requestModel.TransactionId,
OutRefundNumber = "SAMPLE_ORN_" + DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff"),
Amount = new CreateRefundDomesticRefundRequest.Types.Amount()
{
Total = requestModel.OrderAmount,
Refund = requestModel.RefundAmount
},
Reason = "示例退款",
NotifyUrl = Options.TenpayOptions.Instance.Value.NotifyUrl
};
var response = await client.ExecuteCreateRefundDomesticRefundAsync(request, cancellationToken);
if (!response.IsSuccessful())
{
Debug.WriteLine(
"申请退款失败(状态码:{0},错误代码:{1},错误描述:{2})。",
response.GetRawStatus(), response.ErrorCode, response.ErrorMessage
);
}
return Json(response);
}
}
}
}

View File

@ -0,0 +1 @@
<%@ Application Codebehind="Global.asax.cs" Inherits="SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.WebApiApplication" Language="C#" %>

View File

@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample
{
public class WebApiApplication : System.Web.HttpApplication
{
private Hangfire.BackgroundJobServer _backgroundJobServer;
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
AutofacConfig.Init();
_backgroundJobServer = HangfireConfig.Init();
}
protected void Application_End()
{
_backgroundJobServer?.Dispose();
}
}
}

View File

@ -1,3 +1,8 @@
using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web.Configuration;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Options
{
public partial class TenpayOptions
@ -19,6 +24,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Options
SecretV3 = WebConfigurationManager.AppSettings[$"TenpayOptions_Merchant_{i}_SecretV3"],
CertificateSerialNumber = WebConfigurationManager.AppSettings[$"TenpayOptions_Merchant_{i}_CertificateSerialNumber"],
CertificatePrivateKey = WebConfigurationManager.AppSettings[$"TenpayOptions_Merchant_{i}_CertificatePrivateKey"],
PlatformPublicKeyId = WebConfigurationManager.AppSettings[$"TenpayOptions_Merchant_{i}_PlatformPublicKeyId"],
PlatformPublicKey = WebConfigurationManager.AppSettings[$"TenpayOptions_Merchant_{i}_PlatformPublicKey"],
})
.ToArray(),
NotifyUrl = WebConfigurationManager.AppSettings[$"TenpayOptions_NotifyUrl"]
@ -46,6 +53,10 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Options
public string CertificateSerialNumber { get; set; } = string.Empty;
public string CertificatePrivateKey { get; set; } = string.Empty;
public string PlatformPublicKeyId { get; set; }
public string PlatformPublicKey { get; set; }
}
}
}

View File

@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// 有关程序集的常规信息是通过以下项进行控制的
// 控制。更改这些特性值可修改
// 与程序集关联的信息。
[assembly: AssemblyTitle("SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_NetFramework48")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_NetFramework48")]
[assembly: AssemblyCopyright("版权所有(C) 2025")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// 将 ComVisible 设置为 false 将使此程序集中的类型
// 对 COM 组件不可见。如果需要
// COM在该类型上将 ComVisible 属性设置为 true。
[assembly: ComVisible(false)]
// 如果此项目向 COM 公开,则下列 GUID 用于 typelib 的 ID
[assembly: Guid("22aea3c5-d9af-4ec5-b208-73dc8795a1bb")]
// 程序集的版本信息由下列四个值组成:
//
// 主版本
// 次版本
// 内部版本号
// 修订号
//
// 你可以指定所有值,也可以让修订版本和内部版本号采用默认值,
// 方法是按如下所示使用 "*":
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -1,30 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.2.0.1\build\net46\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.props" Condition="Exists('..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.2.0.1\build\net46\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>
</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{7667F0D3-B41D-43C2-B69D-A68FE230EBF7}</ProjectGuid>
<ProjectGuid>{DFA229FF-0A4B-411E-9A11-681BA55DFBAE}</ProjectGuid>
<ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample</RootNamespace>
<AssemblyName>SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_NetFramework47</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<RootNamespace>SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_NetFramework48</RootNamespace>
<AssemblyName>SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_NetFramework48</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<MvcBuildViews>false</MvcBuildViews>
<UseIISExpress>true</UseIISExpress>
<Use64BitIISExpress>
</Use64BitIISExpress>
<IISExpressSSLPort>
</IISExpressSSLPort>
<IISExpressAnonymousAuthentication>
</IISExpressAnonymousAuthentication>
<IISExpressWindowsAuthentication>
</IISExpressWindowsAuthentication>
<IISExpressUseClassicPipelineMode>
</IISExpressUseClassicPipelineMode>
<UseGlobalApplicationHostFile>
</UseGlobalApplicationHostFile>
<Use64BitIISExpress />
<IISExpressSSLPort>44339</IISExpressSSLPort>
<IISExpressAnonymousAuthentication />
<IISExpressWindowsAuthentication />
<IISExpressUseClassicPipelineMode />
<UseGlobalApplicationHostFile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
@ -46,37 +44,62 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=3.6.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.3.6.0\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll</HintPath>
<Private>True</Private>
<Reference Include="Autofac, Version=6.4.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
<HintPath>..\..\packages\Autofac.6.4.0\lib\netstandard2.0\Autofac.dll</HintPath>
</Reference>
<Reference Include="Autofac.Integration.Mvc, Version=6.1.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
<HintPath>..\..\packages\Autofac.Mvc5.6.1.0\lib\net472\Autofac.Integration.Mvc.dll</HintPath>
</Reference>
<Reference Include="Autofac.Integration.WebApi, Version=6.1.1.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
<HintPath>..\..\packages\Autofac.WebApi2.6.1.1\lib\net472\Autofac.Integration.WebApi.dll</HintPath>
</Reference>
<Reference Include="Flurl, Version=4.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Flurl.4.0.0\lib\net472\Flurl.dll</HintPath>
</Reference>
<Reference Include="Flurl.Http, Version=4.0.2.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Flurl.Http.4.0.2\lib\net461\Flurl.Http.dll</HintPath>
</Reference>
<Reference Include="Hangfire.AspNet, Version=0.3.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Hangfire.AspNet.0.3.0\lib\net45\Hangfire.AspNet.dll</HintPath>
</Reference>
<Reference Include="Hangfire.Autofac, Version=2.7.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Hangfire.Autofac.2.7.0\lib\net45\Hangfire.Autofac.dll</HintPath>
</Reference>
<Reference Include="Hangfire.Core, Version=1.8.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Hangfire.Core.1.8.0\lib\net46\Hangfire.Core.dll</HintPath>
</Reference>
<Reference Include="Hangfire.InMemory, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Hangfire.InMemory.1.0.0\lib\net451\Hangfire.InMemory.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Bcl.AsyncInterfaces.8.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Owin.Host.SystemWeb, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Owin.Host.SystemWeb.3.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll</HintPath>
</Reference>
<Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
<HintPath>..\..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
</Reference>
<Reference Include="SKIT.FlurlHttpClient.Common, Version=3.1.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\SKIT.FlurlHttpClient.Common.3.1.1\lib\net462\SKIT.FlurlHttpClient.Common.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
</Reference>
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Configuration" />
<Reference Include="System.Data" />
<Reference Include="System.Core" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Diagnostics.DiagnosticSource, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Diagnostics.DiagnosticSource.6.0.0\lib\net461\System.Diagnostics.DiagnosticSource.dll</HintPath>
<Reference Include="System.Diagnostics.DiagnosticSource, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Diagnostics.DiagnosticSource.4.7.1\lib\net46\System.Diagnostics.DiagnosticSource.dll</HintPath>
</Reference>
<Reference Include="System.Memory, Version=4.0.1.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<Reference Include="System.Drawing" />
<Reference Include="System.Memory, Version=4.0.1.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Net.Http" />
<Reference Include="System.Net.Http.Formatting, Version=5.2.7.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.9\lib\net45\System.Net.Http.Formatting.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Net.Http.WebRequest" />
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
@ -84,124 +107,97 @@
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Text.Encodings.Web, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Text.Encodings.Web.6.0.0\lib\net461\System.Text.Encodings.Web.dll</HintPath>
<Reference Include="System.Text.Encodings.Web, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Text.Encodings.Web.8.0.0\lib\net462\System.Text.Encodings.Web.dll</HintPath>
</Reference>
<Reference Include="System.Text.Json, Version=8.0.0.5, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Text.Json.8.0.5\lib\net462\System.Text.Json.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll</HintPath>
</Reference>
<Reference Include="System.Web.Entity" />
<Reference Include="System.Web.ApplicationServices" />
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Core" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Web" />
<Reference Include="System.Web.Abstractions" />
<Reference Include="System.Web.ApplicationServices" />
<Reference Include="System.Web.Entity" />
<Reference Include="System.Web.Helpers, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.WebPages.3.2.9\lib\net45\System.Web.Helpers.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.Http, Version=5.2.7.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.9\lib\net45\System.Web.Http.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.Http.WebHost, Version=5.2.7.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.9\lib\net45\System.Web.Http.WebHost.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.Mvc, Version=5.2.7.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<Private>True</Private>
<HintPath>..\..\packages\Microsoft.AspNet.Mvc.5.2.9\lib\net45\System.Web.Mvc.dll</HintPath>
</Reference>
<Reference Include="System.Web.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.Razor.3.2.9\lib\net45\System.Web.Razor.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.Routing" />
<Reference Include="System.Web.WebPages, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.WebPages.3.2.9\lib\net45\System.Web.WebPages.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.WebPages.Deployment, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.WebPages.3.2.9\lib\net45\System.Web.WebPages.Deployment.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.WebPages.3.2.9\lib\net45\System.Web.WebPages.Razor.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq" />
<Reference Include="Autofac, Version=6.3.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
<HintPath>..\..\packages\Autofac.6.3.0\lib\netstandard2.0\Autofac.dll</HintPath>
</Reference>
<Reference Include="Autofac.Integration.Mvc, Version=6.0.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
<HintPath>..\..\packages\Autofac.Mvc5.6.0.0\lib\net472\Autofac.Integration.Mvc.dll</HintPath>
</Reference>
<Reference Include="Autofac.Integration.WebApi, Version=6.1.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
<HintPath>..\..\packages\Autofac.WebApi2.6.1.0\lib\net472\Autofac.Integration.WebApi.dll</HintPath>
</Reference>
<Reference Include="Hangfire.AspNet, Version=0.2.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Hangfire.AspNet.0.2.0\lib\net45\Hangfire.AspNet.dll</HintPath>
</Reference>
<Reference Include="Hangfire.Autofac, Version=2.3.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Hangfire.Autofac.2.3.1\lib\net45\Hangfire.Autofac.dll</HintPath>
</Reference>
<Reference Include="Hangfire.Core, Version=1.7.28.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Hangfire.Core.1.7.33\lib\net46\Hangfire.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Hangfire.InMemory, Version=0.3.4.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Hangfire.InMemory.0.3.6\lib\net45\Hangfire.InMemory.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.AspNetCore.Html.Abstractions, Version=2.2.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNetCore.Html.Abstractions.2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.Html.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AspNetCore.Razor, Version=2.2.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNetCore.Razor.2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.Razor.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Bcl.AsyncInterfaces.6.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.Owin, Version=3.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Owin.4.2.2\lib\net45\Microsoft.Owin.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Owin.Host.SystemWeb, Version=3.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Owin.Host.SystemWeb.4.2.2\lib\net45\Microsoft.Owin.Host.SystemWeb.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Web.Helpers, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.WebHelpers.3.2.9\lib\net45\Microsoft.Web.Helpers.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Web.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<Reference Include="System.Configuration" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="Microsoft.Web.Infrastructure, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<Private>True</Private>
<HintPath>..\..\packages\Microsoft.Web.Infrastructure.2.0.0\lib\net40\Microsoft.Web.Infrastructure.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<Reference Include="Newtonsoft.Json">
<HintPath>..\..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
<HintPath>..\..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
<Reference Include="System.Net.Http">
</Reference>
<Reference Include="SKIT.FlurlHttpClient.Common, Version=2.1.1.0, Culture=neutral, PublicKeyToken=null" />
<Reference Include="WebMatrix.Data, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.WebPages.Data.3.2.9\lib\net45\WebMatrix.Data.dll</HintPath>
<Private>True</Private>
<Reference Include="System.Net.Http.Formatting, Version=5.2.9.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.9\lib\net45\System.Net.Http.Formatting.dll</HintPath>
</Reference>
<Reference Include="WebMatrix.WebData, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.WebPages.WebData.3.2.9\lib\net45\WebMatrix.WebData.dll</HintPath>
<Reference Include="System.Net.Http.WebRequest">
</Reference>
<Reference Include="System.Web.Helpers, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<Private>True</Private>
<HintPath>..\..\packages\Microsoft.AspNet.WebPages.3.2.9\lib\net45\System.Web.Helpers.dll</HintPath>
</Reference>
<Reference Include="System.Web.Http, Version=5.2.9.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.9\lib\net45\System.Web.Http.dll</HintPath>
</Reference>
<Reference Include="System.Web.Http.WebHost, Version=5.2.9.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.9\lib\net45\System.Web.Http.WebHost.dll</HintPath>
</Reference>
<Reference Include="System.Web.Mvc, Version=5.2.9.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<Private>True</Private>
<HintPath>..\..\packages\Microsoft.AspNet.Mvc.5.2.9\lib\net45\System.Web.Mvc.dll</HintPath>
</Reference>
<Reference Include="System.Web.Optimization">
<HintPath>..\..\packages\Microsoft.AspNet.Web.Optimization.1.1.3\lib\net40\System.Web.Optimization.dll</HintPath>
</Reference>
<Reference Include="System.Web.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<Private>True</Private>
<HintPath>..\..\packages\Microsoft.AspNet.Razor.3.2.9\lib\net45\System.Web.Razor.dll</HintPath>
</Reference>
<Reference Include="System.Web.WebPages, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<Private>True</Private>
<HintPath>..\..\packages\Microsoft.AspNet.WebPages.3.2.9\lib\net45\System.Web.WebPages.dll</HintPath>
</Reference>
<Reference Include="System.Web.WebPages.Deployment, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<Private>True</Private>
<HintPath>..\..\packages\Microsoft.AspNet.WebPages.3.2.9\lib\net45\System.Web.WebPages.Deployment.dll</HintPath>
</Reference>
<Reference Include="System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<Private>True</Private>
<HintPath>..\..\packages\Microsoft.AspNet.WebPages.3.2.9\lib\net45\System.Web.WebPages.Razor.dll</HintPath>
</Reference>
<Reference Include="WebGrease">
<Private>True</Private>
<HintPath>..\..\packages\WebGrease.1.6.0\lib\WebGrease.dll</HintPath>
</Reference>
<Reference Include="Antlr3.Runtime">
<Private>True</Private>
<HintPath>..\..\packages\Antlr.3.5.0.2\lib\Antlr3.Runtime.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="App_Start\HangfireInitializer.cs" />
<Compile Include="App_Start\AutofacInitializer.cs" />
<Compile Include="App_Start\FilterRegistration.cs" />
<Compile Include="App_Start\RouteRegistration.cs" />
<Compile Include="App_Start\WebApiConfiguration.cs" />
<Reference Include="Microsoft.CodeDom.Providers.DotNetCompilerPlatform">
<HintPath>..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.2.0.1\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="App_Start\AutofacConfig.cs" />
<Compile Include="App_Start\FilterConfig.cs" />
<Compile Include="App_Start\HangfireConfig.cs" />
<Compile Include="App_Start\RouteConfig.cs" />
<Compile Include="App_Start\WebApiConfig.cs" />
<Compile Include="Controllers\TenpayNotifyController.cs" />
<Compile Include="Controllers\TenpayOrderController.cs" />
<Compile Include="Controllers\TenpayRefundController.cs" />
@ -213,10 +209,12 @@
<Compile Include="Options\TenpayOptions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Services\BackgroundJobs\TenpayCertificateRefreshingBackgroundJob.cs" />
<Compile Include="Services\HttpClients\Implements\WechatTenpayPublicKeyManagerFactory.cs" />
<Compile Include="Services\HttpClients\Implements\WechatTenpayCertificateManagerFactory.cs" />
<Compile Include="Services\HttpClients\Implements\WechatTenpayHttpClientFactory.cs" />
<Compile Include="Services\HttpClients\Implements\WechatTenpayClientFactory.cs" />
<Compile Include="Services\HttpClients\IWechatTenpayPublicKeyManagerFactory.cs" />
<Compile Include="Services\HttpClients\IWechatTenpayCertificateManagerFactory.cs" />
<Compile Include="Services\HttpClients\IWechatTenpayHttpClientFactory.cs" />
<Compile Include="Services\HttpClients\IWechatTenpayClientFactory.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="Global.asax" />
@ -229,38 +227,36 @@
</Content>
</ItemGroup>
<ItemGroup>
<None Include="Packages.config" />
<Folder Include="App_Data\" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\SKIT.FlurlHttpClient.Wechat.TenpayV3\SKIT.FlurlHttpClient.Wechat.TenpayV3.csproj">
<Project>{6fe502d4-c43d-49c9-9e57-d1ee566fd1c3}</Project>
<Project>{b9f2eb07-609c-d1b6-31de-1bfb32beee0d}</Project>
<Name>SKIT.FlurlHttpClient.Wechat.TenpayV3</Name>
</ProjectReference>
</ItemGroup>
<Import Project="..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.2.0.1\build\net46\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.props" Condition="Exists('..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.2.0.1\build\net46\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets" Condition="'$(VSToolsPath)' != ''" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" Condition="false" />
<Target Name="MvcBuildViews" AfterTargets="AfterBuild" Condition="'$(MvcBuildViews)'=='true'">
<AspNetCompiler VirtualPath="temp" PhysicalPath="$(WebProjectOutputDir)" />
</Target>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.2.0.1\build\net46\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.2.0.1\build\net46\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.props'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.3.6.0\build\net472\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.3.6.0\build\net472\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.targets'))" />
</Target>
<ProjectExtensions>
<VisualStudio>
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
<WebProjectProperties>
<UseIIS>True</UseIIS>
<AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>8080</DevelopmentServerPort>
<DevelopmentServerPort>49987</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>http://localhost:8080/</IISUrl>
<IISUrl>https://localhost:44339/</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>
<UseCustomServer>False</UseCustomServer>
<CustomServerUrl>
@ -270,5 +266,16 @@
</FlavorProperties>
</VisualStudio>
</ProjectExtensions>
<Import Project="..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.3.6.0\build\net472\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.targets" Condition="Exists('..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.3.6.0\build\net472\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.2.0.1\build\net46\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.2.0.1\build\net46\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.props'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target> -->
</Project>

View File

@ -0,0 +1,65 @@
using System;
using System.Diagnostics;
using System.Threading.Tasks;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Services.BackgroundJobs
{
using SKIT.FlurlHttpClient.Wechat.TenpayV3;
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Models;
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings;
internal class TenpayCertificateRefreshingBackgroundJob
{
private readonly HttpClients.IWechatTenpayClientFactory _wechatTenpayClientFactory;
public TenpayCertificateRefreshingBackgroundJob(
HttpClients.IWechatTenpayClientFactory wechatTenpayClientFactory)
{
_wechatTenpayClientFactory = wechatTenpayClientFactory;
}
public async Task ExecuteAsync()
{
// NOTICE:
// 2024-10-01 后微信支付新增了基于微信支付公钥的验证身份方式,
// 如果你已切换至使用平台公钥,则不再需要下载平台证书,可删除此定时任务。
foreach (var tenpayMerchantOptions in Options.TenpayOptions.Instance.Value.Merchants)
{
try
{
const string ALGORITHM_TYPE = "RSA";
using (var client = _wechatTenpayClientFactory.Create(tenpayMerchantOptions.MerchantId))
{
var request = new QueryCertificatesRequest() { AlgorithmType = ALGORITHM_TYPE };
var response = await client.ExecuteQueryCertificatesAsync(request);
if (response.IsSuccessful())
{
// NOTICE:
// 如果构造 Client 时启用了 `AutoDecryptResponseSensitiveProperty` 配置项,则无需再执行下面一行的手动解密方法:
response = client.DecryptResponseSensitiveProperty(response);
foreach (var certificate in response.CertificateList)
{
client.PlatformCertificateManager.AddEntry(CertificateEntry.Parse(ALGORITHM_TYPE, certificate));
}
Debug.WriteLine("刷新微信商户平台证书成功。");
}
else
{
Debug.WriteLine(
"刷新微信商户平台证书失败(状态码:{0},错误代码:{1},错误描述:{2})。",
response.GetRawStatus(), response.ErrorCode, response.ErrorMessage
);
}
}
}
catch (Exception ex)
{
Debug.WriteLine("刷新微信商户平台证书遇到异常。\r\n{0}", ex);
}
}
}
}
}

View File

@ -0,0 +1,9 @@
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Services.HttpClients
{
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings;
public interface IWechatTenpayPublicKeyManagerFactory
{
IPublicKeyManager Create(string merchantId);
}
}

View File

@ -13,11 +13,11 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Services.HttpClients.Imple
_dict = new ConcurrentDictionary<string, ICertificateManager>();
}
public CertificateManager Create(string merchantId)
public ICertificateManager Create(string merchantId)
{
// NOTICE:
// 这里的工厂方法是为了演示多租户而存在的,可根据商户号生成不同的证书管理器。
// 如果你的项目只存在唯一一个租户,那么直接注入 `CertificateManager` 即可。
// 如果你的项目只存在唯一一个租户,那么直接注入 `PlatformCertificateManager` 即可。
return _dict.GetOrAdd(merchantId, new InMemoryCertificateManager());
}

View File

@ -1,13 +1,21 @@
using System;
using System.Linq;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Services.HttpClients.Implements
{
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings;
internal partial class WechatTenpayClientFactory : IWechatTenpayClientFactory
{
private readonly IWechatTenpayCertificateManagerFactory _tenpayCertificateManagerFactory;
private readonly IWechatTenpayPublicKeyManagerFactory _tenpayPublicKeyManagerFactory;
public WechatTenpayClientFactory(
IWechatTenpayCertificateManagerFactory tenpayCertificateManagerFactory)
IWechatTenpayCertificateManagerFactory tenpayCertificateManagerFactory,
IWechatTenpayPublicKeyManagerFactory tenpayPublicKeyManagerFactory)
{
_tenpayCertificateManagerFactory = tenpayCertificateManagerFactory;
_tenpayPublicKeyManagerFactory = tenpayPublicKeyManagerFactory;
}
public WechatTenpayClient Create(string merchantId)
@ -24,11 +32,18 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Services.HttpClients.Imple
{
MerchantId = tenpayMerchantOptions.MerchantId,
MerchantV3Secret = tenpayMerchantOptions.SecretV3,
MerchantCertSerialNumber = tenpayMerchantOptions.CertSerialNumber,
MerchantCertPrivateKey = tenpayMerchantOptions.CertPrivateKey,
CertificateManager = _tenpayCertificateManagerFactory.Create(tenpayMerchantOptions.MerchantId),
MerchantCertificateSerialNumber = tenpayMerchantOptions.CertificateSerialNumber,
MerchantCertificatePrivateKey = tenpayMerchantOptions.CertificatePrivateKey,
AutoEncryptRequestSensitiveProperty = false,
AutoDecryptResponseSensitiveProperty = false
AutoDecryptResponseSensitiveProperty = false,
// 基于平台证书的认证方式还需设置以下参数:
PlatformAuthScheme = PlatformAuthScheme.Certificate,
PlatformCertificateManager = _tenpayCertificateManagerFactory.Create(tenpayMerchantOptions.MerchantId),
// 基于平台公钥的认证方式还需设置以下参数:
//PlatformAuthScheme = PlatformAuthScheme.PublicKey,
//PlatformPublicKeyManager = _tenpayPublicKeyManagerFactory.Create(tenpayMerchantOptions.MerchantId)
});
}
}

View File

@ -0,0 +1,36 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Services.HttpClients.Implements
{
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings;
internal partial class WechatTenpayPublicKeyManagerFactory : IWechatTenpayPublicKeyManagerFactory
{
private readonly ConcurrentDictionary<string, IPublicKeyManager> _dict;
public WechatTenpayPublicKeyManagerFactory()
{
_dict = new ConcurrentDictionary<string, IPublicKeyManager>();
}
public IPublicKeyManager Create(string merchantId)
{
// NOTICE:
// 这里的工厂方法是为了演示多租户而存在的,可根据商户号生成不同的公钥管理器。
// 如果你的项目只存在唯一一个租户,那么直接注入 `PlatformPublicKeyManager` 即可。
var tenpayMerchantOptions = Options.TenpayOptions.Instance.Value.Merchants?.FirstOrDefault(e => string.Equals(merchantId, e.MerchantId));
if (tenpayMerchantOptions == null)
throw new Exception("未在配置项中找到该 MerchantId 对应的微信商户号。");
return _dict.GetOrAdd(merchantId, (_) =>
{
var manager = new InMemoryPublicKeyManager();
manager.AddEntry(new PublicKeyEntry(PublicKeyEntry.ALGORITHM_TYPE_RSA, tenpayMerchantOptions.PlatformPublicKeyId, tenpayMerchantOptions.PlatformPublicKey));
return manager;
});
}
}
}

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 有关使用 Web.config 转换的详细信息,请访问 https://go.microsoft.com/fwlink/?LinkId=301874 -->
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<!--
在下例中“SetAttributes”转换将更改
“connectionString”的值仅在“Match”定位器找到值为“MyDB”的
特性“name”时使用“ReleaseSQLServer”。
<connectionStrings>
<add name="MyDB"
connectionString="Data Source=ReleaseSQLServer;Initial Catalog=MyReleaseDB;Integrated Security=True"
xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
</connectionStrings>
-->
<system.web>
<!--
在以下示例中,"Replace" 转换将替换 Web.config 文件的
整个 <customErrors> 节。
请注意,由于在 <system.web> 节点下只有一个
customErrors 节,因此无需使用 "xdt:Locator" 属性。
<customErrors defaultRedirect="GenericError.htm"
mode="RemoteOnly" xdt:Transform="Replace">
<error statusCode="500" redirect="InternalError.htm"/>
</customErrors>
-->
</system.web>
</configuration>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 有关使用 Web.config 转换的详细信息,请访问 https://go.microsoft.com/fwlink/?LinkId=301874 -->
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<!--
在下例中“SetAttributes”转换将更改
“connectionString”的值仅在“Match”定位器找到值为“MyDB”的
特性“name”时使用“ReleaseSQLServer”。
<connectionStrings>
<add name="MyDB"
connectionString="Data Source=ReleaseSQLServer;Initial Catalog=MyReleaseDB;Integrated Security=True"
xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
</connectionStrings>
-->
<system.web>
<compilation xdt:Transform="RemoveAttributes(debug)" />
<!--
在以下示例中,"Replace" 转换将替换 Web.config 文件的
整个 <customErrors> 节。
请注意,由于在 <system.web> 节点下只有一个
customErrors 节,因此无需使用 "xdt:Locator" 属性。
<customErrors defaultRedirect="GenericError.htm"
mode="RemoteOnly" xdt:Transform="Replace">
<error statusCode="500" redirect="InternalError.htm"/>
</customErrors>
-->
</system.web>
</configuration>

View File

@ -1,58 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<!--
有关如何配置 ASP.NET 应用程序的详细信息,请访问
https://go.microsoft.com/fwlink/?LinkId=301879
-->
<configuration>
<appSettings>
<add key="TenpayOptions_Merchant_0_MerchantId" value="填写商户号" />
<add key="TenpayOptions_Merchant_0_SecretV3" value="填写商户 V3 API 密钥" />
<add key="TenpayOptions_Merchant_0_CertificateSerialNumber" value="填写商户证书序列号" />
<add key="TenpayOptions_Merchant_0_CertificatePrivateKey" value="填写商户证书文件内容" />
<add key="TenpayOptions_Merchant_0_PlatformPublicKeyId" value="填写平台公钥 ID仅当基于平台公钥的认证方式时需要" />
<add key="TenpayOptions_Merchant_0_PlatformPublicKey" value="填写平台公钥内容(仅当基于平台公钥的认证方式时需要)" />
<add key="TenpayOptions_NotifyUrl" value="https://localhost:5001" />
</appSettings>
<system.web>
<compilation debug="true" targetFramework="4.8" />
<httpRuntime targetFramework="4.8" />
</system.web>
<system.webServer>
<handlers>
<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
<remove name="OPTIONSVerbHandler" />
<remove name="TRACEVerbHandler" />
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>
</system.webServer>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Diagnostics.DiagnosticSource" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
<assemblyIdentity name="Antlr3.Runtime" publicKeyToken="eb42632606e9261f" />
<bindingRedirect oldVersion="0.0.0.0-3.5.0.2" newVersion="3.5.0.2" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.1.1" newVersion="4.0.1.1" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Net.Http.Formatting" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.7.0" newVersion="5.2.7.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Text.Encodings.Web" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.2.0.1" newVersion="4.2.0.1" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.Http" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.7.0" newVersion="5.2.7.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Autofac" publicKeyToken="17863af14b0044da" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.3.0.0" newVersion="6.3.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Bcl.AsyncInterfaces" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.1.0.0" newVersion="3.1.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<assemblyIdentity name="Newtonsoft.Json" culture="neutral" publicKeyToken="30ad4fe6b2a6aeed" />
<bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.Optimization" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="1.1.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Web.Infrastructure" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="0.0.0.0-2.0.0.0" newVersion="2.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="1.0.0.0-1.6.5135.21930" newVersion="1.6.5135.21930" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
@ -65,24 +59,44 @@
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="1.0.0.0-5.2.9.0" newVersion="5.2.9.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.1.2" newVersion="4.0.1.2" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.Http" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.9.0" newVersion="5.2.9.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Net.Http.Formatting" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.9.0" newVersion="5.2.9.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Autofac" publicKeyToken="17863af14b0044da" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.4.0.0" newVersion="6.4.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Bcl.AsyncInterfaces" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.2.0.1" newVersion="4.2.0.1" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Text.Json" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.5" newVersion="8.0.0.5" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<system.web>
<compilation debug="true" targetFramework="4.7.2" />
<httpRuntime targetFramework="4.7.2" />
</system.web>
<system.codedom>
<compilers>
<compiler extension=".cs" language="c#;cs;csharp" warningLevel="4" compilerOptions="/langversion:7.3 /nowarn:1659;1699;1701;612;618" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=3.6.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<compiler extension=".vb" language="vb;vbs;visualbasic;vbscript" warningLevel="4" compilerOptions="/langversion:default /nowarn:41008,40000,40008 /define:_MYTYPE=\&quot;Web\&quot; /optionInfer+" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=3.6.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=2.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:default /nowarn:1659;1699;1701" />
<compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=2.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:default /nowarn:41008 /define:_MYTYPE=\&quot;Web\&quot; /optionInfer+" />
</compilers>
</system.codedom>
<system.webServer>
<handlers>
<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
<remove name="OPTIONSVerbHandler" />
<remove name="TRACEVerbHandler" />
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>
</system.webServer>
</configuration>
</configuration>

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Antlr" version="3.5.0.2" targetFramework="net48" />
<package id="Autofac" version="6.4.0" targetFramework="net48" />
<package id="Autofac.Mvc5" version="6.1.0" targetFramework="net48" />
<package id="Autofac.WebApi2" version="6.1.1" targetFramework="net48" />
<package id="bootstrap" version="5.2.3" targetFramework="net48" />
<package id="Flurl" version="4.0.0" targetFramework="net48" />
<package id="Flurl.Http" version="4.0.2" targetFramework="net48" />
<package id="Hangfire.AspNet" version="0.3.0" targetFramework="net48" />
<package id="Hangfire.Autofac" version="2.7.0" targetFramework="net48" />
<package id="Hangfire.Core" version="1.8.0" targetFramework="net48" />
<package id="Hangfire.InMemory" version="1.0.0" targetFramework="net48" />
<package id="jQuery" version="3.7.0" targetFramework="net48" />
<package id="Microsoft.AspNet.Mvc" version="5.2.9" targetFramework="net48" />
<package id="Microsoft.AspNet.Mvc.zh-Hans" version="5.2.9" targetFramework="net48" />
<package id="Microsoft.AspNet.Razor" version="3.2.9" targetFramework="net48" />
<package id="Microsoft.AspNet.Razor.zh-Hans" version="3.2.9" targetFramework="net48" />
<package id="Microsoft.AspNet.Web.Optimization" version="1.1.3" targetFramework="net48" />
<package id="Microsoft.AspNet.Web.Optimization.zh-Hans" version="1.1.3" targetFramework="net48" />
<package id="Microsoft.AspNet.WebApi" version="5.2.9" targetFramework="net48" />
<package id="Microsoft.AspNet.WebApi.Client" version="5.2.9" targetFramework="net48" />
<package id="Microsoft.AspNet.WebApi.Client.zh-Hans" version="5.2.9" targetFramework="net48" />
<package id="Microsoft.AspNet.WebApi.Core" version="5.2.9" targetFramework="net48" />
<package id="Microsoft.AspNet.WebApi.Core.zh-Hans" version="5.2.9" targetFramework="net48" />
<package id="Microsoft.AspNet.WebApi.HelpPage" version="5.2.9" targetFramework="net48" />
<package id="Microsoft.AspNet.WebApi.WebHost" version="5.2.9" targetFramework="net48" />
<package id="Microsoft.AspNet.WebApi.WebHost.zh-Hans" version="5.2.9" targetFramework="net48" />
<package id="Microsoft.AspNet.WebPages" version="3.2.9" targetFramework="net48" />
<package id="Microsoft.AspNet.WebPages.zh-Hans" version="3.2.9" targetFramework="net48" />
<package id="Microsoft.Bcl.AsyncInterfaces" version="8.0.0" targetFramework="net48" />
<package id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="2.0.1" targetFramework="net48" />
<package id="Microsoft.Owin" version="3.0.1" targetFramework="net48" />
<package id="Microsoft.Owin.Host.SystemWeb" version="3.0.1" targetFramework="net48" />
<package id="Microsoft.Web.Infrastructure" version="2.0.0" targetFramework="net48" />
<package id="Modernizr" version="2.8.3" targetFramework="net48" />
<package id="Newtonsoft.Json" version="13.0.3" targetFramework="net48" />
<package id="Owin" version="1.0" targetFramework="net48" />
<package id="SKIT.FlurlHttpClient.Common" version="3.1.1" targetFramework="net48" />
<package id="System.Buffers" version="4.5.1" targetFramework="net48" />
<package id="System.Diagnostics.DiagnosticSource" version="4.7.1" targetFramework="net48" />
<package id="System.Memory" version="4.5.5" targetFramework="net48" />
<package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net48" />
<package id="System.Runtime.CompilerServices.Unsafe" version="6.0.0" targetFramework="net48" />
<package id="System.Text.Encodings.Web" version="8.0.0" targetFramework="net48" />
<package id="System.Text.Json" version="8.0.5" targetFramework="net48" />
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net48" />
<package id="System.ValueTuple" version="4.5.0" targetFramework="net48" />
<package id="WebGrease" version="1.6.0" targetFramework="net48" />
</packages>