forked from gongxuegit/tiku-backend.net
feat: add payment notifications and entitlement fulfillment
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user