87 lines
4.3 KiB
C#
87 lines
4.3 KiB
C#
#nullable enable
|
|
using System.Net.Http.Headers;
|
|
using System.Net.Http.Json;
|
|
using System.Text.Json;
|
|
using Baya.Application.Contracts.Invoices;
|
|
using Baya.Domain.Entities.Invoices;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
namespace Baya.Infrastructure.CrossCutting.Seams.Real;
|
|
|
|
/// <summary>
|
|
/// Real <see cref="IMoadianClient"/> over سامانه مودیان (refinement-phase-8, 6.5). Selected by
|
|
/// <c>Seams:Moadian:Provider = moadian</c>. Submits the commission invoice (صورتحساب) to the tax-authority API and
|
|
/// maps the outcome: a returned 22-digit reference ⇒ <see cref="MoadianStatus.Registered"/>; an accepted-but-not-
|
|
/// yet-registered submission ⇒ <see cref="MoadianStatus.Submitted"/> (the reconciliation poll walks it to
|
|
/// <c>registered</c>); a rejection ⇒ <see cref="MoadianStatus.Failed"/>.
|
|
///
|
|
/// <para><b>Idempotent submit.</b> The invoice number is sent as the unique document id, so مودیان dedups a
|
|
/// re-submission — which is what lets the reconciliation job safely re-call this for a still-<c>submitted</c>
|
|
/// invoice (the seam has one verb; a re-submit doubles as the status poll). <b>Enrollment + the signing
|
|
/// certificate</b> (memory/economic id, تعهدنامه) are a deploy-time concern — a deployment supplies the memory id
|
|
/// + a current token via <c>Seams:Moadian</c>; signing the payload with the platform's private key is added inside
|
|
/// this adapter at integration without changing the seam.</para>
|
|
/// </summary>
|
|
public sealed class MoadianClient(
|
|
HttpClient httpClient,
|
|
IOptions<SeamOptions> options,
|
|
ILogger<MoadianClient> logger) : IMoadianClient
|
|
{
|
|
private const string DefaultBaseUrl = "https://tp.tax.gov.ir";
|
|
private readonly MoadianOptions _options = options.Value.Moadian;
|
|
|
|
private string BaseUrl => string.IsNullOrWhiteSpace(_options.BaseUrl) ? DefaultBaseUrl : _options.BaseUrl.TrimEnd('/');
|
|
|
|
public async ValueTask<MoadianSubmissionResult> SubmitAsync(InvoiceSubmission submission, CancellationToken cancellationToken = default)
|
|
{
|
|
var payload = new
|
|
{
|
|
memoryId = _options.MemoryId,
|
|
invoiceNumber = submission.InvoiceNumber,
|
|
bookingId = submission.BookingId,
|
|
totalAmount = submission.GrossIrr,
|
|
commissionAmount = submission.PlatformCommissionIrr,
|
|
vatAmount = submission.VatIrr,
|
|
};
|
|
|
|
using var request = new HttpRequestMessage(HttpMethod.Post, $"{BaseUrl}/req/api/self-tsp/sync/normal-enveloped")
|
|
{
|
|
Content = JsonContent.Create(payload),
|
|
};
|
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _options.AccessToken);
|
|
|
|
try
|
|
{
|
|
using var response = await httpClient.SendAsync(request, cancellationToken);
|
|
var raw = await response.Content.ReadAsStringAsync(cancellationToken);
|
|
|
|
if (!response.IsSuccessStatusCode)
|
|
{
|
|
logger.LogWarning("مودیان submission returned http {Http} for invoice {InvoiceNumber}",
|
|
(int)response.StatusCode, submission.InvoiceNumber);
|
|
return new MoadianSubmissionResult(MoadianStatus.Failed, null);
|
|
}
|
|
|
|
using var doc = JsonDocument.Parse(raw);
|
|
var root = doc.RootElement;
|
|
|
|
// A returned reference number registers the invoice; otherwise it was accepted and is awaiting the ref.
|
|
var reference = root.TryGetProperty("referenceNumber", out var refEl) ? refEl.GetString()
|
|
: root.TryGetProperty("uid", out var uid) ? uid.GetString()
|
|
: null;
|
|
|
|
return string.IsNullOrEmpty(reference)
|
|
? new MoadianSubmissionResult(MoadianStatus.Submitted, null)
|
|
: new MoadianSubmissionResult(MoadianStatus.Registered, reference);
|
|
}
|
|
catch (Exception ex) when (ex is HttpRequestException or JsonException or TaskCanceledException)
|
|
{
|
|
// A مودیان outage leaves the invoice submittable again on the next reconciliation tick — never failed
|
|
// permanently on a transient error.
|
|
logger.LogWarning(ex, "مودیان submission failed transiently for invoice {InvoiceNumber}", submission.InvoiceNumber);
|
|
return new MoadianSubmissionResult(MoadianStatus.Submitted, null);
|
|
}
|
|
}
|
|
}
|