refinement phase 8
This commit is contained in:
@@ -3,12 +3,19 @@ namespace Baya.Infrastructure.CrossCutting.Seams;
|
||||
/// <summary>
|
||||
/// Options bound from the <c>Seams</c> configuration section. The mock seams read non-secret defaults
|
||||
/// from here; production keys/paths come from environment variables or user-secrets, never committed.
|
||||
///
|
||||
/// <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();
|
||||
@@ -22,6 +29,85 @@ public sealed class SeamOptions
|
||||
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 — user-secrets / environment, never committed.
|
||||
/// </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";
|
||||
|
||||
// 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 provider is selected the Development OTP-in-logs/echo
|
||||
/// bridge is disabled — the OTP must never be logged once real SMS ships.
|
||||
/// </summary>
|
||||
public sealed class SmsOptions
|
||||
{
|
||||
/// <summary><c>mock</c> (default) | <c>kavenegar</c> | <c>smsir</c> | <c>ghasedak</c>.</summary>
|
||||
public string Provider { get; set; } = SeamProviders.Mock;
|
||||
|
||||
/// <summary>Gateway API key / token (secret — user-secrets or environment, never committed).</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>
|
||||
@@ -60,6 +146,19 @@ public sealed class ReviewModerationOptions
|
||||
/// </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; }
|
||||
|
||||
@@ -86,6 +185,19 @@ public sealed class CurrencyOptions
|
||||
/// </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; }
|
||||
}
|
||||
@@ -97,6 +209,19 @@ public sealed class MoadianOptions
|
||||
/// </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; }
|
||||
|
||||
@@ -119,6 +244,17 @@ public sealed class BnplOptions
|
||||
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,
|
||||
@@ -126,7 +262,30 @@ public sealed class BnplOptions
|
||||
/// </summary>
|
||||
public sealed class PaymentsOptions
|
||||
{
|
||||
/// <summary>The platform's own registered IBAN (SHEBA) the mock split credits the commission leg to.</summary>
|
||||
/// <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
|
||||
@@ -156,6 +315,9 @@ public sealed class PaymentCaptureOptions
|
||||
/// </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";
|
||||
|
||||
@@ -170,6 +332,9 @@ public sealed class ShahkarOptions
|
||||
/// </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";
|
||||
|
||||
@@ -186,6 +351,15 @@ public sealed class IdentityKycOptions
|
||||
/// </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; }
|
||||
|
||||
@@ -203,6 +377,9 @@ public sealed class GeocodingOptions
|
||||
/// </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";
|
||||
|
||||
@@ -224,6 +401,31 @@ public sealed class FieldEncryptionOptions
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user