feat(payment): add per-user in-memory lock for gateway operations
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 9m58s
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 9m58s
Introduce IUserPaymentLock to serialize payment initiate and verify flows per user, preventing concurrent duplicate gateway requests across services. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
namespace CMSMicroservice.Application.Common.Exceptions;
|
||||
|
||||
/// <summary>
|
||||
/// Thrown when a concurrent payment operation is already in progress for the same scope.
|
||||
/// </summary>
|
||||
public class PaymentInProgressException : Exception
|
||||
{
|
||||
public PaymentInProgressException()
|
||||
: base("یک عملیات پرداخت دیگر در حال پردازش است. لطفاً چند لحظه صبر کنید.")
|
||||
{
|
||||
}
|
||||
|
||||
public PaymentInProgressException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public PaymentInProgressException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
namespace CMSMicroservice.Application.Common.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Per-scope in-process mutex for payment flows (initiate / verify).
|
||||
/// Prevents the same user from running duplicate gateway operations concurrently.
|
||||
/// </summary>
|
||||
public interface IUserPaymentLock
|
||||
{
|
||||
Task<T> ExecuteAsync<T>(
|
||||
string scope,
|
||||
PaymentLockStrategy strategy,
|
||||
Func<CancellationToken, Task<T>> action,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task ExecuteAsync(
|
||||
string scope,
|
||||
PaymentLockStrategy strategy,
|
||||
Func<CancellationToken, Task> action,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How to behave when the payment lock is already held.
|
||||
/// </summary>
|
||||
public enum PaymentLockStrategy
|
||||
{
|
||||
/// <summary>Wait up to the configured timeout (callback / verify paths).</summary>
|
||||
WaitForRelease,
|
||||
|
||||
/// <summary>Reject immediately (initiate / double-click paths).</summary>
|
||||
FailFast
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace CMSMicroservice.Application.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Canonical scope keys for <see cref="Interfaces.IUserPaymentLock"/>.
|
||||
/// </summary>
|
||||
public static class PaymentLockScopes
|
||||
{
|
||||
/// <summary>One active payment initiation per user (package, wallet charge, order, IPG deposit).</summary>
|
||||
public static string Initiate(long userId) => $"payment:initiate:user:{userId}";
|
||||
|
||||
/// <summary>One active verify per user + authority/order (duplicate callback protection).</summary>
|
||||
public static string Verify(long userId, string resourceKey)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(resourceKey))
|
||||
throw new ArgumentException("Payment verify resource key is required.", nameof(resourceKey));
|
||||
|
||||
return $"payment:verify:user:{userId}:{resourceKey.Trim()}";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user