Files
meezi/src/Meezi.API/Services/BillingPaymentOrchestrator.cs
T
soroush.asadi ef15fd6247 feat(api): .NET 10 multi-tenant REST API
Full backend implementation:
- Multi-tenant cafe/restaurant management (menus, orders, tables, staff)
- POS order flow with ZarinPal and Snappfood payment integration
- OTP authentication via Kavenegar SMS
- QR digital menu with public discover/finder endpoints
- Customer loyalty, coupons, CRM
- PostgreSQL via EF Core, Redis for caching/sessions
- Background jobs, webhook handlers
- Full migration history

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-05-27 21:33:48 +03:30

157 lines
6.1 KiB
C#

using Meezi.API.Models.Billing;
using Meezi.Core.Enums;
using Meezi.Core.Interfaces;
namespace Meezi.API.Services;
public record PaymentInitResult(bool Success, string? Authority, string? PaymentUrl, string? ErrorMessage);
public record PaymentVerifyResult(bool Success, string? RefId, string? ErrorMessage);
public interface IBillingPaymentOrchestrator
{
Task<IReadOnlyList<PaymentMethodDto>> GetEnabledMethodsAsync(CancellationToken cancellationToken = default);
Task<PaymentInitResult> InitiateAsync(
PaymentProvider provider,
long amountRials,
string paymentId,
string description,
string callbackUrl,
CancellationToken cancellationToken = default);
Task<PaymentVerifyResult> VerifyAsync(
PaymentProvider provider,
string externalId,
long amountRials,
CancellationToken cancellationToken = default);
}
public class BillingPaymentOrchestrator : IBillingPaymentOrchestrator
{
private readonly IZarinPalGateway _zarinPal;
private readonly ITaraPaymentGateway _tara;
private readonly ISnappPayGateway _snappPay;
private readonly IPlatformRuntimeConfig _platform;
private static readonly (string Id, string NameFa)[] AllMethods =
[
(PaymentProviderIds.ZarinPal, "زرین‌پال"),
(PaymentProviderIds.Tara, "تارا (اعتباری)"),
(PaymentProviderIds.SnappPay, "اسنپ‌پی (اقساطی)")
];
public BillingPaymentOrchestrator(
IZarinPalGateway zarinPal,
ITaraPaymentGateway tara,
ISnappPayGateway snappPay,
IPlatformRuntimeConfig platform)
{
_zarinPal = zarinPal;
_tara = tara;
_snappPay = snappPay;
_platform = platform;
}
public async Task<IReadOnlyList<PaymentMethodDto>> GetEnabledMethodsAsync(
CancellationToken cancellationToken = default)
{
var defaultId = await _platform.GetAsync("payment.activeGateway", cancellationToken) ?? PaymentProviderIds.ZarinPal;
var list = new List<PaymentMethodDto>();
if (await _zarinPal.IsEnabledAsync(cancellationToken))
list.Add(new PaymentMethodDto(PaymentProviderIds.ZarinPal, "زرین‌پال", defaultId == PaymentProviderIds.ZarinPal));
if (await _tara.IsEnabledAsync(cancellationToken))
list.Add(new PaymentMethodDto(PaymentProviderIds.Tara, "تارا (اعتباری)", defaultId == PaymentProviderIds.Tara));
if (await _snappPay.IsEnabledAsync(cancellationToken))
list.Add(new PaymentMethodDto(PaymentProviderIds.SnappPay, "اسنپ‌پی (اقساطی)", defaultId == PaymentProviderIds.SnappPay));
if (list.Count == 0)
list.Add(new PaymentMethodDto(PaymentProviderIds.ZarinPal, "زرین‌پال", true));
if (list.All(m => !m.IsDefault))
list[0] = list[0] with { IsDefault = true };
return list;
}
public async Task<PaymentInitResult> InitiateAsync(
PaymentProvider provider,
long amountRials,
string paymentId,
string description,
string callbackUrl,
CancellationToken cancellationToken = default)
{
return provider switch
{
PaymentProvider.Tara => await InitiateTaraAsync(amountRials, paymentId, callbackUrl, cancellationToken),
PaymentProvider.SnappPay => await InitiateSnappPayAsync(amountRials, paymentId, callbackUrl, cancellationToken),
_ => await InitiateZarinPalAsync(amountRials, description, callbackUrl, cancellationToken)
};
}
public async Task<PaymentVerifyResult> VerifyAsync(
PaymentProvider provider,
string externalId,
long amountRials,
CancellationToken cancellationToken = default)
{
return provider switch
{
PaymentProvider.Tara =>
Map(await _tara.VerifyPaymentAsync(externalId, cancellationToken)),
PaymentProvider.SnappPay =>
Map(await _snappPay.VerifyAndSettleAsync(externalId, cancellationToken)),
_ =>
Map(await _zarinPal.VerifyPaymentAsync(externalId, amountRials, cancellationToken))
};
}
private async Task<PaymentInitResult> InitiateZarinPalAsync(
long amountRials,
string description,
string callbackUrl,
CancellationToken cancellationToken)
{
if (!await _zarinPal.IsEnabledAsync(cancellationToken))
return new PaymentInitResult(false, null, null, "ZarinPal is disabled.");
var r = await _zarinPal.RequestPaymentAsync(amountRials, description, callbackUrl, cancellationToken);
return new PaymentInitResult(r.Success, r.Authority, r.PaymentUrl, r.ErrorMessage);
}
private async Task<PaymentInitResult> InitiateTaraAsync(
long amountRials,
string paymentId,
string callbackUrl,
CancellationToken cancellationToken)
{
if (!await _tara.IsEnabledAsync(cancellationToken))
return new PaymentInitResult(false, null, null, "Tara is disabled.");
var r = await _tara.RequestPaymentAsync(amountRials, paymentId, callbackUrl, cancellationToken);
return new PaymentInitResult(r.Success, r.TraceNumber, r.PaymentUrl, r.ErrorMessage);
}
private async Task<PaymentInitResult> InitiateSnappPayAsync(
long amountRials,
string paymentId,
string callbackUrl,
CancellationToken cancellationToken)
{
if (!await _snappPay.IsEnabledAsync(cancellationToken))
return new PaymentInitResult(false, null, null, "Snapp Pay is disabled.");
var r = await _snappPay.RequestPaymentAsync(amountRials, paymentId, callbackUrl, cancellationToken);
return new PaymentInitResult(r.Success, r.PaymentToken, r.PaymentUrl, r.ErrorMessage);
}
private static PaymentVerifyResult Map(ZarinPalVerifyResult r) =>
new(r.Success, r.RefId, r.ErrorMessage);
private static PaymentVerifyResult Map(TaraVerifyResult r) =>
new(r.Success, r.RefId, r.ErrorMessage);
private static PaymentVerifyResult Map(SnappPayVerifyResult r) =>
new(r.Success, r.RefId, r.ErrorMessage);
}