forked from gongxuegit/tiku-backend.net
feat: add payment notifications and entitlement fulfillment
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Tiku.Api.Contracts;
|
||||
using Tiku.Application.Commerce;
|
||||
using Tiku.Application.Security;
|
||||
@@ -78,6 +81,28 @@ public sealed class CommerceController(
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("payments/notify/wechat-pay")]
|
||||
[EndpointSummary("微信支付回调")]
|
||||
[ProducesResponseType<PaymentNotificationProcessResult>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PaymentNotificationProcessResult>> WechatPayNotify(
|
||||
[FromQuery] Guid tenantId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await ProcessNotificationAsync(tenantId, PaymentProviders.WechatPay, cancellationToken));
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("payments/notify/alipay")]
|
||||
[EndpointSummary("支付宝支付回调")]
|
||||
[ProducesResponseType<PaymentNotificationProcessResult>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PaymentNotificationProcessResult>> AlipayNotify(
|
||||
[FromQuery] Guid tenantId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await ProcessNotificationAsync(tenantId, PaymentProviders.Alipay, cancellationToken));
|
||||
}
|
||||
|
||||
private CommerceActor ResolveActor()
|
||||
{
|
||||
if (currentTenant.TenantId is null || currentUser.UserId is null)
|
||||
@@ -87,4 +112,64 @@ public sealed class CommerceController(
|
||||
|
||||
return new CommerceActor(currentTenant.TenantId.Value, currentUser.UserId.Value);
|
||||
}
|
||||
|
||||
private async Task<PaymentNotificationProcessResult> ProcessNotificationAsync(
|
||||
Guid tenantId,
|
||||
string provider,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (tenantId == Guid.Empty)
|
||||
{
|
||||
throw new CommerceException("Tenant id is required for payment notification.", "tenant_required");
|
||||
}
|
||||
|
||||
var rawBody = await ReadRawBodyAsync(Request, cancellationToken);
|
||||
using var body = ParseNotificationBody(Request, rawBody, cancellationToken);
|
||||
var headers = Request.Headers.ToDictionary(
|
||||
pair => pair.Key,
|
||||
pair => pair.Value.ToString(),
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
return await commerceService.ProcessPaymentNotificationAsync(
|
||||
tenantId,
|
||||
provider,
|
||||
headers,
|
||||
rawBody,
|
||||
body.RootElement.Clone(),
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
private static async Task<string> ReadRawBodyAsync(HttpRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
using var reader = new StreamReader(
|
||||
request.Body,
|
||||
Encoding.UTF8,
|
||||
detectEncodingFromByteOrderMarks: false,
|
||||
leaveOpen: false);
|
||||
return await reader.ReadToEndAsync(cancellationToken);
|
||||
}
|
||||
|
||||
private static JsonDocument ParseNotificationBody(
|
||||
HttpRequest request,
|
||||
string rawBody,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (request.HasFormContentType)
|
||||
{
|
||||
_ = cancellationToken;
|
||||
var form = QueryHelpers.ParseQuery(rawBody);
|
||||
var values = form.ToDictionary(
|
||||
pair => pair.Key,
|
||||
pair => pair.Value.ToString(),
|
||||
StringComparer.Ordinal);
|
||||
return JsonDocument.Parse(JsonSerializer.Serialize(values));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(rawBody))
|
||||
{
|
||||
return JsonDocument.Parse("{}");
|
||||
}
|
||||
|
||||
return JsonDocument.Parse(rawBody);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user