Files
tiku-backend.net/Tiku.Infrastructure/Commerce/AlipayProvider.cs
xiong c497a3ca8d
Some checks failed
ci / release-gate (push) Has been cancelled
清理代码
2026-08-03 12:31:39 +08:00

166 lines
6.1 KiB
C#

using System.Globalization;
using System.Text.Json;
using Aop.Api;
using Aop.Api.Domain;
using Aop.Api.Request;
using Aop.Api.Util;
using Tiku.Application.Commerce;
namespace Tiku.Infrastructure.Commerce;
internal sealed class AlipayProvider : IPaymentProvider
{
public string Provider => PaymentProviders.Alipay;
public Task<CreatePaymentProviderResult> CreatePaymentAsync(
PaymentProviderAccount account,
CreatePaymentProviderRequest request,
CancellationToken cancellationToken = default)
{
_ = cancellationToken;
var client = BuildClient(account);
var alipayRequest = new AlipayTradeWapPayRequest();
alipayRequest.SetNotifyUrl(request.NotifyUrl);
if (!string.IsNullOrWhiteSpace(request.ReturnUrl)) alipayRequest.SetReturnUrl(request.ReturnUrl);
alipayRequest.SetBizModel(new AlipayTradeWapPayModel
{
OutTradeNo = request.OrderNo,
Subject = request.Subject,
TotalAmount = FormatYuan(request.AmountCents),
ProductCode = "QUICK_WAP_WAY",
QuitUrl = request.QuitUrl
});
var response = client.pageExecute(alipayRequest);
if (string.IsNullOrWhiteSpace(response.Body))
throw new PaymentProviderException("Alipay create payment returned an empty body.", "alipay_create_failed");
var clientPayload = JsonSerializer.SerializeToElement(new
{
formHtml = response.Body
});
var rawPayload = JsonSerializer.SerializeToElement(new
{
response.Body,
request.OrderNo,
request.AmountCents
});
return Task.FromResult(new CreatePaymentProviderResult(
Provider,
request.Method,
"pending",
null,
clientPayload,
rawPayload));
}
public Task<PaymentNotificationResult> ParsePaymentNotificationAsync(
PaymentProviderAccount account,
PaymentNotificationRequest request,
CancellationToken cancellationToken = default)
{
_ = BuildClient(account);
var values = ToDictionary(request.Body);
var signatureValid = AlipaySignature.RSACheckV1(
values,
Required(account.ConfigPublic, "alipayPublicKey"),
GetString(account.ConfigPublic, "charset") ?? "UTF-8",
GetString(account.ConfigPublic, "signType") ?? "RSA2",
false);
var eventId = GetValue(values, "notify_id") ?? GetValue(values, "trade_no") ?? Guid.NewGuid().ToString("N");
var orderNo = GetValue(values, "out_trade_no")
?? throw new PaymentProviderException("Alipay notification order number is missing.",
"alipay_notify_order_missing");
var tradeStatus = GetValue(values, "trade_status");
var amountCents = YuanToCents(GetValue(values, "total_amount") ?? GetValue(values, "receipt_amount"));
DateTimeOffset? paidAt = DateTimeOffset.TryParse(GetValue(values, "gmt_payment"), out var parsedPaidAt)
? parsedPaidAt
: null;
return Task.FromResult(new PaymentNotificationResult(
Provider,
"payment_notify",
eventId,
orderNo,
GetValue(values, "trade_no"),
amountCents,
string.Equals(tradeStatus, "TRADE_SUCCESS", StringComparison.OrdinalIgnoreCase) ||
string.Equals(tradeStatus, "TRADE_FINISHED", StringComparison.OrdinalIgnoreCase),
signatureValid,
paidAt,
request.Body));
}
private static DefaultAopClient BuildClient(PaymentProviderAccount account)
{
return new DefaultAopClient(
Required(account.ConfigPublic, "gatewayUrl", "gateway"),
Required(account.ConfigPublic, "appId"),
Required(account.SecretPayload, "privateKey", "appPrivateKey"),
"json",
"1.0",
"RSA2",
Required(account.ConfigPublic, "alipayPublicKey"),
"UTF-8",
false);
}
private static string Required(JsonElement element, params string[] keys)
{
if (element.ValueKind == JsonValueKind.Object)
foreach (var key in keys)
if (element.TryGetProperty(key, out var property) &&
property.ValueKind == JsonValueKind.String &&
!string.IsNullOrWhiteSpace(property.GetString()))
return property.GetString()!;
throw new PaymentProviderException(
"Alipay provider configuration is incomplete.",
"alipay_config_incomplete");
}
private static Dictionary<string, string> ToDictionary(JsonElement element)
{
var values = new Dictionary<string, string>(StringComparer.Ordinal);
if (element.ValueKind != JsonValueKind.Object) return values;
foreach (var property in element.EnumerateObject())
values[property.Name] = property.Value.ValueKind == JsonValueKind.String
? property.Value.GetString() ?? string.Empty
: property.Value.GetRawText();
return values;
}
private static string? GetString(JsonElement element, params string[] keys)
{
if (element.ValueKind != JsonValueKind.Object) return null;
foreach (var key in keys)
if (element.TryGetProperty(key, out var property) &&
property.ValueKind == JsonValueKind.String)
return property.GetString();
return null;
}
private static string? GetValue(IReadOnlyDictionary<string, string> values, string key)
{
return values.TryGetValue(key, out var value) && !string.IsNullOrWhiteSpace(value)
? value
: null;
}
private static int YuanToCents(string? value)
{
return decimal.TryParse(value, out var amount)
? (int)Math.Round(amount * 100, MidpointRounding.AwayFromZero)
: 0;
}
private static string FormatYuan(int cents)
{
return (cents / 100m).ToString("0.00", CultureInfo.InvariantCulture);
}
}