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 |
|||
{ |
|||
[DependsOn( |
|||
typeof(EShopPaymentsWeChatPayDomainSharedModule) |
|||
)] |
|||
typeof(EShopPaymentsWeChatPayDomainSharedModule), |
|||
typeof(AbpWeChatPayModule) |
|||
)] |
|||
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 |
|||
{ |
|||
public class WeChatPaySettingDefinitionProvider : SettingDefinitionProvider |
|||
{ |
|||
private readonly IConfiguration _configuration; |
|||
|
|||
public WeChatPaySettingDefinitionProvider(IConfiguration configuration) |
|||
{ |
|||
_configuration = configuration; |
|||
} |
|||
|
|||
public override void Define(ISettingDefinitionContext context) |
|||
{ |
|||
/* Define module settings here. |
|||
* 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