feat: add payment notifications and entitlement fulfillment

This commit is contained in:
xiong
2026-07-26 19:38:53 +08:00
parent 494039973d
commit 36b64dcf70
6 changed files with 570 additions and 13 deletions

View File

@@ -1,5 +1,6 @@
using System.Text.Json;
using Aop.Api;
using Aop.Api.Util;
using Tiku.Application.Commerce;
namespace Tiku.Infrastructure.Commerce;
@@ -25,9 +26,34 @@ internal sealed class AlipayProvider : IPaymentProvider
CancellationToken cancellationToken = default)
{
_ = BuildClient(account);
throw new PaymentProviderException(
"Alipay SDK is configured; notification parsing is enabled in the notification batch.",
"alipay_notification_not_enabled");
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)
@@ -63,4 +89,53 @@ internal sealed class AlipayProvider : IPaymentProvider
"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) =>
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;
}
}