mirror of https://github.com/EasyAbp/EShop.git
26 changed files with 3331 additions and 39 deletions
@ -0,0 +1,28 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Users; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Payments.WeChatPay |
||||
|
{ |
||||
|
[Dependency(ServiceLifetime.Singleton, TryRegister = true)] |
||||
|
public class ClaimPaymentOpenIdProvider : IPaymentOpenIdProvider |
||||
|
{ |
||||
|
public const string OpenIdClaimType = "WeChatOpenId"; |
||||
|
|
||||
|
private readonly ICurrentUser _currentUser; |
||||
|
|
||||
|
public ClaimPaymentOpenIdProvider(ICurrentUser currentUser) |
||||
|
{ |
||||
|
_currentUser = currentUser; |
||||
|
} |
||||
|
|
||||
|
public Task<string> FindUserOpenIdAsync(Guid userId) |
||||
|
{ |
||||
|
return Task.FromResult(userId == _currentUser.Id |
||||
|
? _currentUser.FindClaim(OpenIdClaimType).Value |
||||
|
: throw new NotSupportedException()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,12 +1,25 @@ |
|||||
using Volo.Abp.Modularity; |
using System.Collections.Generic; |
||||
|
using EasyAbp.Abp.WeChat.Pay; |
||||
|
using EasyAbp.Abp.WeChat.Pay.Infrastructure.OptionResolve; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
namespace EasyAbp.EShop.Payments.WeChatPay |
namespace EasyAbp.EShop.Payments.WeChatPay |
||||
{ |
{ |
||||
[DependsOn( |
[DependsOn( |
||||
typeof(EShopPaymentsWeChatPayDomainSharedModule) |
typeof(EShopPaymentsWeChatPayDomainSharedModule), |
||||
)] |
typeof(AbpWeChatPayModule) |
||||
|
)] |
||||
public class EShopPaymentsWeChatPayDomainModule : AbpModule |
public class EShopPaymentsWeChatPayDomainModule : AbpModule |
||||
{ |
{ |
||||
|
public override void PostConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
var configuration = context.Services.GetConfiguration(); |
||||
|
|
||||
|
Configure<AbpWeChatPayResolveOptions>(options => |
||||
|
{ |
||||
|
options.ResolveContributors.AddFirst(new SettingOptionResolveContributor()); |
||||
|
}); |
||||
|
} |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,47 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using System.Xml; |
||||
|
using EasyAbp.Abp.WeChat.Pay.Infrastructure; |
||||
|
using EasyAbp.EShop.Payments.Payments; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Timing; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Payments.WeChatPay |
||||
|
{ |
||||
|
public class EShopWeChatPayHandler : IWeChatPayHandler, ITransientDependency |
||||
|
{ |
||||
|
private readonly IClock _clock; |
||||
|
private readonly IPaymentRepository _paymentRepository; |
||||
|
|
||||
|
public EShopWeChatPayHandler( |
||||
|
IClock clock, |
||||
|
IPaymentRepository paymentRepository) |
||||
|
{ |
||||
|
_clock = clock; |
||||
|
_paymentRepository = paymentRepository; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task HandleAsync(XmlDocument xmlDocument) |
||||
|
{ |
||||
|
if (xmlDocument.Attributes == null || xmlDocument.Attributes["return_code"].Value != "SUCCESS") |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (xmlDocument.Attributes["result_code"].Value == "SUCCESS") |
||||
|
{ |
||||
|
var orderId = Guid.Parse(xmlDocument.Attributes["out_trade_no"].Value); |
||||
|
|
||||
|
var payment = await _paymentRepository.GetAsync(orderId); |
||||
|
|
||||
|
payment.SetExternalTradingCode(xmlDocument.Attributes["transaction_id"].Value); |
||||
|
|
||||
|
payment.CompletePayment(_clock.Now); |
||||
|
|
||||
|
await _paymentRepository.UpdateAsync(payment, true); |
||||
|
} |
||||
|
|
||||
|
// Todo: record xml
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Payments.WeChatPay |
||||
|
{ |
||||
|
public interface IPaymentOpenIdProvider |
||||
|
{ |
||||
|
Task<string> FindUserOpenIdAsync(Guid userId); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using EasyAbp.Abp.WeChat.Pay; |
||||
|
using EasyAbp.Abp.WeChat.Pay.Infrastructure.OptionResolve; |
||||
|
using EasyAbp.EShop.Payments.WeChatPay.Settings; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.Settings; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Payments.WeChatPay |
||||
|
{ |
||||
|
public class SettingOptionResolveContributor : IWeChatPayOptionResolveContributor |
||||
|
{ |
||||
|
public const string ContributorName = "Setting"; |
||||
|
|
||||
|
public string Name => ContributorName; |
||||
|
|
||||
|
public virtual async Task ResolveAsync(WeChatPayOptionsResolverContext context) |
||||
|
{ |
||||
|
var settingProvider = context.ServiceProvider.GetRequiredService<ISettingProvider>(); |
||||
|
context.Options = new AbpWeChatPayOptions |
||||
|
{ |
||||
|
ApiKey = await settingProvider.GetOrNullAsync(WeChatPaySettings.WeChatPayPaymentMethod.ApiKey), |
||||
|
MchId = await settingProvider.GetOrNullAsync(WeChatPaySettings.WeChatPayPaymentMethod.MchId), |
||||
|
IsSandBox = Convert.ToBoolean(await settingProvider.GetOrNullAsync(WeChatPaySettings.WeChatPayPaymentMethod.IsSandBox)), |
||||
|
NotifyUrl = await settingProvider.GetOrNullAsync(WeChatPaySettings.WeChatPayPaymentMethod.NotifyUrl), |
||||
|
RefundNotifyUrl = await settingProvider.GetOrNullAsync(WeChatPaySettings.WeChatPayPaymentMethod.RefundNotifyUrl), |
||||
|
CertificatePath = await settingProvider.GetOrNullAsync(WeChatPaySettings.WeChatPayPaymentMethod.CertificatePath), |
||||
|
CertificateSecret = await settingProvider.GetOrNullAsync(WeChatPaySettings.WeChatPayPaymentMethod.CertificateSecret) |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,14 +1,35 @@ |
|||||
using Volo.Abp.Settings; |
using System; |
||||
|
using Microsoft.Extensions.Configuration; |
||||
|
using Volo.Abp.Settings; |
||||
|
|
||||
namespace EasyAbp.EShop.Payments.WeChatPay.Settings |
namespace EasyAbp.EShop.Payments.WeChatPay.Settings |
||||
{ |
{ |
||||
public class WeChatPaySettingDefinitionProvider : SettingDefinitionProvider |
public class WeChatPaySettingDefinitionProvider : SettingDefinitionProvider |
||||
{ |
{ |
||||
|
private readonly IConfiguration _configuration; |
||||
|
|
||||
|
public WeChatPaySettingDefinitionProvider(IConfiguration configuration) |
||||
|
{ |
||||
|
_configuration = configuration; |
||||
|
} |
||||
|
|
||||
public override void Define(ISettingDefinitionContext context) |
public override void Define(ISettingDefinitionContext context) |
||||
{ |
{ |
||||
/* Define module settings here. |
/* Define module settings here. |
||||
* Use names from WeChatPaySettings class. |
* Use names from WeChatPaySettings class. |
||||
*/ |
*/ |
||||
|
|
||||
|
context.Add( |
||||
|
new SettingDefinition(WeChatPaySettings.WeChatPayPaymentMethod.MchId), |
||||
|
new SettingDefinition(WeChatPaySettings.WeChatPayPaymentMethod.ApiKey), |
||||
|
new SettingDefinition(WeChatPaySettings.WeChatPayPaymentMethod.IsSandBox, "false"), |
||||
|
new SettingDefinition(WeChatPaySettings.WeChatPayPaymentMethod.NotifyUrl, |
||||
|
_configuration["App:SelfUrl"].EnsureEndsWith('/') + "WeChatPay/Notify"), |
||||
|
new SettingDefinition(WeChatPaySettings.WeChatPayPaymentMethod.RefundNotifyUrl, |
||||
|
_configuration["App:SelfUrl"].EnsureEndsWith('/') + "WeChatPay/RefundNotify"), |
||||
|
new SettingDefinition(WeChatPaySettings.WeChatPayPaymentMethod.CertificatePath), |
||||
|
new SettingDefinition(WeChatPaySettings.WeChatPayPaymentMethod.CertificateSecret) |
||||
|
); |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
using Volo.Abp; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Payments.WeChatPay |
||||
|
{ |
||||
|
public class UnifiedOrderFailedException : BusinessException |
||||
|
{ |
||||
|
public UnifiedOrderFailedException(string returnCode, string returnMsg) : base( |
||||
|
message: $"Unified order failed, return_code: {returnCode}, return_msg: {returnMsg}") |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public UnifiedOrderFailedException(string returnCode, string returnMsg, string errCode, string errCodeDes) : |
||||
|
base(message: $"Unified order failed, return_code: {returnCode}, return_msg: {returnMsg}, err_code: {errCode}, err_code_des: {errCodeDes}") |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,106 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using EasyAbp.Abp.WeChat.Pay.Services.Pay; |
||||
|
using EasyAbp.EShop.Payments.Payments; |
||||
|
using EasyAbp.EShop.Payments.WeChatPay.Settings; |
||||
|
using Microsoft.Extensions.Configuration; |
||||
|
using Volo.Abp.Data; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Settings; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Payments.WeChatPay |
||||
|
{ |
||||
|
public class WeChatPayPaymentServiceProvider : IPaymentServiceProvider, ITransientDependency |
||||
|
{ |
||||
|
private readonly ServiceProviderPayService _serviceProviderPayService; |
||||
|
private readonly IConfiguration _configuration; |
||||
|
private readonly ISettingProvider _settingProvider; |
||||
|
private readonly IPaymentOpenIdProvider _paymentOpenIdProvider; |
||||
|
private readonly IPaymentRepository _paymentRepository; |
||||
|
|
||||
|
public const string PaymentMethod = "WeChatPay"; |
||||
|
|
||||
|
public WeChatPayPaymentServiceProvider( |
||||
|
ServiceProviderPayService serviceProviderPayService, |
||||
|
IConfiguration configuration, |
||||
|
ISettingProvider settingProvider, |
||||
|
IPaymentOpenIdProvider paymentOpenIdProvider, |
||||
|
IPaymentRepository paymentRepository) |
||||
|
{ |
||||
|
_serviceProviderPayService = serviceProviderPayService; |
||||
|
_configuration = configuration; |
||||
|
_settingProvider = settingProvider; |
||||
|
_paymentOpenIdProvider = paymentOpenIdProvider; |
||||
|
_paymentRepository = paymentRepository; |
||||
|
} |
||||
|
|
||||
|
public async Task<Payment> PayAsync(Payment payment, Dictionary<string, object> inputExtraProperties, |
||||
|
Dictionary<string, object> payeeConfigurations) |
||||
|
{ |
||||
|
if (payment.Currency != "CNY") |
||||
|
{ |
||||
|
throw new CurrencyNotSupportedException(payment.PaymentMethod, payment.Currency); |
||||
|
} |
||||
|
|
||||
|
var payeeAccount = payeeConfigurations.GetOrDefault("PayeeAccount") as string ?? |
||||
|
await _settingProvider.GetOrNullAsync(WeChatPaySettings.WeChatPayPaymentMethod |
||||
|
.MchId); |
||||
|
|
||||
|
payment.SetPayeeAccount(payeeAccount); |
||||
|
|
||||
|
var openId = await _paymentOpenIdProvider.FindUserOpenIdAsync(payment.UserId); |
||||
|
|
||||
|
var outTradeNo = payment.Id.ToString("N"); |
||||
|
|
||||
|
var result = await _serviceProviderPayService.UnifiedOrderAsync( |
||||
|
appId: inputExtraProperties.GetOrDefault("appid") as string, |
||||
|
subAppId: null, |
||||
|
mchId: payment.PayeeAccount, |
||||
|
subMchId: null, |
||||
|
deviceInfo: payeeConfigurations.GetOrDefault("deviceInfo") as string ?? "EasyAbp Payment Service", |
||||
|
body: payeeConfigurations.GetOrDefault("body") as string ?? "EasyAbp Payment Service", |
||||
|
detail: payeeConfigurations.GetOrDefault("detail") as string, |
||||
|
attach: payeeConfigurations.GetOrDefault("attach") as string, |
||||
|
outTradeNo: outTradeNo, |
||||
|
feeType: payment.Currency, |
||||
|
totalFee: ConvertDecimalToWeChatPayFee(payment.ActualPaymentAmount), |
||||
|
billCreateIp: "127.0.0.1", |
||||
|
timeStart: null, |
||||
|
timeExpire: null, |
||||
|
goodsTag: payeeConfigurations.GetOrDefault("goods_tag") as string, |
||||
|
notifyUrl: payeeConfigurations.GetOrDefault("notify_url") as string |
||||
|
?? _configuration["App:SelfUrl"].EnsureEndsWith('/') + "WeChatPay/Notify", |
||||
|
tradeType: inputExtraProperties.GetOrDefault("trade_type") as string, |
||||
|
productId: null, |
||||
|
limitPay: payeeConfigurations.GetOrDefault("limit_pay") as string, |
||||
|
openId: openId, |
||||
|
subOpenId: null, |
||||
|
receipt: payeeConfigurations.GetOrDefault("receipt") as string ?? "N", |
||||
|
sceneInfo: null); |
||||
|
|
||||
|
if (result.Attributes == null || result.Attributes["return_code"].Value != "SUCCESS") |
||||
|
{ |
||||
|
throw new UnifiedOrderFailedException(result.Attributes?["return_code"].Value, result.Attributes?["return_msg"].Value); |
||||
|
} |
||||
|
|
||||
|
if (result.Attributes["result_code"].Value != "SUCCESS") |
||||
|
{ |
||||
|
throw new UnifiedOrderFailedException(result.Attributes["return_code"]?.Value, |
||||
|
result.Attributes["return_msg"]?.Value, result.Attributes["err_code_des"]?.Value, |
||||
|
result.Attributes["err_code"]?.Value); |
||||
|
} |
||||
|
|
||||
|
payment.SetProperty("trade_type", result.Attributes["trade_type"]); |
||||
|
payment.SetProperty("prepay_id", result.Attributes["prepay_id"]); |
||||
|
payment.SetProperty("code_url", result.Attributes["code_url"]); |
||||
|
|
||||
|
return await _paymentRepository.UpdateAsync(payment, true); |
||||
|
} |
||||
|
|
||||
|
private static int ConvertDecimalToWeChatPayFee(decimal paymentActualPaymentAmount) |
||||
|
{ |
||||
|
return Convert.ToInt32(decimal.Round(paymentActualPaymentAmount, 2, MidpointRounding.AwayFromZero) * 100); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Payments.Payments |
||||
|
{ |
||||
|
public class CurrencyNotSupportedException : BusinessException |
||||
|
{ |
||||
|
public CurrencyNotSupportedException(string paymentMethod, string currency) : base( |
||||
|
message: $"Payment method {paymentMethod} does not support currency: {currency}") |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public CurrencyNotSupportedException(string paymentMethod, string currency, Guid storeId) : base( |
||||
|
message: $"Payment method {paymentMethod} in store {storeId} does not support currency: {currency}") |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
using Volo.Abp; |
||||
|
|
||||
|
namespace EasyAbp.EShop.Payments.Payments |
||||
|
{ |
||||
|
public class PayeeConfigurationMissingValueException : BusinessException |
||||
|
{ |
||||
|
public PayeeConfigurationMissingValueException(string paymentMethod, string configurationKey) : base( |
||||
|
message: $"Payment method ({paymentMethod}) is missing configuration: {configurationKey}.") |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
File diff suppressed because it is too large
@ -0,0 +1,24 @@ |
|||||
|
using System; |
||||
|
using Microsoft.EntityFrameworkCore.Migrations; |
||||
|
|
||||
|
namespace EasyMall.Migrations |
||||
|
{ |
||||
|
public partial class AddedUserIdToPayment : Migration |
||||
|
{ |
||||
|
protected override void Up(MigrationBuilder migrationBuilder) |
||||
|
{ |
||||
|
migrationBuilder.AddColumn<Guid>( |
||||
|
name: "UserId", |
||||
|
table: "PaymentsPayments", |
||||
|
nullable: false, |
||||
|
defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); |
||||
|
} |
||||
|
|
||||
|
protected override void Down(MigrationBuilder migrationBuilder) |
||||
|
{ |
||||
|
migrationBuilder.DropColumn( |
||||
|
name: "UserId", |
||||
|
table: "PaymentsPayments"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue