From 40ed5e24d9ef3c3d466c2d09692f772d62c766ca Mon Sep 17 00:00:00 2001 From: "t-0-ci[bot]" <222030753+t-0-ci[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 14:13:14 +0000 Subject: [PATCH] chore: sync files with `t-0-network/backend` --- .../main/proto/ivms101/v1/ivms/ivms101.proto | 9 +- .../tzero/v1/common/payment_method.proto | 19 +- .../main/proto/tzero/v1/payment/network.proto | 1 - .../proto/tzero/v1/payment/provider.proto | 3 + .../tzero/v1/payment_intent/beneficiary.proto | 95 +++++ .../tzero/v1/payment_intent/network.proto | 389 ++++++++++++++++++ .../v1/payment_intent/pay_in_provider.proto | 120 ++++++ 7 files changed, 623 insertions(+), 13 deletions(-) create mode 100644 sdk/src/main/proto/tzero/v1/payment_intent/beneficiary.proto create mode 100644 sdk/src/main/proto/tzero/v1/payment_intent/network.proto create mode 100644 sdk/src/main/proto/tzero/v1/payment_intent/pay_in_provider.proto diff --git a/sdk/src/main/proto/ivms101/v1/ivms/ivms101.proto b/sdk/src/main/proto/ivms101/v1/ivms/ivms101.proto index e1c65df..725568d 100644 --- a/sdk/src/main/proto/ivms101/v1/ivms/ivms101.proto +++ b/sdk/src/main/proto/ivms101/v1/ivms/ivms101.proto @@ -155,7 +155,7 @@ message LocalNaturalPersonNameId { // Constraint: ValidAddress // There must be at least one occurrence of the element addressLine or (streetName and -// buildingName and/or buildingNumber). +// buildingName and/or buildingNumber) message Address { // Definition: Identifies the nature of the address. @@ -250,7 +250,7 @@ message Address { max_items: 7, items: { string: { - max_len: 70, + max_len: 255, pattern: "^$|^.*\\S.*$" // allow empty, disallow whitespace-only } } @@ -338,7 +338,10 @@ message LegalPerson { LegalPersonName name = 1 [(buf.validate.field).required = true]; // Definition: The address of the legal person. - repeated Address geographic_addresses = 2 [json_name = "geographicAddress"]; + repeated Address geographic_addresses = 2 [ + json_name = "geographicAddress", + (buf.validate.field).repeated.min_items = 1 + ]; // Definition: The unique identification number applied by the VASP to customer. // NOTE The specification has a descrepency in that 5.2.9.3.3 specifies an element diff --git a/sdk/src/main/proto/tzero/v1/common/payment_method.proto b/sdk/src/main/proto/tzero/v1/common/payment_method.proto index 64d5a5e..1b3e090 100644 --- a/sdk/src/main/proto/tzero/v1/common/payment_method.proto +++ b/sdk/src/main/proto/tzero/v1/common/payment_method.proto @@ -11,7 +11,7 @@ enum PaymentMethodType { PAYMENT_METHOD_TYPE_SEPA = 10; PAYMENT_METHOD_TYPE_SWIFT = 20; PAYMENT_METHOD_TYPE_ACH = 50; - PAYMENT_METHOD_TYPE_WIRE = 60; + PAYMENT_METHOD_TYPE_DOMESTIC_WIRE = 60; PAYMENT_METHOD_TYPE_FPS = 70; PAYMENT_METHOD_TYPE_M_PESA = 80 [deprecated = true]; // deprecated in favor of PAYMENT_METHOD_TYPE_AFRICAN_MOBILE_MONEY @@ -48,9 +48,9 @@ message PaymentDetails { // United States Ach ach = 50; - // Wire - Domestic electronic funds transfer + // DomesticWire - US domestic wire transfer // United States - Wire wire = 60; + DomesticWire domestic_wire = 60; // FPS (Faster Payments Service) // United Kingdom @@ -408,8 +408,8 @@ message PaymentDetails { } } - message Wire { - option (payment_method_type) = PAYMENT_METHOD_TYPE_WIRE; + message DomesticWire { + option (payment_method_type) = PAYMENT_METHOD_TYPE_DOMESTIC_WIRE; string bank_name = 10 [(buf.validate.field).string = { min_len: 1, @@ -421,10 +421,11 @@ message PaymentDetails { max_len: 140 }]; - string swift_code = 30 [(buf.validate.field).string = { - min_len: 8, - max_len: 11, - pattern: "^[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?$" + // ABA routing number (9 digits) + string routing_number = 30 [(buf.validate.field).string = { + min_len: 9, + max_len: 9, + pattern: "^[0-9]{9}$" }]; string account_number = 40 [(buf.validate.field).string = { diff --git a/sdk/src/main/proto/tzero/v1/payment/network.proto b/sdk/src/main/proto/tzero/v1/payment/network.proto index a27a39f..5a780c3 100644 --- a/sdk/src/main/proto/tzero/v1/payment/network.proto +++ b/sdk/src/main/proto/tzero/v1/payment/network.proto @@ -15,7 +15,6 @@ import "buf/validate/validate.proto"; * All methods of this service are idempotent, meaning they are safe to retry and multiple calls with the same parameters will have no additional effect. */ service NetworkService { - /** * Used by the provider to publish pay-in and pay-out quotes (FX rates) into the network. * These quotes include tiered pricing bands and an expiration timestamp. diff --git a/sdk/src/main/proto/tzero/v1/payment/provider.proto b/sdk/src/main/proto/tzero/v1/payment/provider.proto index 933c11f..851af8a 100644 --- a/sdk/src/main/proto/tzero/v1/payment/provider.proto +++ b/sdk/src/main/proto/tzero/v1/payment/provider.proto @@ -129,6 +129,8 @@ message AppendLedgerEntriesRequest { ACCOUNT_TYPE_FEE_EXPENSE = 60; // Reflects the fees that the provider has to pay for the services provided by the network. ACCOUNT_TYPE_SETTLEMENT_IN = 80; ACCOUNT_TYPE_SETTLEMENT_OUT = 90; + ACCOUNT_TYPE_PAYMENT_INTENT_IN = 100; + ACCOUNT_TYPE_PAYMENT_INTENT_OUT = 110; } } @@ -272,6 +274,7 @@ message UpdatePaymentRequest { REASON_NO_QUOTE_AFTER_AML_APPROVAL = 1; // AML review completed, but the pay-out provider no longer has a valid quote available. REASON_QUOTE_REJECTED_AFTER_AML_APPROVAL = 2; // AML review completed and a new quote was required, but the pay-in provider rejected the updated quote (e.g. due to rate change after AML delay). REASON_AML_RISK_CHECK_FAILED = 3; // Payment was rejected by the pay-out provider because the AML / risk checks failed. + REASON_CREDIT_LIMIT_EXCEEDED_AFTER_AML_APPROVAL = 4; // AML review completed and a new quote was found, but the updated settlement amount exceeds the credit limit between the pay-in and pay-out providers. } } diff --git a/sdk/src/main/proto/tzero/v1/payment_intent/beneficiary.proto b/sdk/src/main/proto/tzero/v1/payment_intent/beneficiary.proto new file mode 100644 index 0000000..e0dfc4c --- /dev/null +++ b/sdk/src/main/proto/tzero/v1/payment_intent/beneficiary.proto @@ -0,0 +1,95 @@ +syntax = "proto3"; + +package tzero.v1.payment_intent; + +import "tzero/v1/common/common.proto"; +import "tzero/v1/common/payment_method.proto"; +import "buf/validate/validate.proto"; + +/** + * BeneficiaryService must be implemented by beneficiary providers to receive + * notifications about payment intent status changes. + * + * Beneficiary providers are those who: + * - Create payment intents via CreatePaymentIntent + * - Receive settlement (in settlement currency via configured blockchain network) + * - Need to be notified of payment status changes + * + * The network calls this service to notify the beneficiary when: + * - Funds have been received from the payer by pay-in provider + */ +service BeneficiaryService { + /** + * PaymentIntentUpdate notifies the beneficiary provider of status changes. + * + * Idempotency: This endpoint must be idempotent. The network may retry + * delivery in case of failures or timeouts. + */ + rpc PaymentIntentUpdate(PaymentIntentUpdateRequest) returns (PaymentIntentUpdateResponse) { + option idempotency_level = IDEMPOTENT; + }; +} + +/** + * Notification of a payment intent status change. + */ +message PaymentIntentUpdateRequest { + /** + * The payment intent ID this update relates to. + * Matches the ID returned in CreatePaymentIntentResponse. + */ + uint64 payment_intent_id = 10 [(buf.validate.field).uint64.gt = 0]; + + /** + * The type of update. + */ + oneof update { + /** + * Funds were received from the payer by pay-in provider. + */ + FundsReceived funds_received = 20; + } + + /** + * Notification that funds were received from the payer by pay-in provider. + * + */ + message FundsReceived { + /** + * The settlement amount credited to your balance. + * This is calculated as: source_amount / rate + * + * Note: Fees are NOT deducted from this amount. Fees are tracked + * separately and settled in periodic fee settlements. + */ + tzero.v1.common.Decimal settlement_amount = 10; + + /** + * The exchange rate used for settlement. + */ + tzero.v1.common.Decimal rate = 20; + + /** + * The fiat amount received from the end-user. + * Matches the amount originally requested in CreatePaymentIntent. + */ + tzero.v1.common.Decimal payment_amount = 30; + + /* + * The payment method used for the pay-in + */ + tzero.v1.common.PaymentMethodType payment_method = 40; + + /* + * Unique transaction reference identifying the pay-in transaction + */ + string transaction_reference = 50; + } +} + +/** + * Acknowledgment of receiving the payment intent update. + * Empty response indicates successful processing. + * Return an error status code if processing failed and retry is needed. + */ +message PaymentIntentUpdateResponse {} diff --git a/sdk/src/main/proto/tzero/v1/payment_intent/network.proto b/sdk/src/main/proto/tzero/v1/payment_intent/network.proto new file mode 100644 index 0000000..e9dbd2d --- /dev/null +++ b/sdk/src/main/proto/tzero/v1/payment_intent/network.proto @@ -0,0 +1,389 @@ +syntax = "proto3"; + +package tzero.v1.payment_intent; + +import "buf/validate/validate.proto"; +import "tzero/v1/common/common.proto"; +import "tzero/v1/common/payment_method.proto"; +import "ivms101/v1/ivms/ivms101.proto"; +import "google/protobuf/timestamp.proto"; + +/** + * PaymentIntentService provides Payment Intent APIs for providers. + * + * Payment Intent is a flow where: + * 1. Beneficiary provider creates a payment intent specifying amount/currency + * 2. End-user pays via one of the returned payment options + * 3. Pay-in provider confirms funds received + * 4. Settlement will happen periodically between providers + * + * This service is hosted by the T-0 Network and called by providers. + */ +service PaymentIntentService { + /** + * Used by the provider to publish payment intent (pay-in) quotes into the network. + * These quotes include tiered pricing bands and an expiration timestamp. + */ + rpc UpdateQuote(UpdateQuoteRequest) returns (UpdateQuoteResponse) { + option idempotency_level = IDEMPOTENT; + }; + + /** + * GetQuote returns available quotes for a given currency and amount. + * + * Use this to check indicative rates before creating a payment intent. + * The returned quotes show which providers can accept pay-ins and their current rates. + * + * Note: Quotes are indicative only. The actual rate used for settlement is determined + * at the time of ConfirmFundsReceived. + */ + rpc GetQuote(GetQuoteRequest) returns (GetQuoteResponse) { + option idempotency_level = IDEMPOTENT; + }; + + /** + * CreatePaymentIntent initiates a new payment intent. + * + * Called by the beneficiary provider (the one who will receive the settlement). + * The network finds suitable pay-in providers, retrieves their payment details, + * and returns available payment options to present to the end-user. + * + * The returned payment_intent_id must be stored by the beneficiary provider + * to correlate with the PaymentIntentUpdate notification received later. + * + * Idempotency: Multiple calls with the same external_reference return the same payment_intent_id. + */ + rpc CreatePaymentIntent(CreatePaymentIntentRequest) returns (CreatePaymentIntentResponse) { + option idempotency_level = IDEMPOTENT; + }; + + /** + * ConfirmFundsReceived confirms that the pay-in provider has received funds from the end-user. + */ + rpc ConfirmFundsReceived(ConfirmFundsReceivedRequest) returns (ConfirmFundsReceivedResponse) { + option idempotency_level = IDEMPOTENT; + }; +} + +/* + * Base currency is always USD, so the quotes are always in USD/currency format. + */ +message UpdateQuoteRequest { + /** + * Zero or more quotes for pay-in operations, each quote must have a unique currency, and one or more bands, with the + * unique client_quote_id for each band. + */ + repeated Quote payment_intent_quotes = 10; + + message Quote { + // BRL, EUR, GBP, etc. (ISO 4217 currency code) + string currency = 10 [(buf.validate.field).string = { + len: 3, + pattern: "^[A-Z]{3}$" + }]; + + // Payment method must be specified + tzero.v1.common.PaymentMethodType payment_method = 25 [(buf.validate.field).required = true]; + + // list of bands for this quote + repeated Band bands = 30 [(buf.validate.field).repeated = { + min_items: 1 + }]; + + // expiration time of the quote + google.protobuf.Timestamp expiration = 60 [(buf.validate.field).timestamp.gt_now = true]; + + // timestamp quote was created + google.protobuf.Timestamp timestamp = 70 [(buf.validate.field).required = true]; + + message Band { + // unique client generated id for this band + string client_quote_id = 10 [(buf.validate.field).string = { + min_len: 1, + max_len: 64 + }]; + + // max amount of USD this quote is applicable for. Please look into documentation for valid amounts. + tzero.v1.common.Decimal max_amount = 40 [(buf.validate.field).required = true]; + + // USD/currency rate + tzero.v1.common.Decimal rate = 50 [(buf.validate.field).required = true]; + } + } +} + +message UpdateQuoteResponse {} + +/** + * Request to get indicative quotes for a currency/amount pair. + */ +message GetQuoteRequest { + /** + * Pay-in currency code in ISO 4217 format. + * Examples: "EUR", "GBP", "USD", "KES" + */ + string currency = 20 [(buf.validate.field).string = { + len: 3, + pattern: "^[A-Z]{3}$" + }]; + + /** + * Pay-in amount in the specified currency. + * This is the fiat amount the end-user will pay. + */ + tzero.v1.common.Decimal amount = 30 [(buf.validate.field).required = true]; +} + +message GetQuoteResponse { + oneof Result { + Success success = 10; + QuoteNotFound quote_not_found = 20; + } + + /** + * Quotes were found for the requested currency/amount. + */ + message Success { + /* + * Best quotes per payment method available for the specified currency and amount. + */ + repeated IndicativeQuote best_quotes = 10; + /** + * Available indicative quotes. + * Each entry represents a different pay-in provider and payment method combination. + * Use CreatePaymentIntent to get the actual payment details for making a payment. + */ + repeated IndicativeQuote all_quotes = 20; + + /** + * Represents an indicative quote from a pay-in provider. + * Contains the payment method, provider info, and indicative exchange rate. + */ + message IndicativeQuote { + /** + * The payment method type (e.g., SEPA, SWIFT, mobile money). + */ + tzero.v1.common.PaymentMethodType payment_method = 10; + + /** + * The T-0 provider ID of the pay-in provider offering this quote. + * Providers can use this to identify counterparties. + */ + uint32 provider_id = 20; + + /** + * Indicative exchange rate USD/XXX (base currency is always USD). + * + * Note: This is indicative only. The actual rate is determined when pay-in provider calls ConfirmFundsReceived + */ + tzero.v1.common.Decimal indicative_rate = 30; + } + } + + /** + * No providers have active quotes for the requested currency/amount. + */ + message QuoteNotFound {} +} + +/** + * Represents pay-in details for a payment intent option. + * Contains the payment method, provider info, payment details, and indicative exchange rate. + */ +message PaymentIntentPayInDetails { + /** + * The payment method type (e.g., SEPA, SWIFT, mobile money). + * Determines which payment details format is provided. + */ + tzero.v1.common.PaymentMethodType payment_method = 10; + + /** + * The T-0 provider ID of the pay-in provider offering this quote. + * Providers can use this to identify counterparties. + */ + uint32 provider_id = 20; + + /** + * Payment details for the end-user to make the payment. + * Contains bank account info, mobile money details, etc. based on payment_method. + * This should be displayed to the end-user to complete their payment. + */ + tzero.v1.common.PaymentDetails payment_details = 30; + + /** + * Indicative exchange rate USD/XXX (base currency is always USD). + * + * Note: This is indicative only. The actual rate is determined when pay-in provider calls ConfirmFundsReceived + */ + tzero.v1.common.Decimal indicative_rate = 40; +} + +/** + * Request to create a new payment intent. + */ +message CreatePaymentIntentRequest { + /** + * Provider-supplied unique identifier for this payment intent. + * Used for idempotency - subsequent requests with the same external_reference + * return the existing payment_intent_id instead of creating a new one. + * Should be unique per beneficiary provider. + */ + string external_reference = 10; + + /** + * Pay-in currency code in ISO 4217 format. + * The currency that the end-user will pay in. + */ + string currency = 20 [(buf.validate.field).string = { + len: 3, + pattern: "^[A-Z]{3}$" + }]; + + /** + * Pay-in amount in the specified currency. + * The exact fiat amount the end-user should pay. + */ + tzero.v1.common.Decimal amount = 30 [(buf.validate.field).required = true]; + + /** + * Travel rule compliance data. + * Required for regulatory compliance under FATF guidelines. + */ + TravelRuleData travel_rule_data = 40; + + /** + * Travel rule data containing originator information. + */ + message TravelRuleData { + /** + * The natural or legal person or legal arrangement who is identified + * as the receiver of the requested payment. + */ + repeated ivms101.Person beneficiary = 10 [(buf.validate.field).repeated.min_items = 1]; + + /** + * Optional travel rule data of the payer + */ + optional ivms101.Person payer = 40; + } +} + +message CreatePaymentIntentResponse { + oneof Result { + /** + * Payment intent created successfully with available payment options. + */ + Success success = 10; + /** + * Failed to create payment intent. + */ + Failure failure = 20; + } + + /** + * Payment intent created successfully. + */ + message Success { + /** + * Unique identifier for this payment intent. + * Store this ID to correlate with: + * - PaymentIntentUpdate notifications you'll receive + */ + uint64 payment_intent_id = 10 [(buf.validate.field).uint64.gt = 0]; + + /** + * Available payment options for the end-user. + * Present these options to your user so they can choose how to pay. + * Each entry contains the payment details needed to complete the payment. + */ + repeated PaymentIntentPayInDetails pay_in_details = 20; + } + + /** + * Payment intent creation failed. + */ + message Failure { + /** + * The reason for failure. + */ + Reason reason = 10; + + enum Reason { + FAILURE_REASON_UNSPECIFIED = 0; + /** + * No quotes found for the requested currency/amount. + */ + FAILURE_REASON_QUOTE_NOT_FOUND = 10; + /** + * Payment intent rejected. + */ + FAILURE_REASON_REJECTED = 20; + } + } +} + +/** + * Request to confirm that funds have been received from the end-user. + */ +message ConfirmFundsReceivedRequest { + /** + * The payment intent ID being confirmed. + * Must be a valid, pending payment intent. + */ + uint64 payment_intent_id = 10 [(buf.validate.field).uint64.gt = 0]; + + /* + * Confirmation code received in the get payment details along with the payment_intent_id. This is to prevent + * the accidental confirmation of the wrong payment intent. + */ + string confirmation_code = 20; + + /** + * The payment method used by the end-user. + * Must match one of the payment methods returned in CreatePaymentIntentResponse. + */ + tzero.v1.common.PaymentMethodType payment_method = 30; + + /* + * Transaction reference + */ + string transaction_reference = 40 [(buf.validate.field).string = { + min_len: 1, + max_len: 256 + }]; + +} + +message ConfirmFundsReceivedResponse { + oneof Result { + /** + * Funds confirmed successfully. Settlement will proceed. + */ + Accept accept = 10; + /** + * Funds receipt rejected. No settlement will occur. + */ + Reject reject = 20; + } + + /** + * Funds accepted - settlement will proceed. + * The beneficiary provider will receive a PaymentIntentUpdate notification + * with settlement details. + */ + message Accept { + } + + /** + * Funds rejected. + */ + message Reject { + Reason reason = 10; + + enum Reason { + REJECT_REASON_UNSPECIFIED = 0; + REJECT_REASON_CONFIRMATION_CODE_MISMATCH = 10; + REJECT_REASON_NO_ACTIVE_QUOTE = 20; + } + } +} diff --git a/sdk/src/main/proto/tzero/v1/payment_intent/pay_in_provider.proto b/sdk/src/main/proto/tzero/v1/payment_intent/pay_in_provider.proto new file mode 100644 index 0000000..7aff853 --- /dev/null +++ b/sdk/src/main/proto/tzero/v1/payment_intent/pay_in_provider.proto @@ -0,0 +1,120 @@ +syntax = "proto3"; + +package tzero.v1.payment_intent; + +import "tzero/v1/common/common.proto"; +import "tzero/v1/common/payment_method.proto"; +import "buf/validate/validate.proto"; +import "ivms101/v1/ivms/ivms101.proto"; + +/** + * PayInProviderService must be implemented by pay-in providers to participate + * in the Payment Intent flow. + * + * Pay-in providers are those who: + * - Receive fiat payments from end-users + * - Publish payment intent (pay-in) quotes to the network + * - Confirm when payments are received via ConfirmFundsReceived + * - Settles periodically with the beneficiary provider + * + * The network calls this service to obtain payment details that will be + * presented to end-users for making payments. + */ +service PayInProviderService { + /** + * GetPaymentDetails returns payment details for the end-user. + * + * Called by the network during CreatePaymentIntent processing. + * The provider should return payment details (bank accounts, mobile money info, etc.) + * that the end-user can use to send funds. The payment details should contain payment reference, + * so that on receiving payment from a payer, the pay-in provider can identify which payment intent this payment belongs to + */ + rpc GetPaymentDetails(GetPaymentDetailsRequest) returns (GetPaymentDetailsResponse) { + option idempotency_level = IDEMPOTENT; + }; +} + +/** + * Request for payment details. + */ +message GetPaymentDetailsRequest { + /** + * The payment intent ID this request relates to. + */ + uint64 payment_intent_id = 10 [(buf.validate.field).uint64.gt = 0]; + + /* + * This is the confirmation code to be used later with ConfirmFundsReceived endpoint to prevent accidental + * confirmation of the wrong payment intent + */ + string confirmation_code = 20; + + /** + * Payment methods being requested. + * The provider should return PaymentDetails for the methods in the request. All the payment methods will be taken + * from the quotes submitted by pay-in provider. + */ + repeated tzero.v1.common.PaymentMethodType payment_methods = 30 [(buf.validate.field).repeated.min_items = 1]; + + /** + * The currency for the pay-in. + * ISO 4217 currency code (e.g., "EUR", "GBP", "KES"). + */ + string currency = 40 [(buf.validate.field).string = { + len: 3, + pattern: "^[A-Z]{3}$" + }]; + + /** + * The amount to be paid in the specified currency. + */ + tzero.v1.common.Decimal amount = 50 [(buf.validate.field).required = true]; + + /** + * Travel rule data for this payment + */ + TravelRuleData travel_rule = 60 [(buf.validate.field).required = true]; + + message TravelRuleData { + /** + * The natural or legal person or legal arrangement who is identified + * by the beneficiary provider as the receiver of the requested payment. + */ + repeated ivms101.Person beneficiary = 20 [(buf.validate.field).repeated.min_items = 1]; + + /** + * Beneficiary provider travel rule data. + */ + ivms101.LegalPerson beneficiary_provider = 30 [(buf.validate.field).required = true]; + + /** + * Optional travel rule data of the payer + */ + optional ivms101.Person payer = 40; + } +} + +/** + * Response containing payment details for the requested methods. + */ +message GetPaymentDetailsResponse { + oneof result { + Details details = 10; + Rejection rejection = 20; + } + + message Details { + /** + * Payment details for each supported payment method. + * + * Each PaymentDetails contains the information needed for an end-user + * to send a payment (e.g., bank account details, mobile money number) and payment reference, + * which can be used by pay-in provider to identify incoming payment. + */ + repeated tzero.v1.common.PaymentDetails payment_details = 10; + } + + message Rejection { + string reason = 10; + } +}