diff --git a/.api-sync/known-divergences.json b/.api-sync/known-divergences.json index 865ecf9..16326f3 100644 --- a/.api-sync/known-divergences.json +++ b/.api-sync/known-divergences.json @@ -28,6 +28,114 @@ "field": "expires_at", "reason": "Same pre-existing mismatch as QuoteOut.expires_at, on the payin-quote equivalent (CreatePayinQuoteResponse). Here the field IS cast, `expiresAt: (int) $data['expires_at']`, so a fractional value would silently truncate rather than throw -- a real risk in disguise if the API ever returns one.", "owner": "eric@blindpay.com" + }, + { + "schema": "BankAccountOut", + "field": "spei_protocol", + "reason": "Spec constrains spei_protocol to an enum (clabe/debitcard/phonenum). BankAccountListItem models it as a plain `?string`, not the SpeiProtocol backed enum that CreateSpeiInput/CreateSpeiResponse already use on the same rail -- so every spec value still parses without error, just without case-completeness checking on this shape. Recorded for visibility only.", + "owner": "eric@blindpay.com" + }, + { + "schema": "PayinOut", + "field": "status", + "reason": "Covers PayinOut/CreatePayinOut's inline tracking_transaction.status (failed/completed) and tracking_transaction.status is a DIFFERENT, smaller enum than PayinOut's own top-level status (TransactionStatus, already mapped). TrackingTransaction models this sub-field as a plain `string`, not a backed enum.", + "owner": "eric@blindpay.com" + }, + { + "schema": "PayinOut", + "field": "currency", + "reason": "Spec constrains currency to the Currency enum. Payin models it as a plain `string`; every spec value still parses without error.", + "owner": "eric@blindpay.com" + }, + { + "schema": "PayinOut", + "field": "payment_method", + "reason": "Spec constrains payment_method to the PaymentMethod enum (already mapped for other shapes). Payin models it as a plain `string`, not PaymentMethod; every spec value still parses without error.", + "owner": "eric@blindpay.com" + }, + { + "schema": "PayinOut", + "field": "pse_document_type", + "reason": "Spec constrains pse_document_type to a 2-value enum (CC/NIT). Payin models it as a plain `?string`; every spec value still parses without error.", + "owner": "eric@blindpay.com" + }, + { + "schema": "PayinOut", + "field": "type", + "reason": "Spec constrains type (deposit method) to an enum. Payin models it as a plain `string`; every spec value still parses without error.", + "owner": "eric@blindpay.com" + }, + { + "schema": "OfframpWallet", + "field": "network", + "reason": "Spec constrains network to the Network enum (already mapped for other shapes, e.g. WalletOut). OfframpWallet models it as a plain `string`, not Network; every spec value still parses without error.", + "owner": "eric@blindpay.com" + }, + { + "schema": "PayoutOut", + "field": "status", + "reason": "Covers PayoutOut/PayoutOnEvmOut's inline tracking_transaction.status (failed/found) and tracking_complete.status (tokens_refunded/paid) -- two DIFFERENT, smaller enums than PayoutOut's own top-level status (TransactionStatus, already mapped). TrackingTransaction/TrackingComplete model these sub-fields as plain `string`, not backed enums.", + "owner": "eric@blindpay.com" + }, + { + "schema": "PayoutOut", + "field": "estimated_time_of_arrival", + "reason": "Covers PayoutOut/PayoutOnEvmOut's inline tracking_payment.estimated_time_of_arrival and tracking_liquidity.estimated_time_of_arrival. Both are spec enums (5_min/5_30_min/30_min/2_hours/1_business_day/2_business_days/5_business_days); PayoutTrackingPayment and TrackingLiquidity model them as plain `?string`/`string`, not backed enums.", + "owner": "eric@blindpay.com" + }, + { + "schema": "PayoutOut", + "field": "provider_status", + "reason": "Covers PayoutOut/PayoutOnEvmOut's inline tracking_payment.provider_status and tracking_liquidity.provider_status. Both are spec enums; PayoutTrackingPayment and TrackingLiquidity model them as plain `?string`/`string`, not backed enums.", + "owner": "eric@blindpay.com" + }, + { + "schema": "PayoutOut", + "field": "provider_name", + "reason": "Covers PayoutOut/PayoutOnEvmOut's inline tracking_payment.provider_name, a spec enum of bank-provider display names. PayoutTrackingPayment models it as a plain `?string`, not a backed enum.", + "owner": "eric@blindpay.com" + }, + { + "schema": "PayoutOut", + "field": "ach_cop_document_type", + "reason": "Spec constrains ach_cop_document_type to an enum. Payout models it as a plain `?string`, not the AchCopDocument backed enum that BankAccounts already uses on the same rail; every spec value still parses without error.", + "owner": "eric@blindpay.com" + }, + { + "schema": "VirtualAccountOut", + "field": "kyc_status", + "reason": "Same shape of divergence as CustomerOut.kyc_status above: spec constrains kyc_status to an enum, VirtualAccount models it as a plain `?string`, not a backed enum. Every spec value still parses without error.", + "owner": "eric@blindpay.com" + }, + { + "schema": "VirtualAccountOut", + "field": "account_type", + "reason": "Spec constrains us.account_type to an enum (checking/saving). VirtualAccountUsDetails models it as a plain `string`, not BankAccountType; every spec value still parses without error.", + "owner": "eric@blindpay.com" + }, + { + "schema": "WebhookEndpoint", + "field": "events", + "reason": "Spec constrains each item of the events array to the webhook-topic enum (WebhookEvents, already mapped). WebhookEndpoint models the whole array as plain `?array`, not a typed list of WebhookEvents; every spec value still parses without error.", + "owner": "eric@blindpay.com" + }, + { + "schema": "WebhookEndpointIn", + "field": "events", + "reason": "Same divergence as WebhookEndpoint.events, on the create-input side (CreateWebhookEndpointInput).", + "owner": "eric@blindpay.com" + }, + { + "schema": "PayinOut", + "field": "provider_name", + "reason": "Covers PayinOut/CreatePayinOut's inline tracking_transaction.provider_name, a spec enum of payment-provider names. TrackingTransaction models it as a plain `?string`, not a backed enum; every spec value still parses without error.", + "owner": "eric@blindpay.com" + }, + { + "schema": "CreatePayinQuoteIn", + "field": "pse_document_type", + "reason": "Spec constrains payer_rules.pse_document_type to a 2-value enum (CC/NIT). PayerRules models it as a plain `?string`; every spec value still parses without error.", + "owner": "eric@blindpay.com" } ] } diff --git a/.api-sync/spec-map.json b/.api-sync/spec-map.json index 1b384ff..ea51d45 100644 --- a/.api-sync/spec-map.json +++ b/.api-sync/spec-map.json @@ -20,8 +20,7 @@ }, { "spec": { "schema": "BankAccountOut", "property": "account_type" }, - "sdk": { "file": "src/Types/BankAccountType.php", "class": "BankAccountType" }, - "note": "KNOWN DIVERGENCE, see .api-sync/known-divergences.json: spec value 'saving' vs SDK case value 'savings'. Do not blind-add a new case for 'saving'; that would create a duplicate near-identical case instead of fixing the real one. Kept mapped on purpose so the divergence stays visible." + "sdk": { "file": "src/Types/BankAccountType.php", "class": "BankAccountType" } }, { "spec": { "schema": "VirtualAccountOut", "property": "banking_partner" }, @@ -89,6 +88,58 @@ "spec": { "schema": "PayoutOut", "property": "status" }, "sdk": { "file": "src/Types/TransactionStatus.php", "class": "TransactionStatus" }, "note": "union of PayoutOut/PayinOut/TransferOut.status; SDK's PENDING_REVIEW case has no counterpart in any of the three today, which is fine (SDK may accept a superset)." + }, + { + "spec": { "schema": "BankAccountOut", "property": "ach_cop_document_type" }, + "sdk": { "file": "src/Resources/BankAccounts/BankAccounts.php", "class": "AchCopDocument" } + }, + { + "spec": { "schema": "BankAccountOut", "property": "transfers_type" }, + "sdk": { "file": "src/Resources/BankAccounts/BankAccounts.php", "class": "ArgentinaTransfers" } + }, + { + "spec": { "schema": "PayoutOut", "property": "transfers_type" }, + "sdk": { "file": "src/Resources/Payouts/Payouts.php", "class": "ArgentinaTransferType" } + }, + { + "spec": { "schema": "CustomerLimitIncreaseIn", "property": "supporting_document_type" }, + "sdk": { "file": "src/Resources/Customers/Customers.php", "class": "LimitIncreaseRequestSupportingDocumentType" } + }, + { + "spec": { "schema": "GetCustomerLimitIncreaseOut", "property": "status" }, + "sdk": { "file": "src/Resources/Customers/Customers.php", "class": "LimitIncreaseRequestStatus" } + }, + { + "spec": { "schema": "GetCustomerLimitIncreaseOut", "property": "supporting_document_type" }, + "sdk": { "file": "src/Resources/Customers/Customers.php", "class": "LimitIncreaseRequestSupportingDocumentType" } + }, + { + "spec": { "schema": "PayinOut", "property": "manual_execution_status" }, + "sdk": { "file": "src/Types/ManualExecutionStatus.php", "class": "ManualExecutionStatus" } + }, + { + "spec": { "schema": "PayoutOut", "property": "spei_protocol" }, + "sdk": { "file": "src/Resources/Payouts/Payouts.php", "class": "SpeiProtocol" } + }, + { + "spec": { "schema": "UpdateCustomerIn", "property": "id_doc_type" }, + "sdk": { "file": "src/Resources/Customers/Customers.php", "class": "IdentificationDocument" } + }, + { + "spec": { "schema": "UpdateCustomerIn", "property": "proof_of_address_doc_type" }, + "sdk": { "file": "src/Resources/Customers/Customers.php", "class": "ProofOfAddressDocType" } + }, + { + "spec": { "schema": "UpdateCustomerIn", "property": "purpose_of_transactions" }, + "sdk": { "file": "src/Resources/Customers/Customers.php", "class": "PurposeOfTransactions" } + }, + { + "spec": { "schema": "UpdateCustomerIn", "property": "source_of_funds_doc_type" }, + "sdk": { "file": "src/Resources/Customers/Customers.php", "class": "SourceOfFundsDocType" } + }, + { + "spec": { "schema": "UploadIn", "property": "bucket" }, + "sdk": { "file": "src/Resources/Upload/Upload.php", "class": "UploadBucket" } } ], "typesNote": "Class-per-file below always refers to src/Resources/**/*.php unless the file starts with src/Types/.", @@ -139,6 +190,7 @@ { "spec": "QuoteIn", "sdk": [ { "file": "src/Resources/Quotes/Quotes.php", "class": "CreateQuoteInput" } ] }, { "spec": "QuoteOut", "sdk": [ { "file": "src/Resources/Quotes/Quotes.php", "class": "CreateQuoteResponse" } ] }, { "spec": "CreatePayinQuoteIn", "sdk": [ { "file": "src/Resources/Payins/Quotes.php", "class": "CreatePayinQuoteInput" } ] }, + { "spec": "CreatePayinQuoteIn", "path": "payer_rules", "sdk": [ { "file": "src/Resources/Payins/Quotes.php", "class": "PayerRules" } ] }, { "spec": "CreatePayinQuoteOut", "sdk": [ { "file": "src/Resources/Payins/Quotes.php", "class": "CreatePayinQuoteResponse" } ] }, { "spec": "CreateTransferQuoteIn", "sdk": [ { "file": "src/Resources/Transfers/Transfers.php", "class": "CreateTransferQuoteInput" } ] }, { "spec": "CreateTransferQuoteOut", "sdk": [ { "file": "src/Resources/Transfers/Transfers.php", "class": "CreateTransferQuoteResponse" } ] }, @@ -174,14 +226,96 @@ "note": "inline object, not a $ref'd component schema (no `Tracking*` schema exists in components.schemas); the same 20-property shape also recurs verbatim, unchecked here, in PayoutNewWebhookOut/PayoutUpdateWebhookOut/PayoutCompleteWebhookOut/PayoutPartnerFeeWebhookOut, which this SDK never deserializes (see 'webhook payloads' note below)." }, { - "spec": "PayinOut", + "spec": ["PayoutOut", "PayoutOnEvmOut"], + "path": "tracking_transaction", + "sdk": [ { "file": "src/Types/TrackingTransaction.php", "class": "TrackingTransaction" } ] + }, + { + "spec": ["PayoutOut", "PayoutOnEvmOut"], + "path": "tracking_complete", + "sdk": [ { "file": "src/Types/TrackingComplete.php", "class": "TrackingComplete" } ] + }, + { + "spec": ["PayoutOut", "PayoutOnEvmOut"], + "path": "tracking_liquidity", + "sdk": [ { "file": "src/Types/TrackingLiquidity.php", "class": "TrackingLiquidity" } ] + }, + { + "spec": ["PayinOut", "CreatePayinOut"], "path": "tracking_payment", "sdk": [ { "file": "src/Types/PayinTrackingPayment.php", "class": "PayinTrackingPayment" } ], "note": "PayinOut's inline tracking_payment is a DIFFERENT, smaller shape (step/provider_name/completed_at/review_source/review_context/approved_risk_sources) than the payout one; previously shared with the payout-side TrackingPayment class, which threw a TypeError on payin fetch. Split into a payin-specific class (step/provider_name/completed_at) and PayoutTrackingPayment (step/provider_name/provider_transaction_id/provider_status/estimated_time_of_arrival/completed_at)." }, + { + "spec": ["PayinOut", "CreatePayinOut"], + "path": "tracking_transaction", + "sdk": [ { "file": "src/Types/TrackingTransaction.php", "class": "TrackingTransaction" } ] + }, + { + "spec": ["PayinOut", "CreatePayinOut"], + "path": "tracking_complete", + "sdk": [ { "file": "src/Types/TrackingComplete.php", "class": "TrackingComplete" } ] + }, + { + "spec": ["PayinOut", "CreatePayinOut"], + "path": "blindpay_bank_details", + "sdk": [ { "file": "src/Resources/Payins/Payins.php", "class": "BankDetails" } ] + }, + { + "spec": ["PayinOut", "CreatePayinOut"], + "path": "blindpay_bank_details.ach", + "sdk": [ { "file": "src/Resources/Payins/Payins.php", "class": "BankAch" } ] + }, + { + "spec": ["PayinOut", "CreatePayinOut"], + "path": "blindpay_bank_details.wire", + "sdk": [ { "file": "src/Resources/Payins/Payins.php", "class": "BankWire" } ] + }, + { + "spec": ["PayinOut", "CreatePayinOut"], + "path": "blindpay_bank_details.rtp", + "sdk": [ { "file": "src/Resources/Payins/Payins.php", "class": "BankRtp" } ] + }, + { + "spec": ["PayinOut", "CreatePayinOut"], + "path": "blindpay_bank_details.beneficiary", + "sdk": [ { "file": "src/Resources/Payins/Payins.php", "class": "BankBeneficiary" } ] + }, + { + "spec": ["PayinOut", "CreatePayinOut"], + "path": "blindpay_bank_details.receiving_bank", + "sdk": [ { "file": "src/Resources/Payins/Payins.php", "class": "BankReceivingBank" } ] + }, + { + "spec": ["TransferOut", "CreateTransferOut"], + "path": "tracking_complete", + "sdk": [ { "file": "src/Types/TrackingComplete.php", "class": "TrackingComplete" } ] + }, + { + "spec": ["TransferOut", "CreateTransferOut"], + "path": "tracking_paymaster", + "sdk": [ { "file": "src/Resources/Transfers/Transfers.php", "class": "TransferTrackingPaymaster" } ] + }, + { + "spec": ["TransferOut", "CreateTransferOut"], + "path": "tracking_bridge_swap", + "sdk": [ { "file": "src/Resources/Transfers/Transfers.php", "class": "TransferTrackingBridgeSwap" } ] + }, + { + "spec": ["TransferOut", "CreateTransferOut"], + "path": "tracking_transaction_monitoring", + "sdk": [ { "file": "src/Resources/Transfers/Transfers.php", "class": "TransferTrackingTransactionMonitoring" } ] + }, { "spec": "CreateVirtualAccountIn", "sdk": [ { "file": "src/Resources/VirtualAccounts/VirtualAccounts.php", "class": "CreateVirtualAccountInput" } ] }, { "spec": "UpdateVirtualAccountIn", "sdk": [ { "file": "src/Resources/VirtualAccounts/VirtualAccounts.php", "class": "UpdateVirtualAccountInput" } ] }, { "spec": "VirtualAccountOut", "sdk": [ { "file": "src/Resources/VirtualAccounts/VirtualAccounts.php", "class": "VirtualAccount" } ] }, + { "spec": "VirtualAccountOut", "path": "us", "sdk": [ { "file": "src/Resources/VirtualAccounts/VirtualAccounts.php", "class": "VirtualAccountUsDetails" } ] }, + { "spec": "VirtualAccountOut", "path": "us.ach", "sdk": [ { "file": "src/Resources/VirtualAccounts/VirtualAccounts.php", "class": "VirtualAccountUsAch" } ] }, + { "spec": "VirtualAccountOut", "path": "us.wire", "sdk": [ { "file": "src/Resources/VirtualAccounts/VirtualAccounts.php", "class": "VirtualAccountUsWire" } ] }, + { "spec": "VirtualAccountOut", "path": "us.rtp", "sdk": [ { "file": "src/Resources/VirtualAccounts/VirtualAccounts.php", "class": "VirtualAccountUsRtp" } ] }, + { "spec": "VirtualAccountOut", "path": "us.beneficiary", "sdk": [ { "file": "src/Resources/VirtualAccounts/VirtualAccounts.php", "class": "VirtualAccountBeneficiary" } ] }, + { "spec": "VirtualAccountOut", "path": "us.receiving_bank", "sdk": [ { "file": "src/Resources/VirtualAccounts/VirtualAccounts.php", "class": "VirtualAccountReceivingBank" } ] }, + { "spec": "VirtualAccountOut", "path": "us.swift_receiving_bank", "sdk": [ { "file": "src/Resources/VirtualAccounts/VirtualAccounts.php", "class": "VirtualAccountSwiftReceivingBank" } ] }, { "spec": "CreateBlockchainWalletIn", "discriminator": { "note": "body-shape discriminated: address vs signature hash", "sdkMethods": ["createWithAddress", "createWithHash"] }, diff --git a/.api-sync/unmodeled.json b/.api-sync/unmodeled.json index 80c65d1..11e7e82 100644 --- a/.api-sync/unmodeled.json +++ b/.api-sync/unmodeled.json @@ -1,5 +1,5 @@ { - "$schema": "Seeded state-reconciliation baseline: every spec property, on a schema mapped in .api-sync/spec-map.json, that scripts/api-sync.php currently finds absent from its mapped SDK class(es). Each entry needs a specific reason and an owner. `--check` treats anything absent from both the SDK and this file as pending drift and fails. Do not add an entry for a field this PR's changelist is adding instead.", + "$schema": "Seeded state-reconciliation baseline: every spec property, on a schema mapped in .api-sync/spec-map.json, that scripts/api-sync.php currently finds absent from its mapped SDK class(es). Each entry needs a specific reason and an owner. `--check` treats anything absent from both the SDK and this file as pending drift and fails. Do not add an entry for a field this PR's changelist is adding instead. An entry with `\"field\": \"*\"` (paired with `\"path\"`) is a whole-shape omission: the entire inline object or array-of-object shape at schema.path is not modeled as its own class at all (checked by computeNestedObjectCoverageGaps() in scripts/api-sync.php), as opposed to a single named field being absent from an otherwise-modeled shape.", "entries": [ { "schema": "BankAccountOut", @@ -593,6 +593,202 @@ "field": "requiredWhen", "reason": "Conditional-requirement expression added to the spec after BankDetail was last generated; not modeled.", "owner": "eric@blindpay.com" + }, + { + "schema": "BankAccountOut", + "path": "offramp_wallets[]", + "field": "*", + "reason": "BankAccountListItem stores offramp_wallets as a raw ?array passthrough (not decoded into OfframpWallet objects); callers who need typed offramp wallets use OfframpWallets::list() instead.", + "owner": "eric@blindpay.com" + }, + { + "schema": "CreateCustomerIn", + "path": "owners[]", + "field": "*", + "reason": "Beneficial-owner sub-objects on business customer creation; not modeled as a class, no owners param on CreateCustomerIn.", + "owner": "eric@blindpay.com" + }, + { + "schema": "UpdateCustomerIn", + "path": "owners[]", + "field": "*", + "reason": "Same gap as CreateCustomerIn.owners[] on the update input.", + "owner": "eric@blindpay.com" + }, + { + "schema": "CustomerOut", + "path": "owners[]", + "field": "*", + "reason": "Beneficial-owner sub-objects echoed back on the customer response; not modeled, same gap as the create/update input side.", + "owner": "eric@blindpay.com" + }, + { + "schema": "CustomerOut", + "path": "kyc_warnings[]", + "field": "*", + "reason": "KYC warning sub-objects; CustomerOut's kyc_warnings property is not modeled as a typed class list.", + "owner": "eric@blindpay.com" + }, + { + "schema": "CustomerOut", + "path": "fraud_warnings[]", + "field": "*", + "reason": "Fraud warning sub-objects; not modeled as a typed class list.", + "owner": "eric@blindpay.com" + }, + { + "schema": "CustomerOut", + "path": "aml_hits", + "field": "*", + "reason": "AML screening hit details; not modeled on the customer response.", + "owner": "eric@blindpay.com" + }, + { + "schema": "CustomerOut", + "path": "limit", + "field": "*", + "reason": "Per-customer transaction limit sub-object; not modeled, distinct from GetCustomerLimitsOut which also lacks its own nested modeling (see below).", + "owner": "eric@blindpay.com" + }, + { + "schema": "GetCustomerLimitsOut", + "path": "limits", + "field": "*", + "reason": "Limits container sub-object; GetCustomerLimitsOut is not otherwise modeled with typed nested classes.", + "owner": "eric@blindpay.com" + }, + { + "schema": "GetCustomerLimitsOut", + "path": "limits.payin", + "field": "*", + "reason": "Nested under limits; same gap as GetCustomerLimitsOut.limits.", + "owner": "eric@blindpay.com" + }, + { + "schema": "GetCustomerLimitsOut", + "path": "limits.payout", + "field": "*", + "reason": "Nested under limits; same gap as GetCustomerLimitsOut.limits.", + "owner": "eric@blindpay.com" + }, + { + "schema": "QuoteOut", + "path": "contract", + "field": "*", + "reason": "On-chain contract call metadata for the quote; not modeled on CreateQuoteResponse.", + "owner": "eric@blindpay.com" + }, + { + "schema": "QuoteOut", + "path": "contract.abi[]", + "field": "*", + "reason": "ABI fragment entries under contract; same gap as QuoteOut.contract.", + "owner": "eric@blindpay.com" + }, + { + "schema": "QuoteOut", + "path": "contract.network", + "field": "*", + "reason": "Enum-constrained network sub-object under contract; same gap as QuoteOut.contract.", + "owner": "eric@blindpay.com" + }, + { + "schema": "PayinOut", + "path": "payer_rules", + "field": "*", + "reason": "Same payer_rules shape echoed back on the payin response; not modeled.", + "owner": "eric@blindpay.com" + }, + { + "schema": "PayinOut", + "path": "tracking_transaction.pse_instruction", + "field": "*", + "reason": "PSE-specific instruction sub-object under tracking_transaction; TrackingTransaction models only the common step/status/transaction_hash/completed_at fields, not the rail-specific instruction payloads.", + "owner": "eric@blindpay.com" + }, + { + "schema": "PayinOut", + "path": "tracking_transaction.ted_instruction", + "field": "*", + "reason": "TED-specific (Brazil) instruction sub-object under tracking_transaction; same gap as pse_instruction.", + "owner": "eric@blindpay.com" + }, + { + "schema": "PayinOut", + "path": "tracking_transaction.transfers_instruction", + "field": "*", + "reason": "Transfers-rail (Argentina CVU/CBU/ALIAS) instruction sub-object under tracking_transaction; same gap as pse_instruction.", + "owner": "eric@blindpay.com" + }, + { + "schema": "CreatePayinOut", + "path": "tracking_transaction.pse_instruction", + "field": "*", + "reason": "Same gap as PayinOut.tracking_transaction.pse_instruction, on the create-response shape sharing TrackingTransaction.", + "owner": "eric@blindpay.com" + }, + { + "schema": "CreatePayinOut", + "path": "tracking_transaction.ted_instruction", + "field": "*", + "reason": "Same gap as PayinOut.tracking_transaction.ted_instruction.", + "owner": "eric@blindpay.com" + }, + { + "schema": "CreatePayinOut", + "path": "tracking_transaction.transfers_instruction", + "field": "*", + "reason": "Same gap as PayinOut.tracking_transaction.transfers_instruction.", + "owner": "eric@blindpay.com" + }, + { + "schema": "PayinOut", + "path": "blindpay_bank_details.swift_receiving_bank", + "field": "*", + "reason": "International SWIFT receiving-bank sub-object; BankDetails models ach/wire/rtp/beneficiary/receiving_bank but not the SWIFT-specific receiving bank.", + "owner": "eric@blindpay.com" + }, + { + "schema": "CreatePayinOut", + "path": "blindpay_bank_details.swift_receiving_bank", + "field": "*", + "reason": "Same gap as PayinOut.blindpay_bank_details.swift_receiving_bank.", + "owner": "eric@blindpay.com" + }, + { + "schema": "PayoutOut", + "path": "tracking_documents", + "field": "*", + "reason": "SWIFT payout document-request tracking sub-object; no TrackingDocuments class exists (same field already recorded above as a scalar-shaped unmodeled field on PayoutOut).", + "owner": "eric@blindpay.com" + }, + { + "schema": "PayoutOnEvmOut", + "path": "tracking_documents", + "field": "*", + "reason": "Same gap as PayoutOut.tracking_documents, on the create-response shape.", + "owner": "eric@blindpay.com" + }, + { + "schema": "PayoutOut", + "path": "jpm_track_data", + "field": "*", + "reason": "Internal JPMorgan/Zenus tracking payload; deliberately not surfaced to SDK consumers.", + "owner": "eric@blindpay.com" + }, + { + "schema": "VirtualAccountOut", + "path": "blockchain_wallet", + "field": "*", + "reason": "Embedded blockchain wallet object (network/address); VirtualAccount only models blockchain_wallet_id (the string id), not the embedded object.", + "owner": "eric@blindpay.com" + }, + { + "schema": "VirtualAccountOut", + "path": "us.swift_intermediary_bank", + "field": "*", + "reason": "SWIFT intermediary-bank sub-object; VirtualAccountUsDetails models swift_receiving_bank but not swift_intermediary_bank.", + "owner": "eric@blindpay.com" } ] } diff --git a/.github/workflows/api-sync.yml b/.github/workflows/api-sync.yml index fdb501c..11f1989 100644 --- a/.github/workflows/api-sync.yml +++ b/.github/workflows/api-sync.yml @@ -28,6 +28,41 @@ jobs: run: | git fetch origin api-sync-data git show origin/api-sync-data:.api-sync/spec-current.json > .api-sync/spec-current.json + git show origin/api-sync-data:.api-sync/delivery.json > /tmp/delivery.json + + - name: Verify delivery manifest + env: + DISPATCH_SPEC_SHA256: ${{ github.event.client_payload.spec_sha256 }} + run: | + set -e + if [ ! -s .api-sync/spec-current.json ]; then + echo "::error::api-sync-data is missing .api-sync/spec-current.json" + exit 1 + fi + if [ ! -s /tmp/delivery.json ]; then + echo "::error::api-sync-data is missing .api-sync/delivery.json" + exit 1 + fi + + MANIFEST_SHA256=$(php -r '$d = json_decode(file_get_contents("/tmp/delivery.json"), true); echo $d["spec_sha256"] ?? "";') + if [ -z "$MANIFEST_SHA256" ]; then + echo "::error::delivery.json has no spec_sha256" + exit 1 + fi + + ACTUAL_SHA256=$(sha256sum .api-sync/spec-current.json | awk '{print $1}') + if [ "$ACTUAL_SHA256" != "$MANIFEST_SHA256" ]; then + echo "::error::spec-current.json sha256 ($ACTUAL_SHA256) does not match delivery.json's spec_sha256 ($MANIFEST_SHA256)" + exit 1 + fi + + if [ -n "$DISPATCH_SPEC_SHA256" ] && [ "$DISPATCH_SPEC_SHA256" != "$MANIFEST_SHA256" ]; then + echo "::error::stale delivery: repository_dispatch spec_sha256 ($DISPATCH_SPEC_SHA256) does not match api-sync-data's delivery.json spec_sha256 ($MANIFEST_SHA256)" + exit 1 + fi + + echo "Delivery manifest verified: spec_sha256=$MANIFEST_SHA256" + cat /tmp/delivery.json - name: Create or reset the api-sync branch run: | diff --git a/scripts/api-sync.php b/scripts/api-sync.php index bca729b..63680ba 100644 --- a/scripts/api-sync.php +++ b/scripts/api-sync.php @@ -765,6 +765,186 @@ function reconcileEnums(array $map, array $newSpec, array $reachable, array $cla return [$applicable, $needsHuman]; } +// --------------------------------------------------------------------------- +// Enum coverage (blocking): every enum-constrained property on a mapped schema must resolve to a +// mapped enum symbol, or be a recorded ledger exclusion. Distinct from reconcileEnums(), which +// checks completeness of an enum ALREADY in the map; this checks that no enum-bearing property +// was ever silently left out of the map to begin with. +// --------------------------------------------------------------------------- + +/** Whether $propSchema is enum-constrained: a direct `enum`, an `items.enum` (array of enum), or + * an `anyOf`/`oneOf` member carrying `enum`. */ +function isEnumConstrained(array $propSchema): bool +{ + if (isset($propSchema['enum'])) { + return true; + } + if (isset($propSchema['items']) && is_array($propSchema['items']) && isset($propSchema['items']['enum'])) { + return true; + } + foreach (['anyOf', 'oneOf'] as $key) { + if (! isset($propSchema[$key]) || ! is_array($propSchema[$key])) { + continue; + } + foreach ($propSchema[$key] as $sub) { + if (is_array($sub) && isset($sub['enum'])) { + return true; + } + } + } + + return false; +} + +function computeEnumCoverageGaps(array $map, array $spec, array $reachable, array $classIndex, array $divergenceFieldIndex): array +{ + $enumClassSet = []; + foreach ($map['enums'] as $entry) { + if (isset($entry['sdk']['class'])) { + $enumClassSet[$entry['sdk']['class']] = true; + } + } + + $gaps = []; + foreach ($map['types'] as $entry) { + // Discriminator fan-outs (>1 SDK class per entry) genuinely diverge per rail; not this + // check's job, same restriction reconcileTypes()/auditTypes() use. + if (count($entry['sdk']) !== 1) { + continue; + } + $schemas = typeSpecSchemas($entry); + $schemaPath = $entry['path'] ?? null; + $canonicalSchema = $schemas[0]; + + $info = $classIndex[$entry['sdk'][0]['class']] ?? null; + if ($info === null) { + continue; + } + $ctorTypesByParam = []; + foreach ($info['ctor']['params'] ?? [] as $param) { + $ctorTypesByParam[$param['name']] ??= $param['type']; + } + + foreach ($schemas as $schemaName) { + if (! isset($reachable[$schemaName])) { + continue; + } + $specProps = $schemaPath !== null ? nestedProps($spec, $schemaName, $schemaPath) : schemaProps($spec, $schemaName); + if ($specProps === null) { + continue; + } + + foreach ($specProps as $field) { + $propSchema = $schemaPath !== null + ? nestedPropSchema($spec, $schemaName, $schemaPath, $field) + : ($spec['components']['schemas'][$schemaName]['properties'][$field] ?? []); + if (! isEnumConstrained($propSchema)) { + continue; + } + if (isset($divergenceFieldIndex["{$canonicalSchema}|{$field}"])) { + continue; // recorded exclusion, e.g. "modeled as a plain untyped field" + } + $phpType = $ctorTypesByParam[snakeToCamel($field)] ?? null; + if ($phpType === null) { + continue; // not modeled via a promoted ctor property -- presence gaps are handled elsewhere + } + if (isset($enumClassSet[ltrim($phpType, '?')])) { + continue; // resolves to a mapped enum symbol + } + $label = $schemaName.($schemaPath !== null ? ".{$schemaPath}" : ''); + $gaps[] = "NEEDS_HUMAN: enum-constrained property {$label}.{$field} does not resolve to a mapped enum symbol (SDK models it as {$phpType}); add a spec-map.json enums entry or a known-divergences.json fields exclusion"; + } + } + } + sort($gaps); + + return $gaps; +} + +// --------------------------------------------------------------------------- +// Nested-object coverage (blocking): every inline object / array-of-object shape reachable under +// a mapped schema must itself have a spec-map.json path entry, or a recorded intentional omission +// in unmodeled.json (an entry with field "*" for that schema/path). +// --------------------------------------------------------------------------- + +/** Recursively collect dotted paths (array items suffixed "[]") to every inline object or + * array-of-object shape under $node. $ref'd shapes are excluded -- they are separately reachable, + * mapped component schemas, not inline shapes owned by this schema. */ +function findNestedObjectShapes(array $node, string $basePath): array +{ + $out = []; + foreach ($node['properties'] ?? [] as $propName => $propSchema) { + if (isset($propSchema['$ref'])) { + continue; + } + $types = is_array($propSchema['type'] ?? null) ? $propSchema['type'] : [$propSchema['type'] ?? null]; + if (in_array('object', $types, true) && isset($propSchema['properties'])) { + $path = "{$basePath}.{$propName}"; + $out[] = $path; + $out = array_merge($out, findNestedObjectShapes($propSchema, $path)); + + continue; + } + $items = $propSchema['items'] ?? null; + if (in_array('array', $types, true) && is_array($items) && ! isset($items['$ref'])) { + $itemTypes = is_array($items['type'] ?? null) ? $items['type'] : [$items['type'] ?? null]; + if (in_array('object', $itemTypes, true) && isset($items['properties'])) { + $path = "{$basePath}.{$propName}[]"; + $out[] = $path; + $out = array_merge($out, findNestedObjectShapes($items, $path)); + } + } + } + + return $out; +} + +function computeNestedObjectCoverageGaps(array $map, array $spec, array $reachable, array $unmodeledIndex): array +{ + $mappedPaths = []; + foreach ($map['types'] as $entry) { + $path = $entry['path'] ?? null; + if ($path === null) { + continue; + } + foreach (typeSpecSchemas($entry) as $s) { + $mappedPaths["{$s}|{$path}"] = true; + } + } + + $mappedSchemas = []; + foreach ($map['types'] as $entry) { + foreach (typeSpecSchemas($entry) as $s) { + $mappedSchemas[$s] = true; + } + } + + $gaps = []; + foreach (array_keys($mappedSchemas) as $schemaName) { + if (! isset($reachable[$schemaName])) { + continue; + } + $node = $spec['components']['schemas'][$schemaName] ?? null; + if ($node === null) { + continue; + } + foreach (findNestedObjectShapes($node, $schemaName) as $fullPath) { + $relPath = substr($fullPath, strlen($schemaName) + 1); + if (isset($mappedPaths["{$schemaName}|{$relPath}"])) { + continue; + } + if (isset($unmodeledIndex["{$schemaName}|{$relPath}|*"])) { + continue; // recorded intentional omission + } + $gaps[] = "NEEDS_HUMAN: nested object shape {$schemaName}.{$relPath} has no spec-map.json entry and no recorded omission; " + ."add a mapping or an unmodeled.json entry ({\"schema\":\"{$schemaName}\",\"path\":\"{$relPath}\",\"field\":\"*\"})"; + } + } + sort($gaps); + + return $gaps; +} + // --------------------------------------------------------------------------- // Reconciliation: types / fields // --------------------------------------------------------------------------- @@ -1439,7 +1619,14 @@ function runCli(array $argv, string $root): void $coverage = computeCoverageReport($map, $newSpec, $reachableNew); - $needsHuman = array_merge($mapErrors, $structuralIssues, $enumNeedsHuman, $fieldNeedsHuman); + $enumCoverageGaps = empty($mapErrors) + ? computeEnumCoverageGaps($map, $newSpec, $reachableNew, $classIndex, $divergenceFieldIndex) + : []; + $nestedObjectGaps = empty($mapErrors) + ? computeNestedObjectCoverageGaps($map, $newSpec, $reachableNew, $unmodeledIndex) + : []; + + $needsHuman = array_merge($mapErrors, $structuralIssues, $enumNeedsHuman, $fieldNeedsHuman, $enumCoverageGaps, $nestedObjectGaps); $applicable = array_merge($enumApplicable, $fieldApplicable); usort($applicable, fn ($a, $b) => $a['sortKey'] <=> $b['sortKey']); diff --git a/src/Resources/Customers/Customers.php b/src/Resources/Customers/Customers.php index 82472d5..410fe5b 100644 --- a/src/Resources/Customers/Customers.php +++ b/src/Resources/Customers/Customers.php @@ -91,9 +91,18 @@ enum LimitIncreaseRequestSupportingDocumentType: string case INDIVIDUAL_BANK_STATEMENT = 'individual_bank_statement'; case INDIVIDUAL_TAX_RETURN = 'individual_tax_return'; case INDIVIDUAL_PROOF_OF_INCOME = 'individual_proof_of_income'; + case INDIVIDUAL_PAY_STUB = 'individual_pay_stub'; + case INDIVIDUAL_EMPLOYMENT_LETTER = 'individual_employment_letter'; + case INDIVIDUAL_INVESTMENT_STATEMENT = 'individual_investment_statement'; + case INDIVIDUAL_CRYPTO_EXCHANGE_STATEMENT = 'individual_crypto_exchange_statement'; + case INDIVIDUAL_BLOCKCHAIN_WALLET_STATEMENT = 'individual_blockchain_wallet_statement'; case BUSINESS_BANK_STATEMENT = 'business_bank_statement'; case BUSINESS_FINANCIAL_STATEMENTS = 'business_financial_statements'; case BUSINESS_TAX_RETURN = 'business_tax_return'; + case BUSINESS_CONTRACT = 'business_contract'; + case BUSINESS_ACCOUNTS_RECEIVABLE = 'business_accounts_receivable'; + case BUSINESS_MERCHANT_PROCESSOR_STATEMENT = 'business_merchant_processor_statement'; + case BUSINESS_SHAREHOLDER_LOAN = 'business_shareholder_loan'; } enum OwnerTaxType: string diff --git a/src/Resources/Payins/Payins.php b/src/Resources/Payins/Payins.php index 9edfb30..f3d3409 100644 --- a/src/Resources/Payins/Payins.php +++ b/src/Resources/Payins/Payins.php @@ -154,7 +154,9 @@ public function __construct( public BankWire $wire, public BankRtp $rtp, public BankBeneficiary $beneficiary, - public BankReceivingBank $receivingBank + public BankReceivingBank $receivingBank, + public ?string $swiftAccountNumber = null, + public ?array $swiftReceivingBank = null ) {} public static function fromArray(array $data): self @@ -168,7 +170,9 @@ public static function fromArray(array $data): self wire: BankWire::fromArray($data['wire']), rtp: BankRtp::fromArray($data['rtp']), beneficiary: BankBeneficiary::fromArray($data['beneficiary']), - receivingBank: BankReceivingBank::fromArray($data['receiving_bank']) + receivingBank: BankReceivingBank::fromArray($data['receiving_bank']), + swiftAccountNumber: $data['swift_account_number'] ?? null, + swiftReceivingBank: $data['swift_receiving_bank'] ?? null ); } @@ -184,6 +188,8 @@ public function toArray(): array 'rtp' => $this->rtp->toArray(), 'beneficiary' => $this->beneficiary->toArray(), 'receiving_bank' => $this->receivingBank->toArray(), + 'swift_account_number' => $this->swiftAccountNumber, + 'swift_receiving_bank' => $this->swiftReceivingBank, ]; } } diff --git a/src/Resources/Payins/Quotes.php b/src/Resources/Payins/Quotes.php index e22edc4..5a9ff96 100644 --- a/src/Resources/Payins/Quotes.php +++ b/src/Resources/Payins/Quotes.php @@ -21,20 +21,44 @@ enum PaymentMethod: string readonly class PayerRules { public function __construct( - public array $pixAllowedTaxIds + public array $pixAllowedTaxIds, + public ?string $transfersAllowedTaxId = null, + public ?array $pseAllowedTaxIds = null, + public ?string $pseFullName = null, + public ?string $pseDocumentType = null, + public ?string $pseDocumentNumber = null, + public ?string $pseEmail = null, + public ?string $psePhone = null, + public ?string $pseBankCode = null ) {} public function toArray(): array { return [ 'pix_allowed_tax_ids' => $this->pixAllowedTaxIds, + 'transfers_allowed_tax_id' => $this->transfersAllowedTaxId, + 'pse_allowed_tax_ids' => $this->pseAllowedTaxIds, + 'pse_full_name' => $this->pseFullName, + 'pse_document_type' => $this->pseDocumentType, + 'pse_document_number' => $this->pseDocumentNumber, + 'pse_email' => $this->pseEmail, + 'pse_phone' => $this->psePhone, + 'pse_bank_code' => $this->pseBankCode, ]; } public static function fromArray(array $data): self { return new self( - pixAllowedTaxIds: $data['pix_allowed_tax_ids'] ?? [] + pixAllowedTaxIds: $data['pix_allowed_tax_ids'] ?? [], + transfersAllowedTaxId: $data['transfers_allowed_tax_id'] ?? null, + pseAllowedTaxIds: $data['pse_allowed_tax_ids'] ?? null, + pseFullName: $data['pse_full_name'] ?? null, + pseDocumentType: $data['pse_document_type'] ?? null, + pseDocumentNumber: $data['pse_document_number'] ?? null, + pseEmail: $data['pse_email'] ?? null, + psePhone: $data['pse_phone'] ?? null, + pseBankCode: $data['pse_bank_code'] ?? null ); } } diff --git a/src/Resources/Transfers/Transfers.php b/src/Resources/Transfers/Transfers.php index 70bd2d4..474825e 100644 --- a/src/Resources/Transfers/Transfers.php +++ b/src/Resources/Transfers/Transfers.php @@ -21,7 +21,9 @@ { public function __construct( string $step, - ?DateTimeImmutable $completedAt + public ?float $blockchainScreening = null, + public ?float $riskScore = null, + ?DateTimeImmutable $completedAt = null ) { parent::__construct($step, $completedAt); } @@ -30,6 +32,8 @@ public static function fromArray(array $data): self { return new self( step: $data['step'], + blockchainScreening: isset($data['blockchain_screening']) ? (float) $data['blockchain_screening'] : null, + riskScore: isset($data['risk_score']) ? (float) $data['risk_score'] : null, completedAt: isset($data['completed_at']) ? new DateTimeImmutable($data['completed_at']) : null @@ -40,6 +44,8 @@ public function toArray(): array { return [ 'step' => $this->step, + 'blockchain_screening' => $this->blockchainScreening, + 'risk_score' => $this->riskScore, 'completed_at' => $this->completedAt?->format('c'), ]; } @@ -49,7 +55,10 @@ public function toArray(): array { public function __construct( string $step, - ?DateTimeImmutable $completedAt + public ?string $transactionHash = null, + public ?string $gasFee = null, + public ?string $errorMessage = null, + ?DateTimeImmutable $completedAt = null ) { parent::__construct($step, $completedAt); } @@ -58,6 +67,9 @@ public static function fromArray(array $data): self { return new self( step: $data['step'], + transactionHash: $data['transaction_hash'] ?? null, + gasFee: $data['gas_fee'] ?? null, + errorMessage: $data['error_message'] ?? null, completedAt: isset($data['completed_at']) ? new DateTimeImmutable($data['completed_at']) : null @@ -68,6 +80,9 @@ public function toArray(): array { return [ 'step' => $this->step, + 'transaction_hash' => $this->transactionHash, + 'gas_fee' => $this->gasFee, + 'error_message' => $this->errorMessage, 'completed_at' => $this->completedAt?->format('c'), ]; } @@ -77,7 +92,10 @@ public function toArray(): array { public function __construct( string $step, - ?DateTimeImmutable $completedAt + public ?string $transactionHash = null, + public ?string $gasFee = null, + public ?string $errorMessage = null, + ?DateTimeImmutable $completedAt = null ) { parent::__construct($step, $completedAt); } @@ -86,6 +104,9 @@ public static function fromArray(array $data): self { return new self( step: $data['step'], + transactionHash: $data['transaction_hash'] ?? null, + gasFee: $data['gas_fee'] ?? null, + errorMessage: $data['error_message'] ?? null, completedAt: isset($data['completed_at']) ? new DateTimeImmutable($data['completed_at']) : null @@ -96,6 +117,9 @@ public function toArray(): array { return [ 'step' => $this->step, + 'transaction_hash' => $this->transactionHash, + 'gas_fee' => $this->gasFee, + 'error_message' => $this->errorMessage, 'completed_at' => $this->completedAt?->format('c'), ]; } diff --git a/src/Resources/VirtualAccounts/VirtualAccounts.php b/src/Resources/VirtualAccounts/VirtualAccounts.php index 61e967b..d66a645 100644 --- a/src/Resources/VirtualAccounts/VirtualAccounts.php +++ b/src/Resources/VirtualAccounts/VirtualAccounts.php @@ -21,7 +21,8 @@ public function __construct( public VirtualAccountBeneficiary $beneficiary, public VirtualAccountReceivingBank $receivingBank, public ?string $swiftAccountNumber = null, - public ?VirtualAccountSwiftReceivingBank $swiftReceivingBank = null + public ?VirtualAccountSwiftReceivingBank $swiftReceivingBank = null, + public ?array $swiftIntermediaryBank = null ) {} public static function fromArray(array $data): self @@ -35,7 +36,8 @@ public static function fromArray(array $data): self beneficiary: VirtualAccountBeneficiary::fromArray($data['beneficiary']), receivingBank: VirtualAccountReceivingBank::fromArray($data['receiving_bank']), swiftAccountNumber: $data['swift_account_number'] ?? null, - swiftReceivingBank: isset($data['swift_receiving_bank']) ? VirtualAccountSwiftReceivingBank::fromArray($data['swift_receiving_bank']) : null + swiftReceivingBank: isset($data['swift_receiving_bank']) ? VirtualAccountSwiftReceivingBank::fromArray($data['swift_receiving_bank']) : null, + swiftIntermediaryBank: $data['swift_intermediary_bank'] ?? null ); } @@ -59,6 +61,10 @@ public function toArray(): array $data['swift_receiving_bank'] = $this->swiftReceivingBank->toArray(); } + if ($this->swiftIntermediaryBank !== null) { + $data['swift_intermediary_bank'] = $this->swiftIntermediaryBank; + } + return $data; } } diff --git a/src/Types/ManualExecutionStatus.php b/src/Types/ManualExecutionStatus.php index f39316a..b823bf3 100644 --- a/src/Types/ManualExecutionStatus.php +++ b/src/Types/ManualExecutionStatus.php @@ -6,5 +6,7 @@ enum ManualExecutionStatus: string { + case PENDING = 'pending'; + case CONCLUDED = 'concluded'; case FAILED = 'failed'; } diff --git a/src/Types/TrackingComplete.php b/src/Types/TrackingComplete.php index 2d99a3b..bdc694a 100644 --- a/src/Types/TrackingComplete.php +++ b/src/Types/TrackingComplete.php @@ -12,7 +12,11 @@ public function __construct( string $step, public string $status, public string $transactionHash, - ?DateTimeImmutable $completedAt + public ?string $providerTransactionId = null, + public ?string $refundReason = null, + public ?string $gasFee = null, + public ?string $errorMessage = null, + ?DateTimeImmutable $completedAt = null ) { parent::__construct($step, $completedAt); } @@ -23,6 +27,10 @@ public static function fromArray(array $data): self step: $data['step'], status: $data['status'], transactionHash: $data['transaction_hash'], + providerTransactionId: $data['provider_transaction_id'] ?? null, + refundReason: $data['refund_reason'] ?? null, + gasFee: $data['gas_fee'] ?? null, + errorMessage: $data['error_message'] ?? null, completedAt: isset($data['completed_at']) ? new DateTimeImmutable($data['completed_at']) : null @@ -35,6 +43,10 @@ public function toArray(): array 'step' => $this->step, 'status' => $this->status, 'transaction_hash' => $this->transactionHash, + 'provider_transaction_id' => $this->providerTransactionId, + 'refund_reason' => $this->refundReason, + 'gas_fee' => $this->gasFee, + 'error_message' => $this->errorMessage, 'completed_at' => $this->completedAt?->format('c'), ]; } diff --git a/src/Types/TrackingTransaction.php b/src/Types/TrackingTransaction.php index b86577c..5a6b116 100644 --- a/src/Types/TrackingTransaction.php +++ b/src/Types/TrackingTransaction.php @@ -12,7 +12,25 @@ public function __construct( string $step, public string $status, public string $transactionHash, - ?DateTimeImmutable $completedAt + public ?string $externalId = null, + public ?string $providerName = null, + public ?string $providerTransactionId = null, + public ?string $providerErrorReason = null, + public ?string $senderName = null, + public ?string $senderBankName = null, + public ?string $senderTaxId = null, + public ?string $senderBankCode = null, + public ?string $senderAccountNumber = null, + public ?string $endToEndId = null, + public ?string $traceNumber = null, + public ?string $transactionReference = null, + public ?string $description = null, + public ?array $pseInstruction = null, + public ?array $transfersInstruction = null, + public ?array $tedInstruction = null, + public ?string $ledgerInTransactionId = null, + public ?string $ledgerOutTransactionId = null, + ?DateTimeImmutable $completedAt = null ) { parent::__construct($step, $completedAt); } @@ -23,6 +41,24 @@ public static function fromArray(array $data): self step: $data['step'], status: $data['status'], transactionHash: $data['transaction_hash'], + externalId: $data['external_id'] ?? null, + providerName: $data['provider_name'] ?? null, + providerTransactionId: $data['provider_transaction_id'] ?? null, + providerErrorReason: $data['provider_error_reason'] ?? null, + senderName: $data['sender_name'] ?? null, + senderBankName: $data['sender_bank_name'] ?? null, + senderTaxId: $data['sender_tax_id'] ?? null, + senderBankCode: $data['sender_bank_code'] ?? null, + senderAccountNumber: $data['sender_account_number'] ?? null, + endToEndId: $data['end_to_end_id'] ?? null, + traceNumber: $data['trace_number'] ?? null, + transactionReference: $data['transaction_reference'] ?? null, + description: $data['description'] ?? null, + pseInstruction: $data['pse_instruction'] ?? null, + transfersInstruction: $data['transfers_instruction'] ?? null, + tedInstruction: $data['ted_instruction'] ?? null, + ledgerInTransactionId: $data['ledger_in_transaction_id'] ?? null, + ledgerOutTransactionId: $data['ledger_out_transaction_id'] ?? null, completedAt: isset($data['completed_at']) ? new DateTimeImmutable($data['completed_at']) : null @@ -35,6 +71,24 @@ public function toArray(): array 'step' => $this->step, 'status' => $this->status, 'transaction_hash' => $this->transactionHash, + 'external_id' => $this->externalId, + 'provider_name' => $this->providerName, + 'provider_transaction_id' => $this->providerTransactionId, + 'provider_error_reason' => $this->providerErrorReason, + 'sender_name' => $this->senderName, + 'sender_bank_name' => $this->senderBankName, + 'sender_tax_id' => $this->senderTaxId, + 'sender_bank_code' => $this->senderBankCode, + 'sender_account_number' => $this->senderAccountNumber, + 'end_to_end_id' => $this->endToEndId, + 'trace_number' => $this->traceNumber, + 'transaction_reference' => $this->transactionReference, + 'description' => $this->description, + 'pse_instruction' => $this->pseInstruction, + 'transfers_instruction' => $this->transfersInstruction, + 'ted_instruction' => $this->tedInstruction, + 'ledger_in_transaction_id' => $this->ledgerInTransactionId, + 'ledger_out_transaction_id' => $this->ledgerOutTransactionId, 'completed_at' => $this->completedAt?->format('c'), ]; }