From fca212431442d3066b185710ef352bec26ddcb5a Mon Sep 17 00:00:00 2001 From: alvseven Date: Mon, 8 Jun 2026 13:53:41 -0300 Subject: [PATCH 1/6] feat: add Customers resource, deprecate Receivers (v2.4.0) Mirrors blindpay-node v3.11.0: - New Customers/ resource (parallel to Receivers/, targets /customers/* API paths) - BankAccounts, VirtualAccounts, BlockchainWallets, OfframpWallets, CustodialWallets now hit /customers/{customer_id}/... and take customerId named arg - BlindPay::$customers accessor added - BlindPay::$receivers marked @deprecated https://www.blindpay.com/changelog/2026-06-04-customers-rename --- src/BlindPay.php | 17 + src/Resources/BankAccounts/BankAccounts.php | 68 +- .../CustodialWallets/CustodialWallets.php | 30 +- src/Resources/Customers/Customers.php | 1594 +++++++++++++++++ src/Resources/Customers/CustomersWrapper.php | 125 ++ .../VirtualAccounts/VirtualAccounts.php | 26 +- src/Resources/Wallets/BlockchainWallets.php | 36 +- src/Resources/Wallets/OfframpWallets.php | 16 +- .../BankAccounts/BankAccountsTest.php | 24 +- .../CustodialWallets/CustodialWalletsTest.php | 16 +- .../VirtualAccounts/VirtualAccountsTest.php | 4 +- .../Wallets/BlockchainWalletsTest.php | 16 +- .../Resources/Wallets/OfframpWalletsTest.php | 14 +- 13 files changed, 1861 insertions(+), 125 deletions(-) create mode 100644 src/Resources/Customers/Customers.php create mode 100644 src/Resources/Customers/CustomersWrapper.php diff --git a/src/BlindPay.php b/src/BlindPay.php index a40283a..d523422 100644 --- a/src/BlindPay.php +++ b/src/BlindPay.php @@ -10,6 +10,8 @@ use BlindPay\SDK\Resources\Available\Available; use BlindPay\SDK\Resources\BankAccounts\BankAccounts; use BlindPay\SDK\Resources\CustodialWallets\CustodialWallets; +use BlindPay\SDK\Resources\Customers\Customers; +use BlindPay\SDK\Resources\Customers\CustomersWrapper; use BlindPay\SDK\Resources\Fees\Fees; use BlindPay\SDK\Resources\Instances\Instances; use BlindPay\SDK\Resources\Instances\InstancesWrapper; @@ -59,6 +61,12 @@ class BlindPay implements ApiClientInterface public readonly PayinsWrapper $payins; + public readonly CustomersWrapper $customers; + + /** + * @deprecated 2.4.0 Use $customers instead. Will be removed in v3.0.0. + * See https://www.blindpay.com/changelog/2026-06-04-customers-rename + */ public readonly ReceiversWrapper $receivers; public readonly WalletsWrapper $wallets; @@ -105,6 +113,7 @@ public function __construct( $this->initializeInstances(); $this->initializePayins(); + $this->initializeCustomers(); $this->initializeReceivers(); $this->initializeWallets(); } @@ -133,6 +142,14 @@ private function initializePayins(): void $this->payins = new PayinsWrapper($payinsResource, $quotesResource); } + private function initializeCustomers(): void + { + $customersResource = new Customers($this->instanceId, $this); + $bankAccountsResource = new BankAccounts($this->instanceId, $this); + + $this->customers = new CustomersWrapper($customersResource, $bankAccountsResource); + } + private function initializeReceivers(): void { $receiversResource = new Receivers($this->instanceId, $this); diff --git a/src/Resources/BankAccounts/BankAccounts.php b/src/Resources/BankAccounts/BankAccounts.php index 2e821be..17c6ce7 100644 --- a/src/Resources/BankAccounts/BankAccounts.php +++ b/src/Resources/BankAccounts/BankAccounts.php @@ -182,7 +182,7 @@ public static function fromArray(array $data): self readonly class GetBankAccountInput { public function __construct( - public string $receiverId, + public string $customerId, public string $id ) {} } @@ -191,7 +191,7 @@ public function __construct( { public function __construct( public string $id, - public string $receiverId, + public string $customerId, public string $accountHolderName, public string $accountNumber, public string $routingNumber, @@ -208,7 +208,7 @@ public static function fromArray(array $data): self { return new self( id: $data['id'], - receiverId: $data['receiver_id'], + customerId: $data['customer_id'], accountHolderName: $data['account_holder_name'], accountNumber: $data['account_number'], routingNumber: $data['routing_number'], @@ -226,7 +226,7 @@ public static function fromArray(array $data): self readonly class DeleteBankAccountInput { public function __construct( - public string $receiverId, + public string $customerId, public string $id ) {} } @@ -234,7 +234,7 @@ public function __construct( readonly class CreatePixInput { public function __construct( - public string $receiverId, + public string $customerId, public string $name, public string $pixKey ) {} @@ -274,7 +274,7 @@ public static function fromArray(array $data): self readonly class CreateArgentinaTransfersInput { public function __construct( - public string $receiverId, + public string $customerId, public string $name, public string $beneficiaryName, public string $transfersAccount, @@ -322,7 +322,7 @@ public static function fromArray(array $data): self readonly class CreateSpeiInput { public function __construct( - public string $receiverId, + public string $customerId, public string $beneficiaryName, public string $name, public string $speiClabe, @@ -374,7 +374,7 @@ public static function fromArray(array $data): self readonly class CreateColombiaAchInput { public function __construct( - public string $receiverId, + public string $customerId, public string $name, public BankAccountType $accountType, public string $achCopBeneficiaryFirstName, @@ -442,7 +442,7 @@ public static function fromArray(array $data): self readonly class CreateAchInput { public function __construct( - public string $receiverId, + public string $customerId, public string $name, public AccountClass $accountClass, public string $accountNumber, @@ -563,7 +563,7 @@ public static function fromArray(array $data): self readonly class CreateWireInput { public function __construct( - public string $receiverId, + public string $customerId, public string $name, public string $accountNumber, public string $beneficiaryName, @@ -664,7 +664,7 @@ public static function fromArray(array $data): self readonly class CreateInternationalSwiftInput { public function __construct( - public string $receiverId, + public string $customerId, public string $name, public string $swiftAccountHolderName, public string $swiftAccountNumberIban, @@ -846,7 +846,7 @@ public static function fromArray(array $data): self readonly class CreateRtpInput { public function __construct( - public string $receiverId, + public string $customerId, public string $name, public string $beneficiaryName, public string $routingNumber, @@ -909,7 +909,7 @@ public function toArray(): array readonly class CreatePixSafeInput { public function __construct( - public string $receiverId, + public string $customerId, public string $name, public string $accountNumber, public BankAccountType $accountType, @@ -1008,21 +1008,21 @@ public function __construct( ) {} /* - * List bank accounts for a receiver + * List bank accounts for a customer * - * @param string $receiverId + * @param string $customerId * @return BlindPayApiResponse */ - public function list(string $receiverId): BlindPayApiResponse + public function list(string $customerId): BlindPayApiResponse { - if (empty($receiverId)) { + if (empty($customerId)) { return BlindPayApiResponse::error( - new \BlindPay\SDK\Types\ErrorResponse('Receiver ID cannot be empty') + new \BlindPay\SDK\Types\ErrorResponse('Customer ID cannot be empty') ); } $response = $this->client->get( - "instances/{$this->instanceId}/receivers/{$receiverId}/bank-accounts" + "instances/{$this->instanceId}/customers/{$customerId}/bank-accounts" ); if ($response->isSuccess() && is_array($response->data)) { @@ -1042,14 +1042,14 @@ public function list(string $receiverId): BlindPayApiResponse */ public function get(GetBankAccountInput $input): BlindPayApiResponse { - if (empty($input->receiverId) || empty($input->id)) { + if (empty($input->customerId) || empty($input->id)) { return BlindPayApiResponse::error( - new \BlindPay\SDK\Types\ErrorResponse('Receiver ID and ID cannot be empty') + new \BlindPay\SDK\Types\ErrorResponse('Customer ID and ID cannot be empty') ); } $response = $this->client->get( - "instances/{$this->instanceId}/receivers/{$input->receiverId}/bank-accounts/{$input->id}" + "instances/{$this->instanceId}/customers/{$input->customerId}/bank-accounts/{$input->id}" ); if ($response->isSuccess() && is_array($response->data)) { @@ -1069,14 +1069,14 @@ public function get(GetBankAccountInput $input): BlindPayApiResponse */ public function delete(DeleteBankAccountInput $input): BlindPayApiResponse { - if (empty($input->receiverId) || empty($input->id)) { + if (empty($input->customerId) || empty($input->id)) { return BlindPayApiResponse::error( - new \BlindPay\SDK\Types\ErrorResponse('Receiver ID and ID cannot be empty') + new \BlindPay\SDK\Types\ErrorResponse('Customer ID and ID cannot be empty') ); } return $this->client->delete( - "instances/{$this->instanceId}/receivers/{$input->receiverId}/bank-accounts/{$input->id}" + "instances/{$this->instanceId}/customers/{$input->customerId}/bank-accounts/{$input->id}" ); } @@ -1089,7 +1089,7 @@ public function delete(DeleteBankAccountInput $input): BlindPayApiResponse public function createPix(CreatePixInput $input): BlindPayApiResponse { $response = $this->client->post( - "instances/{$this->instanceId}/receivers/{$input->receiverId}/bank-accounts", + "instances/{$this->instanceId}/customers/{$input->customerId}/bank-accounts", $input->toArray() ); @@ -1111,7 +1111,7 @@ public function createPix(CreatePixInput $input): BlindPayApiResponse public function createArgentinaTransfers(CreateArgentinaTransfersInput $input): BlindPayApiResponse { $response = $this->client->post( - "instances/{$this->instanceId}/receivers/{$input->receiverId}/bank-accounts", + "instances/{$this->instanceId}/customers/{$input->customerId}/bank-accounts", $input->toArray() ); @@ -1133,7 +1133,7 @@ public function createArgentinaTransfers(CreateArgentinaTransfersInput $input): public function createSpei(CreateSpeiInput $input): BlindPayApiResponse { $response = $this->client->post( - "instances/{$this->instanceId}/receivers/{$input->receiverId}/bank-accounts", + "instances/{$this->instanceId}/customers/{$input->customerId}/bank-accounts", $input->toArray() ); @@ -1155,7 +1155,7 @@ public function createSpei(CreateSpeiInput $input): BlindPayApiResponse public function createColombiaAch(CreateColombiaAchInput $input): BlindPayApiResponse { $response = $this->client->post( - "instances/{$this->instanceId}/receivers/{$input->receiverId}/bank-accounts", + "instances/{$this->instanceId}/customers/{$input->customerId}/bank-accounts", $input->toArray() ); @@ -1177,7 +1177,7 @@ public function createColombiaAch(CreateColombiaAchInput $input): BlindPayApiRes public function createAch(CreateAchInput $input): BlindPayApiResponse { $response = $this->client->post( - "instances/{$this->instanceId}/receivers/{$input->receiverId}/bank-accounts", + "instances/{$this->instanceId}/customers/{$input->customerId}/bank-accounts", $input->toArray() ); @@ -1199,7 +1199,7 @@ public function createAch(CreateAchInput $input): BlindPayApiResponse public function createWire(CreateWireInput $input): BlindPayApiResponse { $response = $this->client->post( - "instances/{$this->instanceId}/receivers/{$input->receiverId}/bank-accounts", + "instances/{$this->instanceId}/customers/{$input->customerId}/bank-accounts", $input->toArray() ); @@ -1221,7 +1221,7 @@ public function createWire(CreateWireInput $input): BlindPayApiResponse public function createInternationalSwift(CreateInternationalSwiftInput $input): BlindPayApiResponse { $response = $this->client->post( - "instances/{$this->instanceId}/receivers/{$input->receiverId}/bank-accounts", + "instances/{$this->instanceId}/customers/{$input->customerId}/bank-accounts", $input->toArray() ); @@ -1243,7 +1243,7 @@ public function createInternationalSwift(CreateInternationalSwiftInput $input): public function createRtp(CreateRtpInput $input): BlindPayApiResponse { $response = $this->client->post( - "instances/{$this->instanceId}/receivers/{$input->receiverId}/bank-accounts", + "instances/{$this->instanceId}/customers/{$input->customerId}/bank-accounts", $input->toArray() ); @@ -1265,7 +1265,7 @@ public function createRtp(CreateRtpInput $input): BlindPayApiResponse public function createPixSafe(CreatePixSafeInput $input): BlindPayApiResponse { $response = $this->client->post( - "instances/{$this->instanceId}/receivers/{$input->receiverId}/bank-accounts", + "instances/{$this->instanceId}/customers/{$input->customerId}/bank-accounts", $input->toArray() ); diff --git a/src/Resources/CustodialWallets/CustodialWallets.php b/src/Resources/CustodialWallets/CustodialWallets.php index f21a67e..5087371 100644 --- a/src/Resources/CustodialWallets/CustodialWallets.php +++ b/src/Resources/CustodialWallets/CustodialWallets.php @@ -13,7 +13,7 @@ { public function __construct( public string $id, - public string $receiverId, + public string $customerId, public string $instanceId, public Network $network, public string $address, @@ -24,7 +24,7 @@ public static function fromArray(array $data): self { return new self( id: $data['id'], - receiverId: $data['receiver_id'], + customerId: $data['customer_id'], instanceId: $data['instance_id'], network: Network::from($data['network']), address: $data['address'], @@ -72,7 +72,7 @@ public static function fromArray(array $data): self readonly class CreateCustodialWalletInput { public function __construct( - public string $receiverId, + public string $customerId, public Network $network ) {} @@ -87,7 +87,7 @@ public function toArray(): array readonly class GetCustodialWalletInput { public function __construct( - public string $receiverId, + public string $customerId, public string $id ) {} } @@ -95,7 +95,7 @@ public function __construct( readonly class DeleteCustodialWalletInput { public function __construct( - public string $receiverId, + public string $customerId, public string $id ) {} } @@ -108,21 +108,21 @@ public function __construct( ) {} /* - * List custodial wallets for a receiver + * List custodial wallets for a customer * - * @param string $receiverId + * @param string $customerId * @return BlindPayApiResponse */ - public function list(string $receiverId): BlindPayApiResponse + public function list(string $customerId): BlindPayApiResponse { - if (empty($receiverId)) { + if (empty($customerId)) { return BlindPayApiResponse::error( - new \BlindPay\SDK\Types\ErrorResponse('Receiver ID cannot be empty') + new \BlindPay\SDK\Types\ErrorResponse('Customer ID cannot be empty') ); } $response = $this->client->get( - "instances/{$this->instanceId}/receivers/{$receiverId}/wallets" + "instances/{$this->instanceId}/customers/{$customerId}/wallets" ); if ($response->isSuccess() && is_array($response->data)) { @@ -152,7 +152,7 @@ public function get(GetCustodialWalletInput $input): BlindPayApiResponse } $response = $this->client->get( - "instances/{$this->instanceId}/receivers/{$input->receiverId}/wallets/{$input->id}" + "instances/{$this->instanceId}/customers/{$input->customerId}/wallets/{$input->id}" ); if ($response->isSuccess() && is_array($response->data)) { @@ -173,7 +173,7 @@ public function get(GetCustodialWalletInput $input): BlindPayApiResponse public function create(CreateCustodialWalletInput $input): BlindPayApiResponse { $response = $this->client->post( - "instances/{$this->instanceId}/receivers/{$input->receiverId}/wallets", + "instances/{$this->instanceId}/customers/{$input->customerId}/wallets", $input->toArray() ); @@ -201,7 +201,7 @@ public function getBalance(GetCustodialWalletInput $input): BlindPayApiResponse } $response = $this->client->get( - "instances/{$this->instanceId}/receivers/{$input->receiverId}/wallets/{$input->id}/balance" + "instances/{$this->instanceId}/customers/{$input->customerId}/wallets/{$input->id}/balance" ); if ($response->isSuccess() && is_array($response->data)) { @@ -228,7 +228,7 @@ public function delete(DeleteCustodialWalletInput $input): BlindPayApiResponse } return $this->client->delete( - "instances/{$this->instanceId}/receivers/{$input->receiverId}/wallets/{$input->id}" + "instances/{$this->instanceId}/customers/{$input->customerId}/wallets/{$input->id}" ); } } diff --git a/src/Resources/Customers/Customers.php b/src/Resources/Customers/Customers.php new file mode 100644 index 0000000..a2cc579 --- /dev/null +++ b/src/Resources/Customers/Customers.php @@ -0,0 +1,1594 @@ + $this->id, + 'role' => $this->role->value, + 'first_name' => $this->firstName, + 'last_name' => $this->lastName, + 'date_of_birth' => $this->dateOfBirth, + 'tax_id' => $this->taxId, + 'address_line_1' => $this->addressLine1, + 'address_line_2' => $this->addressLine2, + 'city' => $this->city, + 'state_province_region' => $this->stateProvinceRegion, + 'country' => $this->country->value, + 'postal_code' => $this->postalCode, + 'id_doc_country' => $this->idDocCountry->value, + 'id_doc_type' => $this->idDocType->value, + 'id_doc_front_file' => $this->idDocFrontFile, + 'id_doc_back_file' => $this->idDocBackFile, + 'proof_of_address_doc_type' => $this->proofOfAddressDocType->value, + 'proof_of_address_doc_file' => $this->proofOfAddressDocFile, + ]; + + if ($this->ownershipPercentage !== null) { + $data['ownership_percentage'] = $this->ownershipPercentage; + } + + if ($this->title !== null) { + $data['title'] = $this->title; + } + + if ($this->taxType !== null) { + $data['tax_type'] = $this->taxType->value; + } + + return $data; + } +} + +readonly class KycWarning +{ + public function __construct( + public ?string $code, + public ?string $message, + public ?string $resolutionStatus, + public ?string $warningId + ) {} + + public static function fromArray(array $data): self + { + return new self( + code: $data['code'] ?? null, + message: $data['message'] ?? null, + resolutionStatus: $data['resolution_status'] ?? null, + warningId: $data['warning_id'] ?? null + ); + } +} + +readonly class FraudWarning +{ + public function __construct( + public ?string $id, + public ?string $name, + public ?string $operation, + public ?float $score + ) {} + + public static function fromArray(array $data): self + { + return new self( + id: $data['id'] ?? null, + name: $data['name'] ?? null, + operation: $data['operation'] ?? null, + score: isset($data['score']) ? (float) $data['score'] : null + ); + } +} + +readonly class AmlHits +{ + public function __construct( + public bool $hasSanctionMatch, + public bool $hasPepMatch, + public bool $hasWatchlistMatch, + public bool $hasCrimelistMatch, + public bool $hasAdversemediaMatch + ) {} + + public static function fromArray(array $data): self + { + return new self( + hasSanctionMatch: $data['has_sanction_match'] ?? false, + hasPepMatch: $data['has_pep_match'] ?? false, + hasWatchlistMatch: $data['has_watchlist_match'] ?? false, + hasCrimelistMatch: $data['has_crimelist_match'] ?? false, + hasAdversemediaMatch: $data['has_adversemedia_match'] ?? false + ); + } +} + +readonly class Limit +{ + public function __construct( + public float $perTransaction, + public float $daily, + public float $monthly + ) {} + + public static function fromArray(array $data): self + { + return new self( + perTransaction: (float) $data['per_transaction'], + daily: (float) $data['daily'], + monthly: (float) $data['monthly'] + ); + } +} + +abstract readonly class BaseCustomer +{ + /** + * @param KycWarning[]|null $kycWarnings + * @param FraudWarning[]|null $fraudWarnings + */ + public function __construct( + public string $id, + public AccountClass $type, + public KycType $kycType, + public string $kycStatus, + public ?array $kycWarnings, + public string $email, + public string $taxId, + public string $addressLine1, + public ?string $addressLine2, + public string $city, + public string $stateProvinceRegion, + public Country $country, + public string $postalCode, + public ?string $ipAddress, + public ?string $imageUrl, + public ?string $phoneNumber, + public ProofOfAddressDocType $proofOfAddressDocType, + public string $proofOfAddressDocFile, + public string $instanceId, + public ?string $tosId, + public DateTimeImmutable $createdAt, + public DateTimeImmutable $updatedAt, + public Limit $limit, + public ?array $fraudWarnings = null, + public ?bool $isFbo = null, + public ?AccountPurpose $accountPurpose = null, + public ?string $accountPurposeOther = null, + public ?AmlStatus $amlStatus = null, + public ?AmlHits $amlHits = null, + public ?bool $isTosAccepted = null + ) {} +} + +readonly class IndividualWithStandardKYC extends BaseCustomer +{ + public function __construct( + string $id, + string $kycStatus, + ?array $kycWarnings, + string $email, + string $taxId, + string $addressLine1, + ?string $addressLine2, + string $city, + string $stateProvinceRegion, + Country $country, + string $postalCode, + ?string $ipAddress, + ?string $imageUrl, + ?string $phoneNumber, + ProofOfAddressDocType $proofOfAddressDocType, + string $proofOfAddressDocFile, + string $instanceId, + ?string $tosId, + DateTimeImmutable $createdAt, + DateTimeImmutable $updatedAt, + Limit $limit, + public string $firstName, + public string $lastName, + public string $dateOfBirth, + public Country $idDocCountry, + public IdentificationDocument $idDocType, + public string $idDocFrontFile, + public string $idDocBackFile, + public string $aipriseValidationKey, + ?array $fraudWarnings = null, + ?bool $isFbo = null, + ?AccountPurpose $accountPurpose = null, + ?string $accountPurposeOther = null, + ?AmlStatus $amlStatus = null, + ?AmlHits $amlHits = null, + ?bool $isTosAccepted = null, + public ?string $externalId = null, + public ?string $selfieFile = null, + public ?string $occupation = null + ) { + parent::__construct( + $id, + AccountClass::INDIVIDUAL, + KycType::STANDARD, + $kycStatus, + $kycWarnings, + $email, + $taxId, + $addressLine1, + $addressLine2, + $city, + $stateProvinceRegion, + $country, + $postalCode, + $ipAddress, + $imageUrl, + $phoneNumber, + $proofOfAddressDocType, + $proofOfAddressDocFile, + $instanceId, + $tosId, + $createdAt, + $updatedAt, + $limit, + $fraudWarnings, + $isFbo, + $accountPurpose, + $accountPurposeOther, + $amlStatus, + $amlHits, + $isTosAccepted + ); + } + + public static function fromArray(array $data): self + { + return new self( + id: $data['id'], + kycStatus: $data['kyc_status'], + kycWarnings: isset($data['kyc_warnings']) ? array_map( + fn ($w) => KycWarning::fromArray($w), + $data['kyc_warnings'] + ) : null, + email: $data['email'], + taxId: $data['tax_id'], + addressLine1: $data['address_line_1'], + addressLine2: $data['address_line_2'] ?? null, + city: $data['city'], + stateProvinceRegion: $data['state_province_region'], + country: Country::from($data['country']), + postalCode: $data['postal_code'], + ipAddress: $data['ip_address'] ?? null, + imageUrl: $data['image_url'] ?? null, + phoneNumber: $data['phone_number'] ?? null, + proofOfAddressDocType: ProofOfAddressDocType::from($data['proof_of_address_doc_type']), + proofOfAddressDocFile: $data['proof_of_address_doc_file'], + instanceId: $data['instance_id'], + tosId: $data['tos_id'] ?? null, + createdAt: new DateTimeImmutable($data['created_at']), + updatedAt: new DateTimeImmutable($data['updated_at']), + limit: Limit::fromArray($data['limit']), + firstName: $data['first_name'], + lastName: $data['last_name'], + dateOfBirth: $data['date_of_birth'], + idDocCountry: Country::from($data['id_doc_country']), + idDocType: IdentificationDocument::from($data['id_doc_type']), + idDocFrontFile: $data['id_doc_front_file'], + idDocBackFile: $data['id_doc_back_file'], + aipriseValidationKey: $data['aiprise_validation_key'], + fraudWarnings: isset($data['fraud_warnings']) ? array_map( + fn ($w) => FraudWarning::fromArray($w), + $data['fraud_warnings'] + ) : null, + isFbo: $data['is_fbo'] ?? null, + accountPurpose: isset($data['account_purpose']) ? AccountPurpose::from($data['account_purpose']) : null, + accountPurposeOther: $data['account_purpose_other'] ?? null, + amlStatus: isset($data['aml_status']) ? AmlStatus::from($data['aml_status']) : null, + amlHits: isset($data['aml_hits']) ? AmlHits::fromArray($data['aml_hits']) : null, + isTosAccepted: $data['is_tos_accepted'] ?? null, + externalId: $data['external_id'] ?? null, + selfieFile: $data['selfie_file'] ?? null, + occupation: $data['occupation'] ?? null + ); + } +} + +readonly class IndividualWithEnhancedKYC extends BaseCustomer +{ + public function __construct( + string $id, + string $kycStatus, + ?array $kycWarnings, + string $email, + string $taxId, + string $addressLine1, + ?string $addressLine2, + string $city, + string $stateProvinceRegion, + Country $country, + string $postalCode, + ?string $ipAddress, + ?string $imageUrl, + ?string $phoneNumber, + ProofOfAddressDocType $proofOfAddressDocType, + string $proofOfAddressDocFile, + string $instanceId, + ?string $tosId, + DateTimeImmutable $createdAt, + DateTimeImmutable $updatedAt, + Limit $limit, + public string $firstName, + public string $lastName, + public string $dateOfBirth, + public Country $idDocCountry, + public IdentificationDocument $idDocType, + public string $idDocFrontFile, + public ?string $idDocBackFile, + public string $aipriseValidationKey, + public string $sourceOfFundsDocType, + public string $sourceOfFundsDocFile, + public string $individualHoldingDocFrontFile, + public PurposeOfTransactions $purposeOfTransactions, + public ?string $purposeOfTransactionsExplanation, + ?array $fraudWarnings = null, + ?bool $isFbo = null, + ?AccountPurpose $accountPurpose = null, + ?string $accountPurposeOther = null, + ?AmlStatus $amlStatus = null, + ?AmlHits $amlHits = null, + ?bool $isTosAccepted = null, + public ?string $externalId = null, + public ?string $selfieFile = null, + public ?string $occupation = null + ) { + parent::__construct( + $id, + AccountClass::INDIVIDUAL, + KycType::ENHANCED, + $kycStatus, + $kycWarnings, + $email, + $taxId, + $addressLine1, + $addressLine2, + $city, + $stateProvinceRegion, + $country, + $postalCode, + $ipAddress, + $imageUrl, + $phoneNumber, + $proofOfAddressDocType, + $proofOfAddressDocFile, + $instanceId, + $tosId, + $createdAt, + $updatedAt, + $limit, + $fraudWarnings, + $isFbo, + $accountPurpose, + $accountPurposeOther, + $amlStatus, + $amlHits, + $isTosAccepted + ); + } + + public static function fromArray(array $data): self + { + return new self( + id: $data['id'], + kycStatus: $data['kyc_status'], + kycWarnings: isset($data['kyc_warnings']) ? array_map( + fn ($w) => KycWarning::fromArray($w), + $data['kyc_warnings'] + ) : null, + email: $data['email'], + taxId: $data['tax_id'], + addressLine1: $data['address_line_1'], + addressLine2: $data['address_line_2'] ?? null, + city: $data['city'], + stateProvinceRegion: $data['state_province_region'], + country: Country::from($data['country']), + postalCode: $data['postal_code'], + ipAddress: $data['ip_address'] ?? null, + imageUrl: $data['image_url'] ?? null, + phoneNumber: $data['phone_number'] ?? null, + proofOfAddressDocType: ProofOfAddressDocType::from($data['proof_of_address_doc_type']), + proofOfAddressDocFile: $data['proof_of_address_doc_file'], + instanceId: $data['instance_id'], + tosId: $data['tos_id'] ?? null, + createdAt: new DateTimeImmutable($data['created_at']), + updatedAt: new DateTimeImmutable($data['updated_at']), + limit: Limit::fromArray($data['limit']), + firstName: $data['first_name'], + lastName: $data['last_name'], + dateOfBirth: $data['date_of_birth'], + idDocCountry: Country::from($data['id_doc_country']), + idDocType: IdentificationDocument::from($data['id_doc_type']), + idDocFrontFile: $data['id_doc_front_file'], + idDocBackFile: $data['id_doc_back_file'] ?? null, + aipriseValidationKey: $data['aiprise_validation_key'], + sourceOfFundsDocType: $data['source_of_funds_doc_type'], + sourceOfFundsDocFile: $data['source_of_funds_doc_file'], + individualHoldingDocFrontFile: $data['individual_holding_doc_front_file'], + purposeOfTransactions: PurposeOfTransactions::from($data['purpose_of_transactions']), + purposeOfTransactionsExplanation: $data['purpose_of_transactions_explanation'] ?? null, + fraudWarnings: isset($data['fraud_warnings']) ? array_map( + fn ($w) => FraudWarning::fromArray($w), + $data['fraud_warnings'] + ) : null, + isFbo: $data['is_fbo'] ?? null, + accountPurpose: isset($data['account_purpose']) ? AccountPurpose::from($data['account_purpose']) : null, + accountPurposeOther: $data['account_purpose_other'] ?? null, + amlStatus: isset($data['aml_status']) ? AmlStatus::from($data['aml_status']) : null, + amlHits: isset($data['aml_hits']) ? AmlHits::fromArray($data['aml_hits']) : null, + isTosAccepted: $data['is_tos_accepted'] ?? null, + externalId: $data['external_id'] ?? null, + selfieFile: $data['selfie_file'] ?? null, + occupation: $data['occupation'] ?? null + ); + } +} + +readonly class BusinessWithStandardKYB extends BaseCustomer +{ + /** + * @param Owner[] $owners + */ + public function __construct( + string $id, + string $kycStatus, + ?array $kycWarnings, + string $email, + string $taxId, + string $addressLine1, + ?string $addressLine2, + string $city, + string $stateProvinceRegion, + Country $country, + string $postalCode, + ?string $ipAddress, + ?string $imageUrl, + ?string $phoneNumber, + ProofOfAddressDocType $proofOfAddressDocType, + string $proofOfAddressDocFile, + string $instanceId, + ?string $tosId, + DateTimeImmutable $createdAt, + DateTimeImmutable $updatedAt, + Limit $limit, + public string $legalName, + public ?string $alternateName, + public string $formationDate, + public ?string $website, + public array $owners, + public string $incorporationDocFile, + public string $proofOfOwnershipDocFile, + public ?string $externalId, + public string $aipriseValidationKey, + ?array $fraudWarnings = null, + ?bool $isFbo = null, + ?AccountPurpose $accountPurpose = null, + ?string $accountPurposeOther = null, + ?AmlStatus $amlStatus = null, + ?AmlHits $amlHits = null, + ?bool $isTosAccepted = null, + public ?BusinessType $businessType = null, + public ?string $businessDescription = null, + public ?BusinessIndustry $businessIndustry = null, + public ?EstimatedAnnualRevenue $estimatedAnnualRevenue = null, + public ?SourceOfWealth $sourceOfWealth = null, + public ?bool $publiclyTraded = null + ) { + parent::__construct( + $id, + AccountClass::BUSINESS, + KycType::STANDARD, + $kycStatus, + $kycWarnings, + $email, + $taxId, + $addressLine1, + $addressLine2, + $city, + $stateProvinceRegion, + $country, + $postalCode, + $ipAddress, + $imageUrl, + $phoneNumber, + $proofOfAddressDocType, + $proofOfAddressDocFile, + $instanceId, + $tosId, + $createdAt, + $updatedAt, + $limit, + $fraudWarnings, + $isFbo, + $accountPurpose, + $accountPurposeOther, + $amlStatus, + $amlHits, + $isTosAccepted + ); + } + + public static function fromArray(array $data): self + { + return new self( + id: $data['id'], + kycStatus: $data['kyc_status'], + kycWarnings: isset($data['kyc_warnings']) ? array_map( + fn ($w) => KycWarning::fromArray($w), + $data['kyc_warnings'] + ) : null, + email: $data['email'], + taxId: $data['tax_id'], + addressLine1: $data['address_line_1'], + addressLine2: $data['address_line_2'] ?? null, + city: $data['city'], + stateProvinceRegion: $data['state_province_region'], + country: Country::from($data['country']), + postalCode: $data['postal_code'], + ipAddress: $data['ip_address'] ?? null, + imageUrl: $data['image_url'] ?? null, + phoneNumber: $data['phone_number'] ?? null, + proofOfAddressDocType: ProofOfAddressDocType::from($data['proof_of_address_doc_type']), + proofOfAddressDocFile: $data['proof_of_address_doc_file'], + instanceId: $data['instance_id'], + tosId: $data['tos_id'] ?? null, + createdAt: new DateTimeImmutable($data['created_at']), + updatedAt: new DateTimeImmutable($data['updated_at']), + limit: Limit::fromArray($data['limit']), + legalName: $data['legal_name'], + alternateName: $data['alternate_name'] ?? null, + formationDate: $data['formation_date'], + website: $data['website'] ?? null, + owners: array_map(fn ($o) => Owner::fromArray($o), $data['owners']), + incorporationDocFile: $data['incorporation_doc_file'], + proofOfOwnershipDocFile: $data['proof_of_ownership_doc_file'], + externalId: $data['external_id'] ?? null, + aipriseValidationKey: $data['aiprise_validation_key'], + fraudWarnings: isset($data['fraud_warnings']) ? array_map( + fn ($w) => FraudWarning::fromArray($w), + $data['fraud_warnings'] + ) : null, + isFbo: $data['is_fbo'] ?? null, + accountPurpose: isset($data['account_purpose']) ? AccountPurpose::from($data['account_purpose']) : null, + accountPurposeOther: $data['account_purpose_other'] ?? null, + amlStatus: isset($data['aml_status']) ? AmlStatus::from($data['aml_status']) : null, + amlHits: isset($data['aml_hits']) ? AmlHits::fromArray($data['aml_hits']) : null, + isTosAccepted: $data['is_tos_accepted'] ?? null, + businessType: isset($data['business_type']) ? BusinessType::from($data['business_type']) : null, + businessDescription: $data['business_description'] ?? null, + businessIndustry: isset($data['business_industry']) ? BusinessIndustry::from($data['business_industry']) : null, + estimatedAnnualRevenue: isset($data['estimated_annual_revenue']) ? EstimatedAnnualRevenue::from($data['estimated_annual_revenue']) : null, + sourceOfWealth: isset($data['source_of_wealth']) ? SourceOfWealth::from($data['source_of_wealth']) : null, + publiclyTraded: $data['publicly_traded'] ?? null + ); + } +} + +readonly class CreateIndividualWithStandardKYCInput +{ + public function __construct( + public string $addressLine1, + public string $city, + public Country $country, + public string $dateOfBirth, + public string $email, + public string $firstName, + public ?string $phoneNumber, + public Country $idDocCountry, + public string $idDocFrontFile, + public IdentificationDocument $idDocType, + public ?string $idDocBackFile, + public string $lastName, + public string $postalCode, + public string $proofOfAddressDocFile, + public ProofOfAddressDocType $proofOfAddressDocType, + public string $stateProvinceRegion, + public string $taxId, + public string $tosId, + public ?string $addressLine2 = null, + public ?string $externalId = null, + public ?AccountPurpose $accountPurpose = null, + public ?string $accountPurposeOther = null, + public ?string $selfieFile = null, + public ?string $occupation = null, + public ?string $ipAddress = null, + public ?string $imageUrl = null + ) {} + + public function toArray(): array + { + $data = [ + 'kyc_type' => 'standard', + 'type' => 'individual', + 'address_line_1' => $this->addressLine1, + 'city' => $this->city, + 'country' => $this->country->value, + 'date_of_birth' => $this->dateOfBirth, + 'email' => $this->email, + 'first_name' => $this->firstName, + 'phone_number' => $this->phoneNumber, + 'id_doc_country' => $this->idDocCountry->value, + 'id_doc_front_file' => $this->idDocFrontFile, + 'id_doc_type' => $this->idDocType->value, + 'id_doc_back_file' => $this->idDocBackFile, + 'last_name' => $this->lastName, + 'postal_code' => $this->postalCode, + 'proof_of_address_doc_file' => $this->proofOfAddressDocFile, + 'proof_of_address_doc_type' => $this->proofOfAddressDocType->value, + 'state_province_region' => $this->stateProvinceRegion, + 'tax_id' => $this->taxId, + 'tos_id' => $this->tosId, + ]; + + if ($this->addressLine2 !== null) { + $data['address_line_2'] = $this->addressLine2; + } + + if ($this->externalId !== null) { + $data['external_id'] = $this->externalId; + } + + if ($this->accountPurpose !== null) { + $data['account_purpose'] = $this->accountPurpose->value; + } + + if ($this->accountPurposeOther !== null) { + $data['account_purpose_other'] = $this->accountPurposeOther; + } + + if ($this->selfieFile !== null) { + $data['selfie_file'] = $this->selfieFile; + } + + if ($this->occupation !== null) { + $data['occupation'] = $this->occupation; + } + + if ($this->ipAddress !== null) { + $data['ip_address'] = $this->ipAddress; + } + + if ($this->imageUrl !== null) { + $data['image_url'] = $this->imageUrl; + } + + return $data; + } +} + +readonly class CreateIndividualWithEnhancedKYCInput +{ + public function __construct( + public string $addressLine1, + public string $city, + public Country $country, + public string $dateOfBirth, + public string $email, + public string $firstName, + public Country $idDocCountry, + public string $idDocFrontFile, + public IdentificationDocument $idDocType, + public ?string $idDocBackFile, + public string $individualHoldingDocFrontFile, + public string $lastName, + public string $postalCode, + public ?string $phoneNumber, + public string $proofOfAddressDocFile, + public ProofOfAddressDocType $proofOfAddressDocType, + public PurposeOfTransactions $purposeOfTransactions, + public string $sourceOfFundsDocFile, + public SourceOfFundsDocType $sourceOfFundsDocType, + public ?string $purposeOfTransactionsExplanation, + public string $stateProvinceRegion, + public string $taxId, + public string $tosId, + public ?string $addressLine2 = null, + public ?string $externalId = null, + public ?AccountPurpose $accountPurpose = null, + public ?string $accountPurposeOther = null, + public ?string $selfieFile = null, + public ?string $occupation = null, + public ?string $ipAddress = null, + public ?string $imageUrl = null + ) {} + + public function toArray(): array + { + $data = [ + 'kyc_type' => 'enhanced', + 'type' => 'individual', + 'address_line_1' => $this->addressLine1, + 'city' => $this->city, + 'country' => $this->country->value, + 'date_of_birth' => $this->dateOfBirth, + 'email' => $this->email, + 'first_name' => $this->firstName, + 'id_doc_country' => $this->idDocCountry->value, + 'id_doc_front_file' => $this->idDocFrontFile, + 'id_doc_type' => $this->idDocType->value, + 'id_doc_back_file' => $this->idDocBackFile, + 'individual_holding_doc_front_file' => $this->individualHoldingDocFrontFile, + 'last_name' => $this->lastName, + 'postal_code' => $this->postalCode, + 'phone_number' => $this->phoneNumber, + 'proof_of_address_doc_file' => $this->proofOfAddressDocFile, + 'proof_of_address_doc_type' => $this->proofOfAddressDocType->value, + 'purpose_of_transactions' => $this->purposeOfTransactions->value, + 'source_of_funds_doc_file' => $this->sourceOfFundsDocFile, + 'source_of_funds_doc_type' => $this->sourceOfFundsDocType->value, + 'purpose_of_transactions_explanation' => $this->purposeOfTransactionsExplanation, + 'state_province_region' => $this->stateProvinceRegion, + 'tax_id' => $this->taxId, + 'tos_id' => $this->tosId, + ]; + + if ($this->addressLine2 !== null) { + $data['address_line_2'] = $this->addressLine2; + } + + if ($this->externalId !== null) { + $data['external_id'] = $this->externalId; + } + + if ($this->accountPurpose !== null) { + $data['account_purpose'] = $this->accountPurpose->value; + } + + if ($this->accountPurposeOther !== null) { + $data['account_purpose_other'] = $this->accountPurposeOther; + } + + if ($this->selfieFile !== null) { + $data['selfie_file'] = $this->selfieFile; + } + + if ($this->occupation !== null) { + $data['occupation'] = $this->occupation; + } + + if ($this->ipAddress !== null) { + $data['ip_address'] = $this->ipAddress; + } + + if ($this->imageUrl !== null) { + $data['image_url'] = $this->imageUrl; + } + + return $data; + } +} + +readonly class CreateBusinessWithStandardKYBInput +{ + public function __construct( + public string $addressLine1, + public string $city, + public Country $country, + public string $email, + public string $formationDate, + public string $incorporationDocFile, + public string $legalName, + public array $owners, + public string $postalCode, + public string $proofOfAddressDocFile, + public ProofOfAddressDocType $proofOfAddressDocType, + public string $proofOfOwnershipDocFile, + public string $stateProvinceRegion, + public string $taxId, + public string $tosId, + public ?string $website, + public ?string $addressLine2 = null, + public ?string $alternateName = null, + public ?string $externalId = null, + public ?AccountPurpose $accountPurpose = null, + public ?string $accountPurposeOther = null, + public ?BusinessType $businessType = null, + public ?string $businessDescription = null, + public ?BusinessIndustry $businessIndustry = null, + public ?EstimatedAnnualRevenue $estimatedAnnualRevenue = null, + public ?SourceOfWealth $sourceOfWealth = null, + public ?bool $publiclyTraded = null, + public ?string $ipAddress = null, + public ?string $imageUrl = null, + public ?string $phoneNumber = null + ) {} + + public function toArray(): array + { + $data = [ + 'kyc_type' => 'standard', + 'type' => 'business', + 'address_line_1' => $this->addressLine1, + 'city' => $this->city, + 'country' => $this->country->value, + 'email' => $this->email, + 'formation_date' => $this->formationDate, + 'incorporation_doc_file' => $this->incorporationDocFile, + 'legal_name' => $this->legalName, + 'owners' => array_map(fn ($o) => $o->toArray(), $this->owners), + 'postal_code' => $this->postalCode, + 'proof_of_address_doc_file' => $this->proofOfAddressDocFile, + 'proof_of_address_doc_type' => $this->proofOfAddressDocType->value, + 'proof_of_ownership_doc_file' => $this->proofOfOwnershipDocFile, + 'state_province_region' => $this->stateProvinceRegion, + 'tax_id' => $this->taxId, + 'tos_id' => $this->tosId, + 'website' => $this->website, + ]; + + if ($this->addressLine2 !== null) { + $data['address_line_2'] = $this->addressLine2; + } + + if ($this->alternateName !== null) { + $data['alternate_name'] = $this->alternateName; + } + + if ($this->externalId !== null) { + $data['external_id'] = $this->externalId; + } + + if ($this->accountPurpose !== null) { + $data['account_purpose'] = $this->accountPurpose->value; + } + + if ($this->accountPurposeOther !== null) { + $data['account_purpose_other'] = $this->accountPurposeOther; + } + + if ($this->businessType !== null) { + $data['business_type'] = $this->businessType->value; + } + + if ($this->businessDescription !== null) { + $data['business_description'] = $this->businessDescription; + } + + if ($this->businessIndustry !== null) { + $data['business_industry'] = $this->businessIndustry->value; + } + + if ($this->estimatedAnnualRevenue !== null) { + $data['estimated_annual_revenue'] = $this->estimatedAnnualRevenue->value; + } + + if ($this->sourceOfWealth !== null) { + $data['source_of_wealth'] = $this->sourceOfWealth->value; + } + + if ($this->publiclyTraded !== null) { + $data['publicly_traded'] = $this->publiclyTraded; + } + + if ($this->ipAddress !== null) { + $data['ip_address'] = $this->ipAddress; + } + + if ($this->imageUrl !== null) { + $data['image_url'] = $this->imageUrl; + } + + if ($this->phoneNumber !== null) { + $data['phone_number'] = $this->phoneNumber; + } + + return $data; + } +} + +readonly class CreateCustomerResponse +{ + public function __construct( + public string $id + ) {} + + public static function fromArray(array $data): self + { + return new self( + id: $data['id'] + ); + } +} + +readonly class UpdateCustomerInput +{ + public function __construct( + public string $customerId, + public ?string $email = null, + public ?string $taxId = null, + public ?string $addressLine1 = null, + public ?string $addressLine2 = null, + public ?string $city = null, + public ?string $stateProvinceRegion = null, + public ?Country $country = null, + public ?string $postalCode = null, + public ?string $ipAddress = null, + public ?string $imageUrl = null, + public ?string $phoneNumber = null, + public ?ProofOfAddressDocType $proofOfAddressDocType = null, + public ?string $proofOfAddressDocFile = null, + public ?string $firstName = null, + public ?string $lastName = null, + public ?string $dateOfBirth = null, + public ?Country $idDocCountry = null, + public ?IdentificationDocument $idDocType = null, + public ?string $idDocFrontFile = null, + public ?string $idDocBackFile = null, + public ?string $legalName = null, + public ?string $alternateName = null, + public ?string $formationDate = null, + public ?string $website = null, + public ?array $owners = null, + public ?string $incorporationDocFile = null, + public ?string $proofOfOwnershipDocFile = null, + public ?SourceOfFundsDocType $sourceOfFundsDocType = null, + public ?string $sourceOfFundsDocFile = null, + public ?string $individualHoldingDocFrontFile = null, + public ?PurposeOfTransactions $purposeOfTransactions = null, + public ?string $purposeOfTransactionsExplanation = null, + public ?string $externalId = null, + public ?string $tosId = null, + public ?AccountPurpose $accountPurpose = null, + public ?string $accountPurposeOther = null, + public ?BusinessType $businessType = null, + public ?string $businessDescription = null, + public ?BusinessIndustry $businessIndustry = null, + public ?EstimatedAnnualRevenue $estimatedAnnualRevenue = null, + public ?SourceOfWealth $sourceOfWealth = null, + public ?bool $publiclyTraded = null, + public ?string $selfieFile = null, + public ?string $occupation = null + ) {} + + public function toArray(): array + { + return array_filter([ + 'email' => $this->email, + 'tax_id' => $this->taxId, + 'address_line_1' => $this->addressLine1, + 'address_line_2' => $this->addressLine2, + 'city' => $this->city, + 'state_province_region' => $this->stateProvinceRegion, + 'country' => $this->country?->value, + 'postal_code' => $this->postalCode, + 'ip_address' => $this->ipAddress, + 'image_url' => $this->imageUrl, + 'phone_number' => $this->phoneNumber, + 'proof_of_address_doc_type' => $this->proofOfAddressDocType?->value, + 'proof_of_address_doc_file' => $this->proofOfAddressDocFile, + 'first_name' => $this->firstName, + 'last_name' => $this->lastName, + 'date_of_birth' => $this->dateOfBirth, + 'id_doc_country' => $this->idDocCountry?->value, + 'id_doc_type' => $this->idDocType?->value, + 'id_doc_front_file' => $this->idDocFrontFile, + 'id_doc_back_file' => $this->idDocBackFile, + 'legal_name' => $this->legalName, + 'alternate_name' => $this->alternateName, + 'formation_date' => $this->formationDate, + 'website' => $this->website, + 'owners' => $this->owners, + 'incorporation_doc_file' => $this->incorporationDocFile, + 'proof_of_ownership_doc_file' => $this->proofOfOwnershipDocFile, + 'source_of_funds_doc_type' => $this->sourceOfFundsDocType?->value, + 'source_of_funds_doc_file' => $this->sourceOfFundsDocFile, + 'individual_holding_doc_front_file' => $this->individualHoldingDocFrontFile, + 'purpose_of_transactions' => $this->purposeOfTransactions?->value, + 'purpose_of_transactions_explanation' => $this->purposeOfTransactionsExplanation, + 'external_id' => $this->externalId, + 'tos_id' => $this->tosId, + 'account_purpose' => $this->accountPurpose?->value, + 'account_purpose_other' => $this->accountPurposeOther, + 'business_type' => $this->businessType?->value, + 'business_description' => $this->businessDescription, + 'business_industry' => $this->businessIndustry?->value, + 'estimated_annual_revenue' => $this->estimatedAnnualRevenue?->value, + 'source_of_wealth' => $this->sourceOfWealth?->value, + 'publicly_traded' => $this->publiclyTraded, + 'selfie_file' => $this->selfieFile, + 'occupation' => $this->occupation, + ], fn ($value) => $value !== null); + } +} + +/* + * Get customer limits response + */ +readonly class GetCustomerLimitsResponse +{ + public function __construct( + public array $limits + ) {} + + public static function fromArray(array $data): self + { + return new self( + limits: $data['limits'] + ); + } +} + +readonly class LimitIncreaseRequest +{ + public function __construct( + public string $id, + public string $customerId, + public LimitIncreaseRequestStatus $status, + public float $daily, + public float $monthly, + public float $perTransaction, + public string $supportingDocumentFile, + public LimitIncreaseRequestSupportingDocumentType $supportingDocumentType, + public DateTimeImmutable $createdAt, + public DateTimeImmutable $updatedAt, + public ?int $approvedPerTransaction = null, + public ?int $approvedDaily = null, + public ?int $approvedMonthly = null + ) {} + + public static function fromArray(array $data): self + { + return new self( + id: $data['id'], + customerId: $data['customer_id'], + status: LimitIncreaseRequestStatus::from($data['status']), + daily: (float) $data['daily'], + monthly: (float) $data['monthly'], + perTransaction: (float) $data['per_transaction'], + supportingDocumentFile: $data['supporting_document_file'], + supportingDocumentType: LimitIncreaseRequestSupportingDocumentType::from($data['supporting_document_type']), + createdAt: new DateTimeImmutable($data['created_at']), + updatedAt: new DateTimeImmutable($data['updated_at']), + approvedPerTransaction: isset($data['approved_per_transaction']) ? (int) $data['approved_per_transaction'] : null, + approvedDaily: isset($data['approved_daily']) ? (int) $data['approved_daily'] : null, + approvedMonthly: isset($data['approved_monthly']) ? (int) $data['approved_monthly'] : null + ); + } +} + +readonly class RequestLimitIncreaseInput +{ + public function __construct( + public string $customerId, + public float $daily, + public float $monthly, + public float $perTransaction, + public string $supportingDocumentFile, + public LimitIncreaseRequestSupportingDocumentType $supportingDocumentType + ) {} + + public function toArray(): array + { + return [ + 'daily' => $this->daily, + 'monthly' => $this->monthly, + 'per_transaction' => $this->perTransaction, + 'supporting_document_file' => $this->supportingDocumentFile, + 'supporting_document_type' => $this->supportingDocumentType->value, + ]; + } +} + +readonly class RequestLimitIncreaseResponse +{ + public function __construct( + public string $id + ) {} + + public static function fromArray(array $data): self + { + return new self( + id: $data['id'] + ); + } +} + +readonly class ListCustomersInput extends PaginationParams +{ + public function __construct( + public ?string $fullName = null, + public ?string $customerName = null, + public ?string $status = null, + public ?string $customerId = null, + public ?string $bankAccountId = null, + public ?string $country = null, + ?int $limit = null, + ?int $offset = null, + ?string $startingAfter = null, + ?string $endingBefore = null + ) { + parent::__construct($limit, $offset, $startingAfter, $endingBefore); + } + + public function toArray(): array + { + $params = parent::toArray(); + + if ($this->fullName !== null) { + $params['full_name'] = $this->fullName; + } + + if ($this->customerName !== null) { + $params['customer_name'] = $this->customerName; + } + + if ($this->status !== null) { + $params['status'] = $this->status; + } + + if ($this->customerId !== null) { + $params['customer_id'] = $this->customerId; + } + + if ($this->bankAccountId !== null) { + $params['bank_account_id'] = $this->bankAccountId; + } + + if ($this->country !== null) { + $params['country'] = $this->country; + } + + return $params; + } +} + +readonly class ListCustomersResponse +{ + public function __construct( + public array $data, + public PaginationMetadata $pagination + ) {} + + public static function fromArray(array $data): self + { + return new self( + data: $data['data'], + pagination: PaginationMetadata::fromArray($data['pagination']) + ); + } +} + +class Customers +{ + public function __construct( + private readonly string $instanceId, + private readonly ApiClientInterface $client + ) {} + + /** + * Maps API data to the appropriate customer class + * + * @throws \InvalidArgumentException + */ + private function mapCustomer(array $data): IndividualWithStandardKYC|IndividualWithEnhancedKYC|BusinessWithStandardKYB + { + $type = $data['type'] ?? null; + $kycType = $data['kyc_type'] ?? null; + + return match (true) { + $type === 'individual' && $kycType === 'standard' => IndividualWithStandardKYC::fromArray($data), + $type === 'individual' && $kycType === 'enhanced' => IndividualWithEnhancedKYC::fromArray($data), + $type === 'business' && $kycType === 'standard' => BusinessWithStandardKYB::fromArray($data), + default => throw new \InvalidArgumentException("Unknown customer type: {$type}/{$kycType}") + }; + } + + /* + * List all customers + * + * @return BlindPayApiResponse> + */ + public function list(?ListCustomersInput $params = null): BlindPayApiResponse + { + $queryString = $params?->toQueryString() ?? ''; + $response = $this->client->get("instances/{$this->instanceId}/customers{$queryString}"); + + if ($response->isSuccess() && is_array($response->data)) { + try { + if (isset($response->data['data']) && isset($response->data['pagination'])) { + $customers = array_map( + fn (array $item) => $this->mapCustomer($item), + $response->data['data'] + ); + + return BlindPayApiResponse::success( + new ListCustomersResponse( + data: $customers, + pagination: PaginationMetadata::fromArray($response->data['pagination']) + ) + ); + } + + $customers = array_map( + fn (array $item) => $this->mapCustomer($item), + $response->data + ); + + return BlindPayApiResponse::success($customers); + } catch (\InvalidArgumentException $e) { + return BlindPayApiResponse::error( + new \BlindPay\SDK\Types\ErrorResponse($e->getMessage()) + ); + } + } + + return $response; + } + + /* + * Create individual with standard KYC + * + * @param CreateIndividualWithStandardKYCInput $data + * @return BlindPayApiResponse + */ + public function createIndividualWithStandardKYC(CreateIndividualWithStandardKYCInput $data): BlindPayApiResponse + { + $response = $this->client->post( + "instances/{$this->instanceId}/customers", + $data->toArray() + ); + + if ($response->isSuccess() && is_array($response->data)) { + return BlindPayApiResponse::success( + CreateCustomerResponse::fromArray($response->data) + ); + } + + return $response; + } + + /* + * Create individual with enhanced KYC + * + * @param CreateIndividualWithEnhancedKYCInput $data + * @return BlindPayApiResponse + */ + public function createIndividualWithEnhancedKYC(CreateIndividualWithEnhancedKYCInput $data): BlindPayApiResponse + { + $response = $this->client->post( + "instances/{$this->instanceId}/customers", + $data->toArray() + ); + + if ($response->isSuccess() && is_array($response->data)) { + return BlindPayApiResponse::success( + CreateCustomerResponse::fromArray($response->data) + ); + } + + return $response; + } + + /* + * Create business with standard KYB + * + * @param CreateBusinessWithStandardKYBInput $data + * @return BlindPayApiResponse + */ + public function createBusinessWithStandardKYB(CreateBusinessWithStandardKYBInput $data): BlindPayApiResponse + { + $response = $this->client->post( + "instances/{$this->instanceId}/customers", + $data->toArray() + ); + + if ($response->isSuccess() && is_array($response->data)) { + return BlindPayApiResponse::success( + CreateCustomerResponse::fromArray($response->data) + ); + } + + return $response; + } + + /* + * Get a customer by ID + * + * @param string $customerId + * @return BlindPayApiResponse + */ + public function get(string $customerId): BlindPayApiResponse + { + if (empty($customerId)) { + return BlindPayApiResponse::error( + new \BlindPay\SDK\Types\ErrorResponse('Customer ID cannot be empty') + ); + } + + $response = $this->client->get("instances/{$this->instanceId}/customers/{$customerId}"); + + if ($response->isSuccess() && is_array($response->data)) { + try { + return BlindPayApiResponse::success( + $this->mapCustomer($response->data) + ); + } catch (\InvalidArgumentException $e) { + return BlindPayApiResponse::error( + new \BlindPay\SDK\Types\ErrorResponse($e->getMessage()) + ); + } + } + + return $response; + } + + /* + * Update a customer + * + * @param UpdateCustomerInput $input + * @return BlindPayApiResponse + */ + public function update(UpdateCustomerInput $input): BlindPayApiResponse + { + if (empty($input->customerId)) { + return BlindPayApiResponse::error( + new \BlindPay\SDK\Types\ErrorResponse('Customer ID cannot be empty') + ); + } + + return $this->client->put( + "instances/{$this->instanceId}/customers/{$input->customerId}", + $input->toArray() + ); + } + + /* + * Delete a customer + * + * @param string $customerId + * @return BlindPayApiResponse + */ + public function delete(string $customerId): BlindPayApiResponse + { + if (empty($customerId)) { + return BlindPayApiResponse::error( + new \BlindPay\SDK\Types\ErrorResponse('Customer ID cannot be empty') + ); + } + + return $this->client->delete("instances/{$this->instanceId}/customers/{$customerId}"); + } + + /* + * Get customer limits + * + * @param string $customerId + * @return BlindPayApiResponse + */ + public function getLimits(string $customerId): BlindPayApiResponse + { + if (empty($customerId)) { + return BlindPayApiResponse::error( + new \BlindPay\SDK\Types\ErrorResponse('Customer ID cannot be empty') + ); + } + + $response = $this->client->get("instances/{$this->instanceId}/limits/customers/{$customerId}"); + + if ($response->isSuccess() && is_array($response->data)) { + return BlindPayApiResponse::success( + GetCustomerLimitsResponse::fromArray($response->data) + ); + } + + return $response; + } + + /* + * Get limit increase requests + * + * @param string $customerId + * @return BlindPayApiResponse + */ + public function getLimitIncreaseRequests(string $customerId): BlindPayApiResponse + { + if (empty($customerId)) { + return BlindPayApiResponse::error( + new \BlindPay\SDK\Types\ErrorResponse('Customer ID cannot be empty') + ); + } + + $response = $this->client->get("instances/{$this->instanceId}/customers/{$customerId}/limit-increase"); + + if ($response->isSuccess() && is_array($response->data)) { + $requests = array_map( + fn (array $item) => LimitIncreaseRequest::fromArray($item), + $response->data + ); + + return BlindPayApiResponse::success($requests); + } + + return $response; + } + + /* + * Request limit increase + * + * @param RequestLimitIncreaseInput $input + * @return BlindPayApiResponse + */ + public function requestLimitIncrease(RequestLimitIncreaseInput $input): BlindPayApiResponse + { + if (empty($input->customerId)) { + return BlindPayApiResponse::error( + new \BlindPay\SDK\Types\ErrorResponse('Customer ID cannot be empty') + ); + } + + $response = $this->client->post( + "instances/{$this->instanceId}/customers/{$input->customerId}/limit-increase", + $input->toArray() + ); + + if ($response->isSuccess() && is_array($response->data)) { + return BlindPayApiResponse::success( + RequestLimitIncreaseResponse::fromArray($response->data) + ); + } + + return $response; + } +} diff --git a/src/Resources/Customers/CustomersWrapper.php b/src/Resources/Customers/CustomersWrapper.php new file mode 100644 index 0000000..b75fe73 --- /dev/null +++ b/src/Resources/Customers/CustomersWrapper.php @@ -0,0 +1,125 @@ +> + */ + public function list(?ListCustomersInput $params = null): BlindPayApiResponse + { + return $this->base->list($params); + } + + /* + * Create individual with standard KYC + * + * @param CreateIndividualWithStandardKYCInput $data + * @return BlindPayApiResponse + */ + public function createIndividualWithStandardKYC(CreateIndividualWithStandardKYCInput $data): BlindPayApiResponse + { + return $this->base->createIndividualWithStandardKYC($data); + } + + /* + * Create individual with enhanced KYC + * + * @param CreateIndividualWithEnhancedKYCInput $data + * @return BlindPayApiResponse + */ + public function createIndividualWithEnhancedKYC(CreateIndividualWithEnhancedKYCInput $data): BlindPayApiResponse + { + return $this->base->createIndividualWithEnhancedKYC($data); + } + + /* + * Create business with standard KYB + * + * @param CreateBusinessWithStandardKYBInput $data + * @return BlindPayApiResponse + */ + public function createBusinessWithStandardKYB(CreateBusinessWithStandardKYBInput $data): BlindPayApiResponse + { + return $this->base->createBusinessWithStandardKYB($data); + } + + /* + * Get a customer by ID + * + * @param string $customerId + * @return BlindPayApiResponse + */ + public function get(string $customerId): BlindPayApiResponse + { + return $this->base->get($customerId); + } + + /* + * Update a customer + * + * @param UpdateCustomerInput $input + * @return BlindPayApiResponse + */ + public function update(UpdateCustomerInput $input): BlindPayApiResponse + { + return $this->base->update($input); + } + + /* + * Delete a customer + * + * @param string $customerId + * @return BlindPayApiResponse + */ + public function delete(string $customerId): BlindPayApiResponse + { + return $this->base->delete($customerId); + } + + /* + * Get customer limits + * + * @param string $customerId + * @return BlindPayApiResponse + */ + public function getLimits(string $customerId): BlindPayApiResponse + { + return $this->base->getLimits($customerId); + } + + /* + * Get limit increase requests + * + * @param string $customerId + * @return BlindPayApiResponse + */ + public function getLimitIncreaseRequests(string $customerId): BlindPayApiResponse + { + return $this->base->getLimitIncreaseRequests($customerId); + } + + /* + * Request limit increase + * + * @param RequestLimitIncreaseInput $input + * @return BlindPayApiResponse + */ + public function requestLimitIncrease(RequestLimitIncreaseInput $input): BlindPayApiResponse + { + return $this->base->requestLimitIncrease($input); + } +} diff --git a/src/Resources/VirtualAccounts/VirtualAccounts.php b/src/Resources/VirtualAccounts/VirtualAccounts.php index f8c0159..61e967b 100644 --- a/src/Resources/VirtualAccounts/VirtualAccounts.php +++ b/src/Resources/VirtualAccounts/VirtualAccounts.php @@ -263,7 +263,7 @@ public function toArray(): array readonly class CreateVirtualAccountInput { public function __construct( - public string $receiverId, + public string $customerId, public string $blockchainWalletId, public StablecoinToken $token, public ?BankingPartner $bankingPartner = null, @@ -297,7 +297,7 @@ public function toArray(): array readonly class UpdateVirtualAccountInput { public function __construct( - public string $receiverId, + public string $customerId, public string $virtualAccountId, public string $blockchainWalletId, public StablecoinToken $token @@ -327,7 +327,7 @@ public function __construct( public function create(CreateVirtualAccountInput $input): BlindPayApiResponse { $response = $this->client->post( - "instances/{$this->instanceId}/receivers/{$input->receiverId}/virtual-accounts", + "instances/{$this->instanceId}/customers/{$input->customerId}/virtual-accounts", $input->toArray() ); @@ -354,7 +354,7 @@ public function update(UpdateVirtualAccountInput $input): BlindPayApiResponse } return $this->client->put( - "instances/{$this->instanceId}/receivers/{$input->receiverId}/virtual-accounts/{$input->virtualAccountId}", + "instances/{$this->instanceId}/customers/{$input->customerId}/virtual-accounts/{$input->virtualAccountId}", $input->toArray() ); } @@ -364,11 +364,11 @@ public function update(UpdateVirtualAccountInput $input): BlindPayApiResponse * * @return BlindPayApiResponse */ - public function get(string $receiverId, string $virtualAccountId): BlindPayApiResponse + public function get(string $customerId, string $virtualAccountId): BlindPayApiResponse { - if (empty($receiverId)) { + if (empty($customerId)) { return BlindPayApiResponse::error( - new \BlindPay\SDK\Types\ErrorResponse('Receiver ID cannot be empty') + new \BlindPay\SDK\Types\ErrorResponse('Customer ID cannot be empty') ); } @@ -379,7 +379,7 @@ public function get(string $receiverId, string $virtualAccountId): BlindPayApiRe } $response = $this->client->get( - "instances/{$this->instanceId}/receivers/{$receiverId}/virtual-accounts/{$virtualAccountId}" + "instances/{$this->instanceId}/customers/{$customerId}/virtual-accounts/{$virtualAccountId}" ); if ($response->isSuccess() && is_array($response->data)) { @@ -392,20 +392,20 @@ public function get(string $receiverId, string $virtualAccountId): BlindPayApiRe } /** - * List virtual accounts for a receiver + * List virtual accounts for a customer * * @return BlindPayApiResponse */ - public function list(string $receiverId): BlindPayApiResponse + public function list(string $customerId): BlindPayApiResponse { - if (empty($receiverId)) { + if (empty($customerId)) { return BlindPayApiResponse::error( - new \BlindPay\SDK\Types\ErrorResponse('Receiver ID cannot be empty') + new \BlindPay\SDK\Types\ErrorResponse('Customer ID cannot be empty') ); } $response = $this->client->get( - "instances/{$this->instanceId}/receivers/{$receiverId}/virtual-accounts" + "instances/{$this->instanceId}/customers/{$customerId}/virtual-accounts" ); if ($response->isSuccess() && is_array($response->data)) { diff --git a/src/Resources/Wallets/BlockchainWallets.php b/src/Resources/Wallets/BlockchainWallets.php index 54edcb7..910dcf9 100644 --- a/src/Resources/Wallets/BlockchainWallets.php +++ b/src/Resources/Wallets/BlockchainWallets.php @@ -15,7 +15,7 @@ public function __construct( public string $name, public Network $network, public bool $isAccountAbstraction, - public string $receiverId, + public string $customerId, public ?string $address = null, public ?string $signatureTxHash = null ) {} @@ -27,7 +27,7 @@ public static function fromArray(array $data): self name: $data['name'], network: Network::from($data['network']), isAccountAbstraction: $data['is_account_abstraction'], - receiverId: $data['receiver_id'], + customerId: $data['customer_id'], address: $data['address'] ?? null, signatureTxHash: $data['signature_tx_hash'] ?? null ); @@ -51,7 +51,7 @@ public static function fromArray(array $data): self readonly class CreateBlockchainWalletWithAddressInput { public function __construct( - public string $receiverId, + public string $customerId, public string $name, public Network $network, public string $address @@ -71,7 +71,7 @@ public function toArray(): array readonly class CreateBlockchainWalletWithHashInput { public function __construct( - public string $receiverId, + public string $customerId, public string $name, public Network $network, public string $signatureTxHash @@ -91,7 +91,7 @@ public function toArray(): array readonly class GetBlockchainWalletInput { public function __construct( - public string $receiverId, + public string $customerId, public string $id ) {} } @@ -99,7 +99,7 @@ public function __construct( readonly class DeleteBlockchainWalletInput { public function __construct( - public string $receiverId, + public string $customerId, public string $id ) {} } @@ -219,16 +219,16 @@ public function __construct( * * @return BlindPayApiResponse */ - public function list(string $receiverId): BlindPayApiResponse + public function list(string $customerId): BlindPayApiResponse { - if (empty($receiverId)) { + if (empty($customerId)) { return BlindPayApiResponse::error( - new \BlindPay\SDK\Types\ErrorResponse('Receiver ID cannot be empty') + new \BlindPay\SDK\Types\ErrorResponse('Customer ID cannot be empty') ); } $response = $this->client->get( - "instances/{$this->instanceId}/receivers/{$receiverId}/blockchain-wallets" + "instances/{$this->instanceId}/customers/{$customerId}/blockchain-wallets" ); if ($response->isSuccess() && is_array($response->data)) { @@ -251,7 +251,7 @@ public function list(string $receiverId): BlindPayApiResponse public function createWithAddress(CreateBlockchainWalletWithAddressInput $input): BlindPayApiResponse { $response = $this->client->post( - "instances/{$this->instanceId}/receivers/{$input->receiverId}/blockchain-wallets", + "instances/{$this->instanceId}/customers/{$input->customerId}/blockchain-wallets", $input->toArray() ); @@ -272,7 +272,7 @@ public function createWithAddress(CreateBlockchainWalletWithAddressInput $input) public function createWithHash(CreateBlockchainWalletWithHashInput $input): BlindPayApiResponse { $response = $this->client->post( - "instances/{$this->instanceId}/receivers/{$input->receiverId}/blockchain-wallets", + "instances/{$this->instanceId}/customers/{$input->customerId}/blockchain-wallets", $input->toArray() ); @@ -290,16 +290,16 @@ public function createWithHash(CreateBlockchainWalletWithHashInput $input): Blin * * @return BlindPayApiResponse */ - public function getMessage(string $receiverId): BlindPayApiResponse + public function getMessage(string $customerId): BlindPayApiResponse { - if (empty($receiverId)) { + if (empty($customerId)) { return BlindPayApiResponse::error( - new \BlindPay\SDK\Types\ErrorResponse('Receiver ID cannot be empty') + new \BlindPay\SDK\Types\ErrorResponse('Customer ID cannot be empty') ); } $response = $this->client->get( - "instances/{$this->instanceId}/receivers/{$receiverId}/blockchain-wallets/sign-message" + "instances/{$this->instanceId}/customers/{$customerId}/blockchain-wallets/sign-message" ); if ($response->isSuccess() && is_array($response->data)) { @@ -325,7 +325,7 @@ public function get(GetBlockchainWalletInput $input): BlindPayApiResponse } $response = $this->client->get( - "instances/{$this->instanceId}/receivers/{$input->receiverId}/blockchain-wallets/{$input->id}" + "instances/{$this->instanceId}/customers/{$input->customerId}/blockchain-wallets/{$input->id}" ); if ($response->isSuccess() && is_array($response->data)) { @@ -351,7 +351,7 @@ public function delete(DeleteBlockchainWalletInput $input): BlindPayApiResponse } return $this->client->delete( - "instances/{$this->instanceId}/receivers/{$input->receiverId}/blockchain-wallets/{$input->id}" + "instances/{$this->instanceId}/customers/{$input->customerId}/blockchain-wallets/{$input->id}" ); } diff --git a/src/Resources/Wallets/OfframpWallets.php b/src/Resources/Wallets/OfframpWallets.php index 27b7668..bfc5211 100644 --- a/src/Resources/Wallets/OfframpWallets.php +++ b/src/Resources/Wallets/OfframpWallets.php @@ -14,7 +14,7 @@ public function __construct( public string $id, public string $externalId, public string $instanceId, - public string $receiverId, + public string $customerId, public string $bankAccountId, public string $network, public string $address, @@ -29,7 +29,7 @@ public static function fromArray(array $data): self id: $data['id'], externalId: $data['external_id'], instanceId: $data['instance_id'], - receiverId: $data['receiver_id'], + customerId: $data['customer_id'], bankAccountId: $data['bank_account_id'], network: $data['network'], address: $data['address'], @@ -43,7 +43,7 @@ public static function fromArray(array $data): self readonly class ListOfframpWalletsInput { public function __construct( - public string $receiverId, + public string $customerId, public string $bankAccountId ) {} } @@ -51,7 +51,7 @@ public function __construct( readonly class CreateOfframpWalletInput { public function __construct( - public string $receiverId, + public string $customerId, public string $bankAccountId, public string $externalId, public string $network @@ -91,7 +91,7 @@ public static function fromArray(array $data): self readonly class GetOfframpWalletInput { public function __construct( - public string $receiverId, + public string $customerId, public string $bankAccountId, public string $id ) {} @@ -115,7 +115,7 @@ public function __construct( public function list(ListOfframpWalletsInput $input): BlindPayApiResponse { $response = $this->client->get( - "instances/{$this->instanceId}/receivers/{$input->receiverId}/bank-accounts/{$input->bankAccountId}/offramp-wallets" + "instances/{$this->instanceId}/customers/{$input->customerId}/bank-accounts/{$input->bankAccountId}/offramp-wallets" ); if ($response->isSuccess() && is_array($response->data)) { @@ -138,7 +138,7 @@ public function list(ListOfframpWalletsInput $input): BlindPayApiResponse public function create(CreateOfframpWalletInput $input): BlindPayApiResponse { $response = $this->client->post( - "instances/{$this->instanceId}/receivers/{$input->receiverId}/bank-accounts/{$input->bankAccountId}/offramp-wallets", + "instances/{$this->instanceId}/customers/{$input->customerId}/bank-accounts/{$input->bankAccountId}/offramp-wallets", $input->toArray() ); @@ -165,7 +165,7 @@ public function get(GetOfframpWalletInput $input): BlindPayApiResponse } $response = $this->client->get( - "instances/{$this->instanceId}/receivers/{$input->receiverId}/bank-accounts/{$input->bankAccountId}/offramp-wallets/{$input->id}" + "instances/{$this->instanceId}/customers/{$input->customerId}/bank-accounts/{$input->bankAccountId}/offramp-wallets/{$input->id}" ); if ($response->isSuccess() && is_array($response->data)) { diff --git a/tests/Resources/BankAccounts/BankAccountsTest.php b/tests/Resources/BankAccounts/BankAccountsTest.php index 24f7c40..a376db9 100644 --- a/tests/Resources/BankAccounts/BankAccountsTest.php +++ b/tests/Resources/BankAccounts/BankAccountsTest.php @@ -83,7 +83,7 @@ public function it_creates_a_pix_bank_account(): void $this->mockResponse($mockedPixAccount); $input = new CreatePixInput( - receiverId: 're_000000000000', + customerId: 're_000000000000', name: 'PIX Account', pixKey: '14947677768' ); @@ -114,7 +114,7 @@ public function it_creates_an_argentina_transfers_bank_account(): void $this->mockResponse($mockedArgentinaTransfersAccount); $input = new CreateArgentinaTransfersInput( - receiverId: 're_000000000000', + customerId: 're_000000000000', name: 'Argentina Transfers Account', beneficiaryName: 'Individual full name or business name', transfersAccount: 'BM123123123123', @@ -150,7 +150,7 @@ public function it_creates_a_spei_bank_account(): void $this->mockResponse($mockedSpeiAccount); $input = new CreateSpeiInput( - receiverId: 're_000000000000', + customerId: 're_000000000000', beneficiaryName: 'Individual full name or business name', name: 'SPEI Account', speiClabe: '5482347403740546', @@ -191,7 +191,7 @@ public function it_creates_a_colombia_ach_bank_account(): void $this->mockResponse($mockedColombiaAchAccount); $input = new CreateColombiaAchInput( - receiverId: 're_000000000000', + customerId: 're_000000000000', name: 'Colombia ACH Account', accountType: BankAccountType::CHECKING, achCopBeneficiaryFirstName: 'Fernando', @@ -251,7 +251,7 @@ public function it_creates_an_ach_bank_account(): void $this->mockResponse($mockedAchAccount); $input = new CreateAchInput( - receiverId: 're_000000000000', + customerId: 're_000000000000', name: 'ACH Account', accountClass: AccountClass::INDIVIDUAL, accountNumber: '1001001234', @@ -308,7 +308,7 @@ public function it_creates_a_wire_bank_account(): void $this->mockResponse($mockedWireAccount); $input = new CreateWireInput( - receiverId: 're_000000000000', + customerId: 're_000000000000', name: 'Wire Account', accountNumber: '1001001234', beneficiaryName: 'Individual full name or business name', @@ -381,7 +381,7 @@ public function it_creates_an_international_swift_bank_account(): void $this->mockResponse($mockedInternationalSwiftAccount); $input = new CreateInternationalSwiftInput( - receiverId: 're_000000000000', + customerId: 're_000000000000', name: 'International Swift Account', swiftAccountHolderName: 'John Doe', swiftAccountNumberIban: '123456789', @@ -459,7 +459,7 @@ public function it_creates_an_rtp_bank_account(): void $this->mockResponse($mockedRtpAccount); $input = new CreateRtpInput( - receiverId: 're_000000000000', + customerId: 're_000000000000', name: 'John Doe RTP', beneficiaryName: 'John Doe', routingNumber: '121000358', @@ -497,7 +497,7 @@ public function it_gets_a_bank_account(): void { $mockedBankAccount = [ 'id' => 'ba_000000000000', - 'receiver_id' => 'rcv_123', + 'customer_id' => 'rcv_123', 'account_holder_name' => 'Individual full name or business name', 'account_number' => '1001001234', 'routing_number' => '012345678', @@ -513,7 +513,7 @@ public function it_gets_a_bank_account(): void $this->mockResponse($mockedBankAccount); $input = new GetBankAccountInput( - receiverId: 're_000000000000', + customerId: 're_000000000000', id: 'ba_000000000000' ); @@ -522,7 +522,7 @@ public function it_gets_a_bank_account(): void $this->assertTrue($response->isSuccess()); $this->assertNull($response->error); $this->assertEquals('ba_000000000000', $response->data->id); - $this->assertEquals('rcv_123', $response->data->receiverId); + $this->assertEquals('rcv_123', $response->data->customerId); $this->assertEquals('Individual full name or business name', $response->data->accountHolderName); $this->assertEquals('1001001234', $response->data->accountNumber); $this->assertEquals('012345678', $response->data->routingNumber); @@ -625,7 +625,7 @@ public function it_deletes_a_bank_account(): void $this->mockResponse(['data' => null]); $input = new DeleteBankAccountInput( - receiverId: 're_000000000000', + customerId: 're_000000000000', id: 'ba_000000000000' ); diff --git a/tests/Resources/CustodialWallets/CustodialWalletsTest.php b/tests/Resources/CustodialWallets/CustodialWalletsTest.php index df22636..9aeefe5 100644 --- a/tests/Resources/CustodialWallets/CustodialWalletsTest.php +++ b/tests/Resources/CustodialWallets/CustodialWalletsTest.php @@ -61,7 +61,7 @@ public function it_creates_a_custodial_wallet(): void { $mockedWallet = [ 'id' => 'cw_000000000000', - 'receiver_id' => 're_000000000000', + 'customer_id' => 're_000000000000', 'instance_id' => 'in_000000000000', 'network' => 'solana', 'address' => 'So1ana1234567890', @@ -71,7 +71,7 @@ public function it_creates_a_custodial_wallet(): void $this->mockResponse($mockedWallet); $input = new CreateCustodialWalletInput( - receiverId: 're_000000000000', + customerId: 're_000000000000', network: Network::SOLANA ); @@ -80,7 +80,7 @@ public function it_creates_a_custodial_wallet(): void $this->assertTrue($response->isSuccess()); $this->assertNull($response->error); $this->assertEquals('cw_000000000000', $response->data->id); - $this->assertEquals('re_000000000000', $response->data->receiverId); + $this->assertEquals('re_000000000000', $response->data->customerId); $this->assertEquals(Network::SOLANA, $response->data->network); $this->assertEquals('So1ana1234567890', $response->data->address); } @@ -91,7 +91,7 @@ public function it_lists_custodial_wallets(): void $mockedWallets = [ [ 'id' => 'cw_000000000000', - 'receiver_id' => 're_000000000000', + 'customer_id' => 're_000000000000', 'instance_id' => 'in_000000000000', 'network' => 'solana', 'address' => 'So1ana1234567890', @@ -115,7 +115,7 @@ public function it_gets_a_custodial_wallet(): void { $mockedWallet = [ 'id' => 'cw_000000000000', - 'receiver_id' => 're_000000000000', + 'customer_id' => 're_000000000000', 'instance_id' => 'in_000000000000', 'network' => 'solana', 'address' => 'So1ana1234567890', @@ -125,7 +125,7 @@ public function it_gets_a_custodial_wallet(): void $this->mockResponse($mockedWallet); $input = new GetCustodialWalletInput( - receiverId: 're_000000000000', + customerId: 're_000000000000', id: 'cw_000000000000' ); @@ -156,7 +156,7 @@ public function it_gets_custodial_wallet_balance(): void $this->mockResponse($mockedBalance); $input = new GetCustodialWalletInput( - receiverId: 're_000000000000', + customerId: 're_000000000000', id: 'cw_000000000000' ); @@ -178,7 +178,7 @@ public function it_deletes_a_custodial_wallet(): void $this->mockResponse(['data' => null]); $input = new DeleteCustodialWalletInput( - receiverId: 're_000000000000', + customerId: 're_000000000000', id: 'cw_000000000000' ); diff --git a/tests/Resources/VirtualAccounts/VirtualAccountsTest.php b/tests/Resources/VirtualAccounts/VirtualAccountsTest.php index e6c3b32..263fe30 100644 --- a/tests/Resources/VirtualAccounts/VirtualAccountsTest.php +++ b/tests/Resources/VirtualAccounts/VirtualAccountsTest.php @@ -61,7 +61,7 @@ public function it_updates_a_virtual_account(): void $this->mockResponse(['data' => null]); $input = new UpdateVirtualAccountInput( - receiverId: 're_000000000000', + customerId: 're_000000000000', virtualAccountId: 'va_000000000000', blockchainWalletId: 'bw_000000000000', token: StablecoinToken::USDC @@ -113,7 +113,7 @@ public function it_creates_a_virtual_account(): void $this->mockResponse($mockedVirtualAccount); $input = new CreateVirtualAccountInput( - receiverId: 're_000000000000', + customerId: 're_000000000000', blockchainWalletId: 'bw_000000000000', token: StablecoinToken::USDC ); diff --git a/tests/Resources/Wallets/BlockchainWalletsTest.php b/tests/Resources/Wallets/BlockchainWalletsTest.php index 975ebe2..20fc58b 100644 --- a/tests/Resources/Wallets/BlockchainWalletsTest.php +++ b/tests/Resources/Wallets/BlockchainWalletsTest.php @@ -86,7 +86,7 @@ public function it_lists_blockchain_wallets(): void 'address' => '0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372C', 'signature_tx_hash' => '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359', 'is_account_abstraction' => false, - 'receiver_id' => 're_000000000000', + 'customer_id' => 're_000000000000', ], ]; @@ -115,13 +115,13 @@ public function it_creates_a_blockchain_wallet_with_address(): void 'address' => '0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372C', 'signature_tx_hash' => null, 'is_account_abstraction' => true, - 'receiver_id' => 're_000000000000', + 'customer_id' => 're_000000000000', ]; $this->mockResponse($mockedWallet); $input = new CreateBlockchainWalletWithAddressInput( - receiverId: 're_000000000000', + customerId: 're_000000000000', name: 'Wallet Display Name', network: Network::POLYGON, address: '0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372C' @@ -149,13 +149,13 @@ public function it_creates_a_blockchain_wallet_with_hash(): void 'address' => '0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372C', 'signature_tx_hash' => '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359', 'is_account_abstraction' => false, - 'receiver_id' => 're_000000000000', + 'customer_id' => 're_000000000000', ]; $this->mockResponse($mockedWallet); $input = new CreateBlockchainWalletWithHashInput( - receiverId: 're_000000000000', + customerId: 're_000000000000', name: 'Wallet Display Name', network: Network::POLYGON, signatureTxHash: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359' @@ -183,13 +183,13 @@ public function it_gets_a_blockchain_wallet(): void 'address' => '0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372C', 'signature_tx_hash' => '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359', 'is_account_abstraction' => false, - 'receiver_id' => 're_000000000000', + 'customer_id' => 're_000000000000', ]; $this->mockResponse($mockedWallet); $input = new GetBlockchainWalletInput( - receiverId: 're_000000000000', + customerId: 're_000000000000', id: 'bw_000000000000' ); @@ -209,7 +209,7 @@ public function it_deletes_a_blockchain_wallet(): void $this->mockResponse(['data' => null]); $input = new DeleteBlockchainWalletInput( - receiverId: 're_000000000000', + customerId: 're_000000000000', id: 'bw_000000000000' ); diff --git a/tests/Resources/Wallets/OfframpWalletsTest.php b/tests/Resources/Wallets/OfframpWalletsTest.php index 105113a..91deb98 100644 --- a/tests/Resources/Wallets/OfframpWalletsTest.php +++ b/tests/Resources/Wallets/OfframpWalletsTest.php @@ -63,7 +63,7 @@ public function it_lists_offramp_wallets(): void 'id' => 'ow_000000000000', 'external_id' => 'your_external_id', 'instance_id' => 'in_000000000000', - 'receiver_id' => 're_000000000000', + 'customer_id' => 're_000000000000', 'bank_account_id' => 'ba_000000000000', 'network' => 'tron', 'address' => 'TALJN9zTTEL9TVBb4WuTt6wLvPqJZr3hvb', @@ -75,7 +75,7 @@ public function it_lists_offramp_wallets(): void $this->mockResponse($mockedOfframpWallets); $input = new ListOfframpWalletsInput( - receiverId: 're_000000000000', + customerId: 're_000000000000', bankAccountId: 'ba_000000000000' ); @@ -88,7 +88,7 @@ public function it_lists_offramp_wallets(): void $this->assertEquals('ow_000000000000', $response->data[0]->id); $this->assertEquals('your_external_id', $response->data[0]->externalId); $this->assertEquals('in_000000000000', $response->data[0]->instanceId); - $this->assertEquals('re_000000000000', $response->data[0]->receiverId); + $this->assertEquals('re_000000000000', $response->data[0]->customerId); $this->assertEquals('ba_000000000000', $response->data[0]->bankAccountId); $this->assertEquals('tron', $response->data[0]->network); $this->assertEquals('TALJN9zTTEL9TVBb4WuTt6wLvPqJZr3hvb', $response->data[0]->address); @@ -107,7 +107,7 @@ public function it_creates_an_offramp_wallet(): void $this->mockResponse($mockedOfframpWallet); $input = new CreateOfframpWalletInput( - receiverId: 're_000000000000', + customerId: 're_000000000000', bankAccountId: 'ba_000000000000', externalId: 'your_external_id', network: 'tron' @@ -130,7 +130,7 @@ public function it_gets_an_offramp_wallet(): void 'id' => 'ow_000000000000', 'external_id' => 'your_external_id', 'instance_id' => 'in_000000000000', - 'receiver_id' => 're_000000000000', + 'customer_id' => 're_000000000000', 'bank_account_id' => 'ba_000000000000', 'network' => 'tron', 'address' => 'TALJN9zTTEL9TVBb4WuTt6wLvPqJZr3hvb', @@ -143,7 +143,7 @@ public function it_gets_an_offramp_wallet(): void $input = new GetOfframpWalletInput( id: 'ow_000000000000', bankAccountId: 'ba_000000000000', - receiverId: 're_000000000000' + customerId: 're_000000000000' ); $response = $this->blindpay->wallets->offramp->get($input); @@ -153,7 +153,7 @@ public function it_gets_an_offramp_wallet(): void $this->assertEquals('ow_000000000000', $response->data->id); $this->assertEquals('your_external_id', $response->data->externalId); $this->assertEquals('in_000000000000', $response->data->instanceId); - $this->assertEquals('re_000000000000', $response->data->receiverId); + $this->assertEquals('re_000000000000', $response->data->customerId); $this->assertEquals('ba_000000000000', $response->data->bankAccountId); $this->assertEquals('tron', $response->data->network); $this->assertEquals('TALJN9zTTEL9TVBb4WuTt6wLvPqJZr3hvb', $response->data->address); From 7180eb34c42ca0cd27b84dd6b3ecbcbbff6e5957 Mon Sep 17 00:00:00 2001 From: alvseven Date: Mon, 8 Jun 2026 14:29:05 -0300 Subject: [PATCH 2/6] feat: add SEPA + api-sync content (mirrors node #50) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Folds the api-sync content into the customers-resource minor: - New createSepa method (POSTs type=sepa to /customers/{customer_id}/bank-accounts) - CreateSepaInput + CreateSepaResponse readonly types - sepaBeneficiaryBic field on BankAccount, GetBankAccountResponse, CreateInternationalSwiftInput - New Resources/Ownership/Ownership.php (instance ownership migration) - ManualExecutionStatus type + field on Payin - billingFeeAmount + cpnPaymentId fields on Payout - receiver.delete WebhookEvent - BlindPay::VERSION bumped 2.3.0 → 2.4.0 --- src/BlindPay.php | 4 + src/Resources/BankAccounts/BankAccounts.php | 132 ++++++++++++++++++-- src/Resources/Ownership/Ownership.php | 66 ++++++++++ src/Resources/Payouts/Payouts.php | 4 + src/Resources/Webhooks/Webhooks.php | 26 ++++ src/Types/WebhookEvent.php | 1 + 6 files changed, 226 insertions(+), 7 deletions(-) create mode 100644 src/Resources/Ownership/Ownership.php diff --git a/src/BlindPay.php b/src/BlindPay.php index d523422..9cf0a24 100644 --- a/src/BlindPay.php +++ b/src/BlindPay.php @@ -15,6 +15,7 @@ use BlindPay\SDK\Resources\Fees\Fees; use BlindPay\SDK\Resources\Instances\Instances; use BlindPay\SDK\Resources\Instances\InstancesWrapper; +use BlindPay\SDK\Resources\Ownership\Ownership; use BlindPay\SDK\Resources\PartnerFees\PartnerFees; use BlindPay\SDK\Resources\Payins\Payins; use BlindPay\SDK\Resources\Payins\PayinsWrapper; @@ -59,6 +60,8 @@ class BlindPay implements ApiClientInterface public readonly InstancesWrapper $instances; + public readonly Ownership $ownership; + public readonly PayinsWrapper $payins; public readonly CustomersWrapper $customers; @@ -109,6 +112,7 @@ public function __construct( $this->virtualAccounts = new VirtualAccounts($this->instanceId, $this); $this->transfers = new Transfers($this->instanceId, $this); $this->fees = new Fees($this->instanceId, $this); + $this->ownership = new Ownership($this->instanceId, $this); $this->upload = new Upload($this); $this->initializeInstances(); diff --git a/src/Resources/BankAccounts/BankAccounts.php b/src/Resources/BankAccounts/BankAccounts.php index 17c6ce7..d427237 100644 --- a/src/Resources/BankAccounts/BankAccounts.php +++ b/src/Resources/BankAccounts/BankAccounts.php @@ -96,7 +96,8 @@ public function __construct( public DateTimeImmutable $createdAt, public ?string $tedBankCode = null, public ?string $tedBranchCode = null, - public ?string $tedCpfCnpj = null + public ?string $tedCpfCnpj = null, + public ?string $sepaBeneficiaryBic = null ) {} public static function fromArray(array $data): self @@ -154,7 +155,8 @@ public static function fromArray(array $data): self createdAt: new DateTimeImmutable($data['created_at']), tedBankCode: $data['ted_bank_code'] ?? null, tedBranchCode: $data['ted_branch_code'] ?? null, - tedCpfCnpj: $data['ted_cpf_cnpj'] ?? null + tedCpfCnpj: $data['ted_cpf_cnpj'] ?? null, + sepaBeneficiaryBic: $data['sepa_beneficiary_bic'] ?? null ); } } @@ -201,7 +203,8 @@ public function __construct( public ?string $iban, public bool $isPrimary, public DateTimeImmutable $createdAt, - public DateTimeImmutable $updatedAt + public DateTimeImmutable $updatedAt, + public ?string $sepaBeneficiaryBic = null ) {} public static function fromArray(array $data): self @@ -218,7 +221,8 @@ public static function fromArray(array $data): self iban: $data['iban'] ?? null, isPrimary: $data['is_primary'], createdAt: new DateTimeImmutable($data['created_at']), - updatedAt: new DateTimeImmutable($data['updated_at']) + updatedAt: new DateTimeImmutable($data['updated_at']), + sepaBeneficiaryBic: $data['sepa_beneficiary_bic'] ?? null ); } } @@ -695,7 +699,8 @@ public function __construct( public ?string $dateOfBirth = null, public ?string $tedBankCode = null, public ?string $tedBranchCode = null, - public ?string $tedCpfCnpj = null + public ?string $tedCpfCnpj = null, + public ?string $sepaBeneficiaryBic = null ) {} public function toArray(): array @@ -765,6 +770,10 @@ public function toArray(): array $data['ted_cpf_cnpj'] = $this->tedCpfCnpj; } + if ($this->sepaBeneficiaryBic !== null) { + $data['sepa_beneficiary_bic'] = $this->sepaBeneficiaryBic; + } + return $data; } } @@ -802,7 +811,8 @@ public function __construct( public ?string $swiftIntermediaryBankAccountNumberIban, public ?string $swiftIntermediaryBankName, public ?Country $swiftIntermediaryBankCountry, - public DateTimeImmutable $createdAt + public DateTimeImmutable $createdAt, + public ?string $sepaBeneficiaryBic = null ) {} public static function fromArray(array $data): self @@ -838,7 +848,8 @@ public static function fromArray(array $data): self swiftIntermediaryBankAccountNumberIban: $data['swift_intermediary_bank_account_number_iban'] ?? null, swiftIntermediaryBankName: $data['swift_intermediary_bank_name'] ?? null, swiftIntermediaryBankCountry: isset($data['swift_intermediary_bank_country']) ? Country::from($data['swift_intermediary_bank_country']) : null, - createdAt: new DateTimeImmutable($data['created_at']) + createdAt: new DateTimeImmutable($data['created_at']), + sepaBeneficiaryBic: $data['sepa_beneficiary_bic'] ?? null ); } } @@ -1277,4 +1288,111 @@ public function createPixSafe(CreatePixSafeInput $input): BlindPayApiResponse return $response; } + + /* + * Create SEPA bank account + * + * @param CreateSepaInput $input + * @return BlindPayApiResponse + */ + public function createSepa(CreateSepaInput $input): BlindPayApiResponse + { + $response = $this->client->post( + "instances/{$this->instanceId}/customers/{$input->customerId}/bank-accounts", + $input->toArray() + ); + + if ($response->isSuccess() && is_array($response->data)) { + return BlindPayApiResponse::success( + CreateSepaResponse::fromArray($response->data) + ); + } + + return $response; + } +} + +readonly class CreateSepaInput +{ + public function __construct( + public string $customerId, + public string $name, + public AccountClass $accountClass, + public string $sepaIban, + public string $sepaBeneficiaryBic, + public string $sepaBeneficiaryLegalName, + public string $sepaBeneficiaryAddressLine1, + public string $sepaBeneficiaryCity, + public string $sepaBeneficiaryPostalCode, + public Country $sepaBeneficiaryCountry, + public ?string $sepaBeneficiaryAddressLine2 = null, + public ?string $sepaBeneficiaryStateProvinceRegion = null + ) {} + + public function toArray(): array + { + $data = [ + 'type' => 'sepa', + 'name' => $this->name, + 'account_class' => $this->accountClass->value, + 'sepa_iban' => $this->sepaIban, + 'sepa_beneficiary_bic' => $this->sepaBeneficiaryBic, + 'sepa_beneficiary_legal_name' => $this->sepaBeneficiaryLegalName, + 'sepa_beneficiary_address_line_1' => $this->sepaBeneficiaryAddressLine1, + 'sepa_beneficiary_city' => $this->sepaBeneficiaryCity, + 'sepa_beneficiary_postal_code' => $this->sepaBeneficiaryPostalCode, + 'sepa_beneficiary_country' => $this->sepaBeneficiaryCountry->value, + ]; + + if ($this->sepaBeneficiaryAddressLine2 !== null) { + $data['sepa_beneficiary_address_line_2'] = $this->sepaBeneficiaryAddressLine2; + } + if ($this->sepaBeneficiaryStateProvinceRegion !== null) { + $data['sepa_beneficiary_state_province_region'] = $this->sepaBeneficiaryStateProvinceRegion; + } + + return $data; + } +} + +readonly class CreateSepaResponse +{ + public function __construct( + public string $id, + public string $type, + public string $name, + public AccountClass $accountClass, + public ?RecipientRelationship $recipientRelationship, + public string $sepaIban, + public string $sepaBeneficiaryBic, + public string $sepaBeneficiaryLegalName, + public string $sepaBeneficiaryAddressLine1, + public ?string $sepaBeneficiaryAddressLine2, + public string $sepaBeneficiaryCity, + public ?string $sepaBeneficiaryStateProvinceRegion, + public string $sepaBeneficiaryPostalCode, + public Country $sepaBeneficiaryCountry, + public DateTimeImmutable $createdAt + ) {} + + public static function fromArray(array $data): self + { + return new self( + id: $data['id'], + type: $data['type'], + name: $data['name'], + accountClass: AccountClass::from($data['account_class']), + recipientRelationship: isset($data['recipient_relationship']) ? RecipientRelationship::from($data['recipient_relationship']) : null, + sepaIban: $data['sepa_iban'], + sepaBeneficiaryBic: $data['sepa_beneficiary_bic'], + sepaBeneficiaryLegalName: $data['sepa_beneficiary_legal_name'], + sepaBeneficiaryAddressLine1: $data['sepa_beneficiary_address_line_1'], + sepaBeneficiaryAddressLine2: $data['sepa_beneficiary_address_line_2'] ?? null, + sepaBeneficiaryCity: $data['sepa_beneficiary_city'], + sepaBeneficiaryStateProvinceRegion: $data['sepa_beneficiary_state_province_region'] ?? null, + sepaBeneficiaryPostalCode: $data['sepa_beneficiary_postal_code'], + sepaBeneficiaryCountry: Country::from($data['sepa_beneficiary_country']), + createdAt: new DateTimeImmutable($data['created_at']) + ); + } } diff --git a/src/Resources/Ownership/Ownership.php b/src/Resources/Ownership/Ownership.php new file mode 100644 index 0000000..da77784 --- /dev/null +++ b/src/Resources/Ownership/Ownership.php @@ -0,0 +1,66 @@ + $this->userId, + ]; + } +} + +readonly class MigrateInstanceOwnershipResponse +{ + public function __construct( + public bool $success + ) {} + + public static function fromArray(array $data): self + { + return new self( + success: $data['success'] + ); + } +} + +class Ownership +{ + public function __construct( + private readonly string $instanceId, + private readonly ApiClientInterface $client + ) {} + + /* + * Migrate instance ownership to another user + * + * @param MigrateInstanceOwnershipIn $input + * @return BlindPayApiResponse + */ + public function migrate(MigrateInstanceOwnershipIn $input): BlindPayApiResponse + { + $response = $this->client->post( + "instances/{$this->instanceId}/ownership", + $input->toArray() + ); + + if ($response->isSuccess() && is_array($response->data)) { + return BlindPayApiResponse::success( + MigrateInstanceOwnershipResponse::fromArray($response->data) + ); + } + + return $response; + } +} diff --git a/src/Resources/Payouts/Payouts.php b/src/Resources/Payouts/Payouts.php index 6fa9812..6a20b56 100644 --- a/src/Resources/Payouts/Payouts.php +++ b/src/Resources/Payouts/Payouts.php @@ -184,8 +184,12 @@ public static function fromArray(array $data): self tedBranchCode: $data['ted_branch_code'] ?? null, tedCpfCnpj: $data['ted_cpf_cnpj'] ?? null, billingFeeAmount: isset($data['billing_fee_amount']) ? (float) $data['billing_fee_amount'] : null, +<<<<<<< HEAD cpnPaymentId: $data['cpn_payment_id'] ?? null, senderLegalName: $data['sender_legal_name'] ?? null +======= + cpnPaymentId: $data['cpn_payment_id'] ?? null +>>>>>>> 4d7c0ad (feat: add SEPA + api-sync content (mirrors node #50)) ); } } diff --git a/src/Resources/Webhooks/Webhooks.php b/src/Resources/Webhooks/Webhooks.php index 14da03f..a514c74 100644 --- a/src/Resources/Webhooks/Webhooks.php +++ b/src/Resources/Webhooks/Webhooks.php @@ -137,6 +137,32 @@ public static function fromArray(array $data): self } } +readonly class ReceiverDeleteWebhookOut +{ + public function __construct( + public string $id, + public string $event, + public string $receiverId, + public string $instanceId, + public DateTimeImmutable $timestamp, + public string $type, + public array $data + ) {} + + public static function fromArray(array $data): self + { + return new self( + id: $data['id'], + event: $data['event'], + receiverId: $data['receiver_id'], + instanceId: $data['instance_id'], + timestamp: new DateTimeImmutable($data['timestamp']), + type: $data['type'], + data: $data['data'] + ); + } +} + class Webhooks { public function __construct( diff --git a/src/Types/WebhookEvent.php b/src/Types/WebhookEvent.php index a62745f..2fc91bf 100644 --- a/src/Types/WebhookEvent.php +++ b/src/Types/WebhookEvent.php @@ -8,6 +8,7 @@ enum WebhookEvent: string { case RECEIVER_NEW = 'receiver.new'; case RECEIVER_UPDATE = 'receiver.update'; + case RECEIVER_DELETE = 'receiver.delete'; case BANK_ACCOUNT_NEW = 'bankAccount.new'; case PAYOUT_NEW = 'payout.new'; case PAYOUT_UPDATE = 'payout.update'; From 70fa1383ce41588b70b53fcd8efce61902423e98 Mon Sep 17 00:00:00 2001 From: alvseven Date: Mon, 8 Jun 2026 17:55:25 -0300 Subject: [PATCH 3/6] fix(bank-accounts): correct ListBankAccountsResponse + Rail/nullable bugs found via live QA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three bugs caught by live QA against dev instance: - ListBankAccountsResponse::fromArray expected {data: [...]} but the API returns a plain array (mirrors node SDK #56) - IndividualWith*KYC $aipriseValidationKey typed as 'string' but the API returns null when absent — switched to ?string - Rail enum was missing 'sepa' and 'ted' cases that the API returns Test fixture for it_lists_bank_accounts updated to match real API response shape (plain array, not data-wrapped). --- src/Resources/BankAccounts/BankAccounts.php | 2 +- src/Resources/Customers/Customers.php | 12 ++++++------ src/Resources/Receivers/Receivers.php | 12 ++++++------ src/Types/Rail.php | 2 ++ tests/Resources/BankAccounts/BankAccountsTest.php | 8 +++----- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Resources/BankAccounts/BankAccounts.php b/src/Resources/BankAccounts/BankAccounts.php index d427237..23dece0 100644 --- a/src/Resources/BankAccounts/BankAccounts.php +++ b/src/Resources/BankAccounts/BankAccounts.php @@ -175,7 +175,7 @@ public static function fromArray(array $data): self return new self( data: array_map( fn (array $item) => BankAccountListItem::fromArray($item), - $data['data'] + $data ) ); } diff --git a/src/Resources/Customers/Customers.php b/src/Resources/Customers/Customers.php index a2cc579..98c3ccc 100644 --- a/src/Resources/Customers/Customers.php +++ b/src/Resources/Customers/Customers.php @@ -349,7 +349,7 @@ public function __construct( public IdentificationDocument $idDocType, public string $idDocFrontFile, public string $idDocBackFile, - public string $aipriseValidationKey, + public ?string $aipriseValidationKey, ?array $fraudWarnings = null, ?bool $isFbo = null, ?AccountPurpose $accountPurpose = null, @@ -429,7 +429,7 @@ public static function fromArray(array $data): self idDocType: IdentificationDocument::from($data['id_doc_type']), idDocFrontFile: $data['id_doc_front_file'], idDocBackFile: $data['id_doc_back_file'], - aipriseValidationKey: $data['aiprise_validation_key'], + aipriseValidationKey: $data['aiprise_validation_key'] ?? null, fraudWarnings: isset($data['fraud_warnings']) ? array_map( fn ($w) => FraudWarning::fromArray($w), $data['fraud_warnings'] @@ -478,7 +478,7 @@ public function __construct( public IdentificationDocument $idDocType, public string $idDocFrontFile, public ?string $idDocBackFile, - public string $aipriseValidationKey, + public ?string $aipriseValidationKey, public string $sourceOfFundsDocType, public string $sourceOfFundsDocFile, public string $individualHoldingDocFrontFile, @@ -563,7 +563,7 @@ public static function fromArray(array $data): self idDocType: IdentificationDocument::from($data['id_doc_type']), idDocFrontFile: $data['id_doc_front_file'], idDocBackFile: $data['id_doc_back_file'] ?? null, - aipriseValidationKey: $data['aiprise_validation_key'], + aipriseValidationKey: $data['aiprise_validation_key'] ?? null, sourceOfFundsDocType: $data['source_of_funds_doc_type'], sourceOfFundsDocFile: $data['source_of_funds_doc_file'], individualHoldingDocFrontFile: $data['individual_holding_doc_front_file'], @@ -621,7 +621,7 @@ public function __construct( public string $incorporationDocFile, public string $proofOfOwnershipDocFile, public ?string $externalId, - public string $aipriseValidationKey, + public ?string $aipriseValidationKey, ?array $fraudWarnings = null, ?bool $isFbo = null, ?AccountPurpose $accountPurpose = null, @@ -705,7 +705,7 @@ public static function fromArray(array $data): self incorporationDocFile: $data['incorporation_doc_file'], proofOfOwnershipDocFile: $data['proof_of_ownership_doc_file'], externalId: $data['external_id'] ?? null, - aipriseValidationKey: $data['aiprise_validation_key'], + aipriseValidationKey: $data['aiprise_validation_key'] ?? null, fraudWarnings: isset($data['fraud_warnings']) ? array_map( fn ($w) => FraudWarning::fromArray($w), $data['fraud_warnings'] diff --git a/src/Resources/Receivers/Receivers.php b/src/Resources/Receivers/Receivers.php index ea9ce3e..8939a42 100644 --- a/src/Resources/Receivers/Receivers.php +++ b/src/Resources/Receivers/Receivers.php @@ -349,7 +349,7 @@ public function __construct( public IdentificationDocument $idDocType, public string $idDocFrontFile, public string $idDocBackFile, - public string $aipriseValidationKey, + public ?string $aipriseValidationKey, ?array $fraudWarnings = null, ?bool $isFbo = null, ?AccountPurpose $accountPurpose = null, @@ -429,7 +429,7 @@ public static function fromArray(array $data): self idDocType: IdentificationDocument::from($data['id_doc_type']), idDocFrontFile: $data['id_doc_front_file'], idDocBackFile: $data['id_doc_back_file'], - aipriseValidationKey: $data['aiprise_validation_key'], + aipriseValidationKey: $data['aiprise_validation_key'] ?? null, fraudWarnings: isset($data['fraud_warnings']) ? array_map( fn ($w) => FraudWarning::fromArray($w), $data['fraud_warnings'] @@ -478,7 +478,7 @@ public function __construct( public IdentificationDocument $idDocType, public string $idDocFrontFile, public ?string $idDocBackFile, - public string $aipriseValidationKey, + public ?string $aipriseValidationKey, public string $sourceOfFundsDocType, public string $sourceOfFundsDocFile, public string $individualHoldingDocFrontFile, @@ -563,7 +563,7 @@ public static function fromArray(array $data): self idDocType: IdentificationDocument::from($data['id_doc_type']), idDocFrontFile: $data['id_doc_front_file'], idDocBackFile: $data['id_doc_back_file'] ?? null, - aipriseValidationKey: $data['aiprise_validation_key'], + aipriseValidationKey: $data['aiprise_validation_key'] ?? null, sourceOfFundsDocType: $data['source_of_funds_doc_type'], sourceOfFundsDocFile: $data['source_of_funds_doc_file'], individualHoldingDocFrontFile: $data['individual_holding_doc_front_file'], @@ -621,7 +621,7 @@ public function __construct( public string $incorporationDocFile, public string $proofOfOwnershipDocFile, public ?string $externalId, - public string $aipriseValidationKey, + public ?string $aipriseValidationKey, ?array $fraudWarnings = null, ?bool $isFbo = null, ?AccountPurpose $accountPurpose = null, @@ -705,7 +705,7 @@ public static function fromArray(array $data): self incorporationDocFile: $data['incorporation_doc_file'], proofOfOwnershipDocFile: $data['proof_of_ownership_doc_file'], externalId: $data['external_id'] ?? null, - aipriseValidationKey: $data['aiprise_validation_key'], + aipriseValidationKey: $data['aiprise_validation_key'] ?? null, fraudWarnings: isset($data['fraud_warnings']) ? array_map( fn ($w) => FraudWarning::fromArray($w), $data['fraud_warnings'] diff --git a/src/Types/Rail.php b/src/Types/Rail.php index 50a4cec..d1dcdc9 100644 --- a/src/Types/Rail.php +++ b/src/Types/Rail.php @@ -15,4 +15,6 @@ enum Rail: string case INTERNATIONAL_SWIFT = 'international_swift'; case RTP = 'rtp'; case PIX_SAFE = 'pix_safe'; + case TED = 'ted'; + case SEPA = 'sepa'; } diff --git a/tests/Resources/BankAccounts/BankAccountsTest.php b/tests/Resources/BankAccounts/BankAccountsTest.php index a376db9..3b9a966 100644 --- a/tests/Resources/BankAccounts/BankAccountsTest.php +++ b/tests/Resources/BankAccounts/BankAccountsTest.php @@ -537,10 +537,9 @@ public function it_gets_a_bank_account(): void public function it_lists_bank_accounts(): void { $mockedBankAccounts = [ - 'data' => [ - [ - 'id' => 'ba_000000000000', - 'type' => 'wire', + [ + 'id' => 'ba_000000000000', + 'type' => 'wire', 'name' => 'Bank Account Name', 'pix_key' => '14947677768', 'beneficiary_name' => 'Individual full name or business name', @@ -596,7 +595,6 @@ public function it_lists_bank_accounts(): void ], ], 'created_at' => '2021-01-01T00:00:00Z', - ], ], ]; From 644b13918d6157a6d4ffb4fe270bc71b750410b1 Mon Sep 17 00:00:00 2001 From: alvseven Date: Tue, 16 Jun 2026 00:38:08 -0300 Subject: [PATCH 4/6] fix: apply pint array_indentation formatting --- .../BankAccounts/BankAccountsTest.php | 108 +++++++++--------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/tests/Resources/BankAccounts/BankAccountsTest.php b/tests/Resources/BankAccounts/BankAccountsTest.php index 3b9a966..c17ac90 100644 --- a/tests/Resources/BankAccounts/BankAccountsTest.php +++ b/tests/Resources/BankAccounts/BankAccountsTest.php @@ -540,61 +540,61 @@ public function it_lists_bank_accounts(): void [ 'id' => 'ba_000000000000', 'type' => 'wire', - 'name' => 'Bank Account Name', - 'pix_key' => '14947677768', - 'beneficiary_name' => 'Individual full name or business name', - 'routing_number' => '012345678', - 'account_number' => '1001001234', - 'account_type' => 'checking', - 'account_class' => 'individual', - 'address_line_1' => 'Address line 1', - 'address_line_2' => 'Address line 2', - 'city' => 'City', - 'state_province_region' => 'State/Province/Region', - 'country' => 'US', - 'postal_code' => 'Postal code', - 'spei_protocol' => 'SPEI', - 'spei_institution_code' => '40002', - 'spei_clabe' => '5482347403740546', - 'transfers_type' => 'CVU', - 'transfers_account' => 'BM123123123123', - 'ach_cop_beneficiary_first_name' => 'Fernando', - 'ach_cop_beneficiary_last_name' => 'Guzman Alarcón', - 'ach_cop_document_id' => '1661105408', - 'ach_cop_document_type' => 'CC', - 'ach_cop_email' => 'fernando.guzman@gmail.com', - 'ach_cop_bank_code' => '051', - 'ach_cop_bank_account' => '12345678', - 'swift_code_bic' => '123456789', - 'swift_account_holder_name' => 'John Doe', - 'swift_account_number_iban' => '123456789', - 'swift_beneficiary_address_line_1' => '123 Main Street, Suite 100, Downtown District, City Center CP 12345', - 'swift_beneficiary_address_line_2' => '456 Oak Avenue, Building 7, Financial District, Business Center CP 54321', - 'swift_beneficiary_country' => 'MX', - 'swift_beneficiary_city' => 'City', - 'swift_beneficiary_state_province_region' => 'District', - 'swift_beneficiary_postal_code' => '11530', - 'swift_bank_name' => 'Banco Regional SA', - 'swift_bank_address_line_1' => '123 Main Street, Suite 100, Downtown District, City Center CP 12345', - 'swift_bank_address_line_2' => '456 Oak Avenue, Building 7, Financial District, Business Center CP 54321', - 'swift_bank_country' => 'MX', - 'swift_bank_city' => 'City', - 'swift_bank_state_province_region' => 'District', - 'swift_bank_postal_code' => '11530', - 'swift_intermediary_bank_swift_code_bic' => 'AEIBARB1', - 'swift_intermediary_bank_account_number_iban' => '123456789', - 'swift_intermediary_bank_name' => 'Banco Regional SA', - 'swift_intermediary_bank_country' => 'US', - 'tron_wallet_hash' => 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', - 'offramp_wallets' => [ - [ - 'address' => 'TALJN9zTTEL9TVBb4WuTt6wLvPqJZr3hvb', - 'id' => 'ow_000000000000', - 'network' => 'tron', - 'external_id' => 'your_external_id', - ], + 'name' => 'Bank Account Name', + 'pix_key' => '14947677768', + 'beneficiary_name' => 'Individual full name or business name', + 'routing_number' => '012345678', + 'account_number' => '1001001234', + 'account_type' => 'checking', + 'account_class' => 'individual', + 'address_line_1' => 'Address line 1', + 'address_line_2' => 'Address line 2', + 'city' => 'City', + 'state_province_region' => 'State/Province/Region', + 'country' => 'US', + 'postal_code' => 'Postal code', + 'spei_protocol' => 'SPEI', + 'spei_institution_code' => '40002', + 'spei_clabe' => '5482347403740546', + 'transfers_type' => 'CVU', + 'transfers_account' => 'BM123123123123', + 'ach_cop_beneficiary_first_name' => 'Fernando', + 'ach_cop_beneficiary_last_name' => 'Guzman Alarcón', + 'ach_cop_document_id' => '1661105408', + 'ach_cop_document_type' => 'CC', + 'ach_cop_email' => 'fernando.guzman@gmail.com', + 'ach_cop_bank_code' => '051', + 'ach_cop_bank_account' => '12345678', + 'swift_code_bic' => '123456789', + 'swift_account_holder_name' => 'John Doe', + 'swift_account_number_iban' => '123456789', + 'swift_beneficiary_address_line_1' => '123 Main Street, Suite 100, Downtown District, City Center CP 12345', + 'swift_beneficiary_address_line_2' => '456 Oak Avenue, Building 7, Financial District, Business Center CP 54321', + 'swift_beneficiary_country' => 'MX', + 'swift_beneficiary_city' => 'City', + 'swift_beneficiary_state_province_region' => 'District', + 'swift_beneficiary_postal_code' => '11530', + 'swift_bank_name' => 'Banco Regional SA', + 'swift_bank_address_line_1' => '123 Main Street, Suite 100, Downtown District, City Center CP 12345', + 'swift_bank_address_line_2' => '456 Oak Avenue, Building 7, Financial District, Business Center CP 54321', + 'swift_bank_country' => 'MX', + 'swift_bank_city' => 'City', + 'swift_bank_state_province_region' => 'District', + 'swift_bank_postal_code' => '11530', + 'swift_intermediary_bank_swift_code_bic' => 'AEIBARB1', + 'swift_intermediary_bank_account_number_iban' => '123456789', + 'swift_intermediary_bank_name' => 'Banco Regional SA', + 'swift_intermediary_bank_country' => 'US', + 'tron_wallet_hash' => 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', + 'offramp_wallets' => [ + [ + 'address' => 'TALJN9zTTEL9TVBb4WuTt6wLvPqJZr3hvb', + 'id' => 'ow_000000000000', + 'network' => 'tron', + 'external_id' => 'your_external_id', ], - 'created_at' => '2021-01-01T00:00:00Z', + ], + 'created_at' => '2021-01-01T00:00:00Z', ], ]; From 37c2f4f6cd1d9af9c1b8183a83ddcd6317501ebf Mon Sep 17 00:00:00 2001 From: alvseven Date: Tue, 16 Jun 2026 00:46:06 -0300 Subject: [PATCH 5/6] fix: decode payouts/payins list as plain arrays (API returns [] not {data,pagination}) --- src/Resources/Payins/Payins.php | 5 +---- src/Resources/Payouts/Payouts.php | 5 +---- tests/Resources/Payins/PayinsTest.php | 10 +--------- tests/Resources/Payouts/PayoutsTest.php | 12 +----------- 4 files changed, 4 insertions(+), 28 deletions(-) diff --git a/src/Resources/Payins/Payins.php b/src/Resources/Payins/Payins.php index bd5d4a1..122fcf4 100644 --- a/src/Resources/Payins/Payins.php +++ b/src/Resources/Payins/Payins.php @@ -8,7 +8,6 @@ use BlindPay\SDK\Types\BlindPayApiResponse; use BlindPay\SDK\Types\ManualExecutionStatus; use BlindPay\SDK\Types\Network; -use BlindPay\SDK\Types\PaginationMetadata; use BlindPay\SDK\Types\PaginationParams; use BlindPay\SDK\Types\StablecoinToken; use BlindPay\SDK\Types\TrackingComplete; @@ -316,7 +315,6 @@ public function toArray(): array { public function __construct( public array $data, - public PaginationMetadata $pagination ) {} public static function fromArray(array $data): self @@ -324,9 +322,8 @@ public static function fromArray(array $data): self return new self( data: array_map( fn (array $item) => Payin::fromArray($item), - $data['data'] + $data ), - pagination: PaginationMetadata::fromArray($data['pagination']) ); } } diff --git a/src/Resources/Payouts/Payouts.php b/src/Resources/Payouts/Payouts.php index 6a20b56..8c525e1 100644 --- a/src/Resources/Payouts/Payouts.php +++ b/src/Resources/Payouts/Payouts.php @@ -11,7 +11,6 @@ use BlindPay\SDK\Types\Country; use BlindPay\SDK\Types\Currency; use BlindPay\SDK\Types\Network; -use BlindPay\SDK\Types\PaginationMetadata; use BlindPay\SDK\Types\PaginationParams; use BlindPay\SDK\Types\Rail; use BlindPay\SDK\Types\StablecoinToken; @@ -222,7 +221,6 @@ public function toArray(): array { public function __construct( public array $data, - public PaginationMetadata $pagination ) {} public static function fromArray(array $data): self @@ -230,9 +228,8 @@ public static function fromArray(array $data): self return new self( data: array_map( fn (array $item) => Payout::fromArray($item), - $data['data'] + $data ), - pagination: PaginationMetadata::fromArray($data['pagination']) ); } } diff --git a/tests/Resources/Payins/PayinsTest.php b/tests/Resources/Payins/PayinsTest.php index dd64cab..db5e6a1 100644 --- a/tests/Resources/Payins/PayinsTest.php +++ b/tests/Resources/Payins/PayinsTest.php @@ -147,12 +147,7 @@ private function getPayinMockData(): array public function it_lists_payins(): void { $mockedPayins = [ - 'data' => [$this->getPayinMockData()], - 'pagination' => [ - 'has_more' => true, - 'next_page' => 3, - 'prev_page' => 1, - ], + $this->getPayinMockData(), ]; $this->mockResponse($mockedPayins); @@ -165,9 +160,6 @@ public function it_lists_payins(): void $this->assertCount(1, $response->data->data); $this->assertEquals('re_000000000000', $response->data->data[0]->id); $this->assertEquals('re_000000000000', $response->data->data[0]->receiverId); - $this->assertTrue($response->data->pagination->hasMore); - $this->assertEquals(3, $response->data->pagination->nextPage); - $this->assertEquals(1, $response->data->pagination->prevPage); } #[Test] diff --git a/tests/Resources/Payouts/PayoutsTest.php b/tests/Resources/Payouts/PayoutsTest.php index fd1a5ef..f8131a8 100644 --- a/tests/Resources/Payouts/PayoutsTest.php +++ b/tests/Resources/Payouts/PayoutsTest.php @@ -155,14 +155,7 @@ private function getPayoutMockData(): array public function it_lists_payouts(): void { $mockedPayouts = [ - 'data' => [ - $this->getPayoutMockData(), - ], - 'pagination' => [ - 'has_more' => true, - 'next_page' => 3, - 'prev_page' => 1, - ], + $this->getPayoutMockData(), ]; $this->mockResponse($mockedPayouts); @@ -176,9 +169,6 @@ public function it_lists_payouts(): void $this->assertEquals('pa_000000000000', $response->data->data[0]->id); $this->assertEquals('re_000000000000', $response->data->data[0]->receiverId); $this->assertEquals('0x123...890', $response->data->data[0]->senderWalletAddress); - $this->assertTrue($response->data->pagination->hasMore); - $this->assertEquals(3, $response->data->pagination->nextPage); - $this->assertEquals(1, $response->data->pagination->prevPage); } #[Test] From e57d72a05ba7e5f200427e6572a39a2480767b83 Mon Sep 17 00:00:00 2001 From: alvseven Date: Tue, 16 Jun 2026 00:52:23 -0300 Subject: [PATCH 6/6] fix: resolve leftover conflict markers in Payouts fromArray --- src/Resources/Payouts/Payouts.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Resources/Payouts/Payouts.php b/src/Resources/Payouts/Payouts.php index 8c525e1..4722b56 100644 --- a/src/Resources/Payouts/Payouts.php +++ b/src/Resources/Payouts/Payouts.php @@ -183,12 +183,8 @@ public static function fromArray(array $data): self tedBranchCode: $data['ted_branch_code'] ?? null, tedCpfCnpj: $data['ted_cpf_cnpj'] ?? null, billingFeeAmount: isset($data['billing_fee_amount']) ? (float) $data['billing_fee_amount'] : null, -<<<<<<< HEAD cpnPaymentId: $data['cpn_payment_id'] ?? null, senderLegalName: $data['sender_legal_name'] ?? null -======= - cpnPaymentId: $data['cpn_payment_id'] ?? null ->>>>>>> 4d7c0ad (feat: add SEPA + api-sync content (mirrors node #50)) ); } }