470 lines
25 KiB
C#
470 lines
25 KiB
C#
namespace Baya.Infrastructure.CrossCutting.Seams;
|
||
|
||
/// <summary>
|
||
/// Options bound from the <c>Seams</c> configuration section. The mock seams read non-secret defaults
|
||
/// from here; deployed keys/paths come from the environment-specific appsettings file or environment variables.
|
||
///
|
||
/// <para><b>Provider selection (refinement-phase-8).</b> Each vendor rail carries a <c>Provider</c> selector
|
||
/// (default = the mock, so an unconfigured environment behaves exactly as before). Setting it to a real
|
||
/// provider token (e.g. <c>Seams:Sms:Provider = kavenegar</c>) swaps in the real HTTP adapter behind the same
|
||
/// contract — handlers never change. This makes a <b>partial rollout</b> the normal case: real SMS + real
|
||
/// geocoder while payments stay mocked in a pre-launch environment is just three config keys.</para>
|
||
/// </summary>
|
||
public sealed class SeamOptions
|
||
{
|
||
public const string SectionName = "Seams";
|
||
|
||
public FieldEncryptionOptions FieldEncryption { get; set; } = new();
|
||
public SmsOptions Sms { get; set; } = new();
|
||
public ObjectStorageOptions ObjectStorage { get; set; } = new();
|
||
public BankOwnershipOptions BankOwnership { get; set; } = new();
|
||
public GeocodingOptions Geocoding { get; set; } = new();
|
||
public ShahkarOptions Shahkar { get; set; } = new();
|
||
public IdentityKycOptions IdentityKyc { get; set; } = new();
|
||
public PaymentCaptureOptions PaymentCapture { get; set; } = new();
|
||
public PaymentsOptions Payments { get; set; } = new();
|
||
public MoadianOptions Moadian { get; set; } = new();
|
||
public BnplOptions Bnpl { get; set; } = new();
|
||
public CurrencyOptions Currency { get; set; } = new();
|
||
public BankTransferOptions BankTransfer { get; set; } = new();
|
||
public ReviewModerationOptions ReviewModeration { get; set; } = new();
|
||
public LicenseVerificationOptions LicenseVerification { get; set; } = new();
|
||
public FinnotechOptions Finnotech { get; set; } = new();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Shared credentials for the Finnotech-class KYC bridge that fronts three trust rails — شاهکار
|
||
/// (<c>IShahkarVerifier</c>), e-KYC (<c>IIdentityKycProvider</c>), and استعلام شبا
|
||
/// (<c>IBankAccountOwnershipVerifier</c>). Each seam opts in with its own <c>Provider = finnotech</c> selector,
|
||
/// but they authenticate against the same tenant, so the connection facts live here once. All values are
|
||
/// secrets — the environment-specific appsettings file or environment variables.
|
||
/// </summary>
|
||
public sealed class FinnotechOptions
|
||
{
|
||
/// <summary>API host (defaults to Finnotech's public sandbox/production host at the adapter).</summary>
|
||
public string BaseUrl { get; set; } = string.Empty;
|
||
|
||
/// <summary>The tenant's client id (<c>NID</c>) — the Finnotech app identifier.</summary>
|
||
public string ClientId { get; set; } = string.Empty;
|
||
|
||
/// <summary>A pre-issued bearer access token (client-credential token exchange is out of scope for the MVP
|
||
/// adapter; a deployment supplies a current token, refreshed out-of-band).</summary>
|
||
public string AccessToken { get; set; } = string.Empty;
|
||
}
|
||
|
||
/// <summary>Stable provider tokens for the <c>Provider</c> selectors, so a typo fails closed to the mock.</summary>
|
||
public static class SeamProviders
|
||
{
|
||
public const string Mock = "mock";
|
||
public const string LocalDisk = "local";
|
||
|
||
// SMS gateways
|
||
public const string Kavenegar = "kavenegar";
|
||
public const string SmsIr = "smsir";
|
||
public const string Ghasedak = "ghasedak";
|
||
|
||
/// <summary><b>Broadcast relay, not an SMS gateway</b> — the standalone <c>telegram-otp-bot/</c> service
|
||
/// sends every code to a fixed list of Telegram chat ids. The pre-launch demo rail; replace with a real
|
||
/// gateway before onboarding customers outside the trusted group.</summary>
|
||
public const string Telegram = "telegram";
|
||
|
||
// Object storage
|
||
public const string S3 = "s3";
|
||
|
||
// Trust / identity (a Finnotech-class KYC bridge fronts Shahkar / e-KYC / استعلام شبا)
|
||
public const string Finnotech = "finnotech";
|
||
|
||
// Geocoding
|
||
public const string Neshan = "neshan";
|
||
|
||
// Card PSP acquirers
|
||
public const string ZarinPal = "zarinpal";
|
||
public const string Sadad = "sadad";
|
||
public const string Vandar = "vandar";
|
||
public const string Jibit = "jibit";
|
||
|
||
// BNPL
|
||
public const string SnappPay = "snapppay";
|
||
public const string Digipay = "digipay";
|
||
|
||
// e-invoicing
|
||
public const string Moadian = "moadian";
|
||
}
|
||
|
||
/// <summary>
|
||
/// The outbound SMS rail (<c>ISmsSender</c>). <see cref="Provider"/> = <c>mock</c> logs the OTP (the b2
|
||
/// <c>LoggingSmsSender</c>); set it to <c>kavenegar</c> / <c>smsir</c> / <c>ghasedak</c> to deliver over a real
|
||
/// Iranian gateway. <b>refinement-phase-8:</b> when a real gateway is selected the Development OTP-in-logs/echo
|
||
/// bridge is disabled — the OTP must never be logged once real SMS ships.
|
||
///
|
||
/// <para><c>telegram</c> is the one exception: it is a <b>Development convenience channel, not an SMS gateway</b>
|
||
/// (see <see cref="TelegramOptions"/>), so the OTP-capture bridge stays enabled alongside it.</para>
|
||
/// </summary>
|
||
public sealed class SmsOptions
|
||
{
|
||
/// <summary><c>mock</c> (default) | <c>kavenegar</c> | <c>smsir</c> | <c>ghasedak</c> | <c>telegram</c>
|
||
/// (Development only).</summary>
|
||
public string Provider { get; set; } = SeamProviders.Mock;
|
||
|
||
/// <summary>Gateway API key / token (secret — the environment-specific appsettings file or environment variables).</summary>
|
||
public string ApiKey { get; set; } = string.Empty;
|
||
|
||
/// <summary>The registered sender line (used by <c>SendAsync</c> free-form messages and non-template sends).</summary>
|
||
public string SenderLine { get; set; } = string.Empty;
|
||
|
||
/// <summary>Override the gateway base URL (defaults to the provider's public API host).</summary>
|
||
public string BaseUrl { get; set; } = string.Empty;
|
||
|
||
/// <summary>The approved OTP template/pattern name the gateway sends the code through (verify-lookup APIs).</summary>
|
||
public string OtpTemplate { get; set; } = string.Empty;
|
||
|
||
/// <summary>Connection facts for the <c>telegram</c> relay; ignored by every other provider.</summary>
|
||
public TelegramOptions Telegram { get; set; } = new();
|
||
}
|
||
|
||
/// <summary>
|
||
/// The Telegram OTP relay (the standalone <c>telegram-otp-bot/</c> Node service), selected by
|
||
/// <c>Seams:Sms:Provider = telegram</c>. It replaces "read the OTP out of the server log" — the tester gets the
|
||
/// code on their phone without an Iranian SMS gateway contract.
|
||
///
|
||
/// <para><b>It is not an SMS gateway.</b> There is no per-user routing: the relay <i>broadcasts</i> every code
|
||
/// to a fixed list of Telegram chat ids, so every configured recipient reads every login code. That is workable
|
||
/// for a trusted demo group — which is why the pre-launch <c>balinyaar.ir</c> deployment uses it — and
|
||
/// disqualifying once anyone outside that group can request a code. Switch <c>Seams:Sms:Provider</c> to
|
||
/// <c>kavenegar</c> at that point; nothing else changes.</para>
|
||
/// </summary>
|
||
public sealed class TelegramOptions
|
||
{
|
||
/// <summary>The relay's root URL, e.g. <c>http://127.0.0.1:5010</c>.</summary>
|
||
public string BaseUrl { get; set; } = string.Empty;
|
||
|
||
/// <summary>The shared secret sent as the relay's <c>X-Api-Key</c> header — it must equal the relay's
|
||
/// <c>API_KEY</c>. <b>Secret:</b> committed config carries an empty/placeholder value; the real one comes
|
||
/// from <c>Seams:Sms:Telegram:ApiKey</c> in appsettings or the environment.</summary>
|
||
public string ApiKey { get; set; } = string.Empty;
|
||
|
||
/// <summary>Per-request timeout. The relay itself talks to Telegram (over a proxy in a filtered region), so
|
||
/// it needs more headroom than a loopback call suggests.</summary>
|
||
public int TimeoutSeconds { get; set; } = 10;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Tunes the mock <c>ILicenseVerificationService</c> (b15 partner-center eNamad / MoH establishment-permit
|
||
/// check). By default every check returns <c>NeedsManualReview</c> so <c>VerifyPartnerCenter</c> records the
|
||
/// human admin decision. Set <see cref="AutoApprove"/> to have the mock return <c>Valid</c> (test the
|
||
/// auto-approve path). The real eNamad / MoH registry adapter ignores these knobs.
|
||
/// </summary>
|
||
public sealed class LicenseVerificationOptions
|
||
{
|
||
/// <summary>When true, permit/eNamad checks auto-approve (return <c>Valid</c>) instead of requiring a manual decision.</summary>
|
||
public bool AutoApprove { get; set; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Tunes the mock <c>IReviewModerationService</c> (b14 AI review pre-screen). By default clean text returns a
|
||
/// human-review <c>Flag</c> (keeping the publish gate on); a banned-word hit returns <c>Reject</c>. Set
|
||
/// <see cref="AutoApproveClean"/> to have clean text auto-<c>Approve</c> (auto-publish). The real text
|
||
/// classifier / LLM endpoint ignores these knobs.
|
||
/// </summary>
|
||
public sealed class ReviewModerationOptions
|
||
{
|
||
/// <summary>When true, clean text is auto-approved (auto-published) instead of flagged for human review.</summary>
|
||
public bool AutoApproveClean { get; set; }
|
||
|
||
/// <summary>Case-insensitive substrings that mark a review for rejection.</summary>
|
||
public List<string> BannedWords { get; set; } = ["scam", "fraud", "کلاهبردار"];
|
||
}
|
||
|
||
/// <summary>
|
||
/// Tunes the mock <c>IBankTransferProvider</c> (b13 PAYA/SATNA payouts). By default every instruction settles
|
||
/// paid with a deterministic transfer reference and no money moves. Set <see cref="ForceFailure"/> to fail the
|
||
/// whole batch (→ <c>failed</c>) or <see cref="FailIban"/> to fail just one destination (→ <c>partially_failed</c>,
|
||
/// so the retry path is testable). The real transferor ignores these — the source settlement account, per-nurse
|
||
/// Sheba, and the reconciliation callback come from provider config.
|
||
/// </summary>
|
||
public sealed class BankTransferOptions
|
||
{
|
||
/// <summary><c>mock</c> (default) | <c>jibit</c> | <c>vandar</c> | <c>sadad</c> — the payout transferor.</summary>
|
||
public string Provider { get; set; } = SeamProviders.Mock;
|
||
|
||
/// <summary>Transferor API base URL.</summary>
|
||
public string BaseUrl { get; set; } = string.Empty;
|
||
|
||
/// <summary>Transferor API key / bearer token (secret).</summary>
|
||
public string ApiKey { get; set; } = string.Empty;
|
||
|
||
/// <summary>The platform's registered <b>source settlement account</b> the batch debits (IBAN/account id the
|
||
/// transferor recognises). Every PAYA/SATNA transfer originates here.</summary>
|
||
public string SourceSettlementAccount { get; set; } = string.Empty;
|
||
|
||
/// <summary>When true, every payout instruction is rejected so the whole-batch-failure path is testable.</summary>
|
||
public bool ForceFailure { get; set; }
|
||
|
||
/// <summary>A designated IBAN that is rejected while others succeed — exercises the <c>partially_failed</c>
|
||
/// batch outcome and the single-payout retry.</summary>
|
||
public string FailIban { get; set; } = string.Empty;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Tunes the mock <c>ICurrencyNormalizer</c> (b12). Toman → IRR multiplies by <see cref="TomanToIrrMultiplier"/>
|
||
/// (10); IRR passes through. The real adapter reads the multiplier from provider config. Conversion happens only
|
||
/// at the provider boundary, never internally.
|
||
/// </summary>
|
||
public sealed class CurrencyOptions
|
||
{
|
||
/// <summary>How many Rial one Toman is (10). A currency redenomination is a config change, not a code change.</summary>
|
||
public long TomanToIrrMultiplier { get; set; } = 10;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Tunes the mock <c>IMoadianClient</c> (b11 e-invoicing). By default a submission stays <c>pending</c> with no
|
||
/// reference. Set <see cref="ForceRegistered"/> to make it return <c>registered</c> with a fake 22-digit ref so
|
||
/// the reconciliation/registered path is testable. The real سامانه مودیان adapter ignores these.
|
||
/// </summary>
|
||
public sealed class MoadianOptions
|
||
{
|
||
/// <summary><c>mock</c> (default) | <c>moadian</c> — the real سامانه مودیان submission adapter.</summary>
|
||
public string Provider { get; set; } = SeamProviders.Mock;
|
||
|
||
/// <summary>مودیان API base URL (the tax-authority endpoint).</summary>
|
||
public string BaseUrl { get; set; } = string.Empty;
|
||
|
||
/// <summary>The platform's مودیان memory/economic id (<c>memoryId</c> / شناسه یکتای حافظه مالیاتی).</summary>
|
||
public string MemoryId { get; set; } = string.Empty;
|
||
|
||
/// <summary>A pre-issued bearer token for the مودیان API (the signing-certificate token exchange is a
|
||
/// deploy-time concern; a deployment supplies a current token). Secret.</summary>
|
||
public string AccessToken { get; set; } = string.Empty;
|
||
|
||
/// <summary>When true, a submission returns <c>registered</c> + a deterministic fake 22-digit reference.</summary>
|
||
public bool ForceRegistered { get; set; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Tunes the deterministic mock <c>IBnplProvider</c> (b12, superset of the b11 revert-only stub). The mock
|
||
/// commission % drives the settle (net = order − commission); the real adapter reads the actual deducted amount
|
||
/// from each settlement and ignores these knobs.
|
||
/// </summary>
|
||
public sealed class BnplOptions
|
||
{
|
||
/// <summary><c>mock</c> (default) | <c>real</c> — when <c>real</c>, <c>IBnplProviderResolver</c> maps each
|
||
/// <c>provider_code</c> to its concrete adapter (SnappPay / Digipay) instead of the one mock.</summary>
|
||
public string Provider { get; set; } = SeamProviders.Mock;
|
||
|
||
/// <summary>Per-provider connection facts, keyed by <c>provider_code</c> (<c>snapppay</c>/<c>digipay</c>).
|
||
/// Credentials proper (client id/secret) come from the encrypted <c>payment_gateways.config_json</c> in a
|
||
/// full deployment; the base URL + non-secret facts can be defaulted here.</summary>
|
||
public Dictionary<string, BnplProviderConnection> Providers { get; set; } = new(StringComparer.OrdinalIgnoreCase);
|
||
|
||
/// <summary>The currency the BNPL providers speak on the wire (<c>TOMAN</c> or <c>IRR</c>); conversion to IRR
|
||
/// happens only at the adapter boundary via <c>ICurrencyNormalizer</c>. SnappPay/Digipay speak Rial.</summary>
|
||
public string WireCurrency { get; set; } = "IRR";
|
||
|
||
/// <summary>When true, token/revert/update/cancel all fail so the provider-declined paths are testable.</summary>
|
||
public bool ForceFailure { get; set; }
|
||
|
||
/// <summary>When true, the mock reports the provider returned its commission (a non-null, zero reversal
|
||
/// placeholder) so the <c>provider_commission_reversed_amount</c> reconciliation is exercised.</summary>
|
||
public bool ReverseProviderCommission { get; set; }
|
||
|
||
/// <summary>The mock provider merchant commission rate (fraction) the settle deducts. Read from the response
|
||
/// by the handler, never hardcoded there; the real adapter reads the true per-contract deducted amount.</summary>
|
||
public decimal CommissionRate { get; set; } = 0.10m;
|
||
|
||
/// <summary>When true the mock settle stamps <c>settled_at = now</c>; false models the non-instant
|
||
/// (deferred/T+1–3/weekly) settlement with a null <c>settled_at</c>.</summary>
|
||
public bool SettlementInstant { get; set; } = true;
|
||
|
||
/// <summary>The provider credit ceiling (IRR); an order above it returns <c>ceiling_exceeded</c>.</summary>
|
||
public long CreditCeilingIrr { get; set; } = 2_000_000_000;
|
||
|
||
/// <summary>A designated test mobile that returns <c>not_eligible</c> so the fall-back-to-card path is testable.</summary>
|
||
public string NotEligibleMobile { get; set; } = "09120000099";
|
||
}
|
||
|
||
/// <summary>Non-secret connection facts for one BNPL provider (base URL, sandbox flag, merchant handle). The
|
||
/// secret client id/secret live in the encrypted <c>payment_gateways.config_json</c>; a real adapter reads both.</summary>
|
||
public sealed class BnplProviderConnection
|
||
{
|
||
public string BaseUrl { get; set; } = string.Empty;
|
||
public bool Sandbox { get; set; }
|
||
|
||
/// <summary>Optional non-secret merchant/terminal identifier the provider expects on requests.</summary>
|
||
public string MerchantId { get; set; } = string.Empty;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Tunes the b10 money-path mocks (PSP acquirer, تسهیم split, webhook verifier). The real adapters ignore
|
||
/// these — production merchant ids / signing keys come from <c>payment_gateways.config_json</c> and secrets,
|
||
/// never from here.
|
||
/// </summary>
|
||
public sealed class PaymentsOptions
|
||
{
|
||
/// <summary><c>mock</c> (default) | <c>zarinpal</c> | <c>sadad</c> | <c>vandar</c> | <c>jibit</c> — the card
|
||
/// acquirer <c>IPaymentProvider</c> + <c>ISettlementSplitProvider</c> + <c>IWebhookVerifier</c> swap together.</summary>
|
||
public string Provider { get; set; } = SeamProviders.Mock;
|
||
|
||
/// <summary>Acquirer IPG base URL (the payment-request / verify / refund host).</summary>
|
||
public string BaseUrl { get; set; } = string.Empty;
|
||
|
||
/// <summary>The acquirer merchant id / terminal (non-secret handle). Production reads it from the encrypted
|
||
/// <c>payment_gateways.config_json</c>; this default enables a single-merchant deployment without the DB row.</summary>
|
||
public string MerchantId { get; set; } = string.Empty;
|
||
|
||
/// <summary>Where the acquirer sends the customer back after the hosted payment page (the return deep-link the
|
||
/// adapter passes as the callback URL when opening the IPG session).</summary>
|
||
public string CallbackUrl { get; set; } = string.Empty;
|
||
|
||
/// <summary>Per-provider webhook signing secret (HMAC key), keyed by <c>provider_code</c>. The real
|
||
/// <c>IWebhookVerifier</c> verifies the raw callback body against this; a provider with no signature falls back
|
||
/// to the mandatory server-side <c>verify</c> re-check.</summary>
|
||
public Dictionary<string, string> WebhookSigningSecrets { get; set; } = new(StringComparer.OrdinalIgnoreCase);
|
||
|
||
/// <summary>The header the provider carries its signature in (default <c>X-Signature</c>).</summary>
|
||
public string SignatureHeader { get; set; } = "X-Signature";
|
||
|
||
/// <summary>The platform's own registered IBAN (SHEBA) the split credits the commission leg to.</summary>
|
||
public string PlatformSheba { get; set; } = "IR000000000000000000000001";
|
||
|
||
/// <summary>A callback whose raw body contains this marker is treated as an <b>invalid signature</b> by the
|
||
/// mock verifier, so the "unverified callback mutates nothing" path is testable.</summary>
|
||
public string InvalidSignatureMarker { get; set; } = "INVALID_SIGNATURE";
|
||
}
|
||
|
||
/// <summary>
|
||
/// Tunes the mock <c>IPaymentCaptureSimulator</c> (backend-phase-9). By default every capture succeeds with
|
||
/// a fake gateway reference and <see cref="PspFeeAmount"/>. Set <see cref="ForceFailure"/> to exercise the
|
||
/// "capture failed → no booking" path. The real b10 card capture ignores these.
|
||
/// </summary>
|
||
public sealed class PaymentCaptureOptions
|
||
{
|
||
/// <summary>When true, every capture returns a failed result so conversion refuses to create a booking.</summary>
|
||
public bool ForceFailure { get; set; }
|
||
|
||
/// <summary>The PSP/gateway fee (IRR) the mock reports on a successful capture.</summary>
|
||
public long? PspFeeAmount { get; set; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Tunes the mock <c>IShahkarVerifier</c> (phone↔national-id binding). A submitted phone equal to
|
||
/// <see cref="SharedSimPhone"/> returns the explicit shared-SIM failure; a national id equal to
|
||
/// <see cref="MismatchNationalId"/> returns a plain mismatch; every other pair matches. The real vendor
|
||
/// implementation ignores these.
|
||
/// </summary>
|
||
public sealed class ShahkarOptions
|
||
{
|
||
/// <summary><c>mock</c> (default) | <c>finnotech</c> — the real شاهکار bridge (shares <c>Seams:Finnotech</c> creds).</summary>
|
||
public string Provider { get; set; } = SeamProviders.Mock;
|
||
|
||
/// <summary>The designated test phone that returns the shared-SIM failure state.</summary>
|
||
public string SharedSimPhone { get; set; } = "09120000000";
|
||
|
||
/// <summary>The designated test national id that returns a plain phone↔national-id mismatch.</summary>
|
||
public string MismatchNationalId { get; set; } = "1111111111";
|
||
}
|
||
|
||
/// <summary>
|
||
/// Tunes the mock <c>IIdentityKycProvider</c>. A national id equal to <see cref="FailNationalId"/> fails
|
||
/// KYC; every other (well-formed) national id passes with <see cref="MatchedName"/>. The real e-KYC vendor
|
||
/// implementation ignores these.
|
||
/// </summary>
|
||
public sealed class IdentityKycOptions
|
||
{
|
||
/// <summary><c>mock</c> (default) | <c>finnotech</c> — the real e-KYC bridge (shares <c>Seams:Finnotech</c> creds).</summary>
|
||
public string Provider { get; set; } = SeamProviders.Mock;
|
||
|
||
/// <summary>The designated test national id that fails identity KYC.</summary>
|
||
public string FailNationalId { get; set; } = "0000000000";
|
||
|
||
/// <summary>The name the mock reports as matched on a passing KYC (informational; the authoritative
|
||
/// identity name for credential cross-check comes from the users row).</summary>
|
||
public string MatchedName { get; set; } = "Verified Nurse";
|
||
}
|
||
|
||
/// <summary>
|
||
/// Tunes the mock <c>IGeocoder</c>. By default it resolves every address to a deterministic point around
|
||
/// the city centroid. Set <see cref="ReturnNullCoordinates"/> to force the unresolved path globally, or
|
||
/// embed <see cref="LowConfidenceMarker"/> in a single address to exercise the "saved without a map pin"
|
||
/// UI state per-request. The real vendor implementation ignores these.
|
||
/// </summary>
|
||
public sealed class GeocodingOptions
|
||
{
|
||
/// <summary><c>mock</c> (default) | <c>neshan</c> — the real Neshan geocoding adapter.</summary>
|
||
public string Provider { get; set; } = SeamProviders.Mock;
|
||
|
||
/// <summary>Neshan API key (secret). The real geocoder sends it as the <c>Api-Key</c> header.</summary>
|
||
public string ApiKey { get; set; } = string.Empty;
|
||
|
||
/// <summary>Neshan API base URL (defaults to the public host at the adapter).</summary>
|
||
public string BaseUrl { get; set; } = string.Empty;
|
||
|
||
/// <summary>When true, every geocode returns null coordinates with low confidence.</summary>
|
||
public bool ReturnNullCoordinates { get; set; }
|
||
|
||
/// <summary>An address whose text contains this marker resolves to null coordinates (testability).</summary>
|
||
public string LowConfidenceMarker { get; set; } = "NO_GEO";
|
||
|
||
/// <summary>Confidence returned for a successfully resolved address.</summary>
|
||
public double ResolvedConfidence { get; set; } = 0.9;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Tunes the mock <c>IBankAccountOwnershipVerifier</c> (استعلام شبا). A submitted IBAN equal to
|
||
/// <see cref="MismatchIban"/> returns an ownership mismatch so the payout-gating path is testable; every
|
||
/// other IBAN returns a match. The real vendor implementation ignores these.
|
||
/// </summary>
|
||
public sealed class BankOwnershipOptions
|
||
{
|
||
/// <summary><c>mock</c> (default) | <c>finnotech</c> — the real استعلام شبا bridge (shares <c>Seams:Finnotech</c> creds).</summary>
|
||
public string Provider { get; set; } = SeamProviders.Mock;
|
||
|
||
/// <summary>The designated test IBAN that returns <c>matched_national_id = false</c>.</summary>
|
||
public string MismatchIban { get; set; } = "IR000000000000000000000000";
|
||
|
||
/// <summary>The account-holder name the mock echoes back for a matching inquiry.</summary>
|
||
public string MatchedHolderName { get; set; } = "Verified Account Holder";
|
||
|
||
/// <summary>The account-holder name the mock returns for the mismatch IBAN.</summary>
|
||
public string MismatchHolderName { get; set; } = "Unmatched Account Holder";
|
||
}
|
||
|
||
public sealed class FieldEncryptionOptions
|
||
{
|
||
/// <summary>Base64 32-byte AES key. Local-dev default only; override per environment.</summary>
|
||
public string Key { get; set; } = string.Empty;
|
||
|
||
/// <summary>Base64 HMAC key used for deterministic lookup hashes.</summary>
|
||
public string HashKey { get; set; } = string.Empty;
|
||
}
|
||
|
||
public sealed class ObjectStorageOptions
|
||
{
|
||
/// <summary><c>local</c> (default) | <c>s3</c> — S3/MinIO/ArvanCloud object storage with presigned PUT/GET.</summary>
|
||
public string Provider { get; set; } = SeamProviders.LocalDisk;
|
||
|
||
/// <summary>Filesystem root the local-disk mock writes blobs under.</summary>
|
||
public string RootPath { get; set; } = string.Empty;
|
||
|
||
/// <summary>S3-compatible endpoint host, e.g. <c>https://s3.ir-thr-at1.arvanstorage.ir</c> or a MinIO URL.</summary>
|
||
public string ServiceUrl { get; set; } = string.Empty;
|
||
|
||
/// <summary>The bucket blobs are stored in.</summary>
|
||
public string Bucket { get; set; } = string.Empty;
|
||
|
||
/// <summary>The S3 region (SigV4 credential scope; MinIO/ArvanCloud commonly use <c>us-east-1</c> or their own).</summary>
|
||
public string Region { get; set; } = "us-east-1";
|
||
|
||
/// <summary>S3 access key id (secret).</summary>
|
||
public string AccessKey { get; set; } = string.Empty;
|
||
|
||
/// <summary>S3 secret access key (secret).</summary>
|
||
public string SecretKey { get; set; } = string.Empty;
|
||
|
||
/// <summary>Use path-style addressing (<c>{endpoint}/{bucket}/{key}</c>) — required by MinIO/ArvanCloud; AWS
|
||
/// proper uses virtual-host style. Default true (path-style) since Iranian S3 endpoints expect it.</summary>
|
||
public bool UsePathStyle { get; set; } = true;
|
||
|
||
/// <summary>How long a presigned GET/PUT URL stays valid (seconds).</summary>
|
||
public int PresignExpirySeconds { get; set; } = 900;
|
||
}
|