From ae115a50bf4e9ada95e4e397aec4d76d03a93299 Mon Sep 17 00:00:00 2001 From: Eric Viana Date: Mon, 27 Jul 2026 12:17:45 -0300 Subject: [PATCH 1/2] feat: add customer.* webhook events and customer_id filters (wave 1) Adds the parts of the receivers-to-customers rename that the currently deployed API already supports, ahead of blindpay-v2 PR #1799: - WebhookEvents gains CUSTOMER_NEW/CUSTOMER_UPDATE/CUSTOMER_DELETE cases, additive alongside the existing RECEIVER_NEW/RECEIVER_UPDATE cases (now marked @deprecated). The deployed WebhookEndpointIn.events enum already lists the customer.* values and dual-emits, so subscribing today is safe. - ListPayinsInput and ListPayoutsInput gain an additive customerId filter mapped to customer_id, since GET /payins and GET /payouts already accept that query param today. receiverId/receiver_id is kept for backward compatibility. - Fix two live TypeErrors in Customers.php: Owner::fromArray and LimitIncreaseRequest::fromArray were reading customer_id for nested fields the deployed API still keys receiver_id (no dual-emit at that nesting level), so any business customer with owners or any getLimitIncreaseRequests() call threw today. - Delete src/Types/WebhookEvent.php, a dead duplicate enum that also failed php -l from a duplicated case. - Document the Customers/CustomersWrapper resource in CLAUDE.md (previously undocumented) and fix stale receiver_id/receivers references in doc examples that no longer match the shipped code. - Bump VERSION to 2.5.0 (minor, additive only). The other 11 field renames (customer_local_amount, customer_type, customer_kyc_status, etc.), RECEIVERS_* error codes, and removing the legacy Receivers resource are intentionally NOT included here: the deployed API does not accept those yet and shipping them now would break every user of this SDK until blindpay-v2 PR #1799 deploys. Claude-Session: https://claude.ai/code/session_01F1stiNzuNtJXoXtiW9ZCbs --- CLAUDE.md | 14 +++++----- src/BlindPay.php | 2 +- src/Resources/Customers/Customers.php | 4 +-- src/Resources/Payins/Payins.php | 5 ++++ src/Resources/Payouts/Payouts.php | 5 ++++ src/Resources/Webhooks/Webhooks.php | 12 +++++++++ src/Types/WebhookEvent.php | 33 ----------------------- tests/Resources/Webhooks/WebhooksTest.php | 21 +++++++++++++++ 8 files changed, 54 insertions(+), 42 deletions(-) delete mode 100644 src/Types/WebhookEvent.php diff --git a/CLAUDE.md b/CLAUDE.md index a7c8270..cb422eb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -22,7 +22,10 @@ blindpay-php/ Payins.php # Resource class + inline types (Payin, ListPayinsInput, etc.) PayinsWrapper.php # Wrapper delegating to Payins + exposing Quotes sub-resource Quotes.php # Sub-resource class + inline types - Receivers/ # Complex resource with sub-resource + Customers/ # Complex resource with sub-resource + Customers.php # Resource class + inline types + CustomersWrapper.php # Wrapper delegating to Customers + exposing BankAccounts + Receivers/ # Deprecated alias of Customers, kept for backward compatibility until v3.0.0 Receivers.php # Resource class + inline types ReceiversWrapper.php # Wrapper delegating to Receivers + exposing BankAccounts Wallets/ # Aggregation wrapper (no base methods, only sub-resources) @@ -67,13 +70,12 @@ blindpay-php/ StablecoinToken.php # enum: USDC, USDT, USDB TransactionDocumentType.php TransactionStatus.php # enum: refunded, processing, completed, failed, on_hold, pending_review - WebhookEvent.php # enum: receiver.new, payout.new, payin.new, etc. tests/ Resources/ # Mirrors src/Resources structure Available/AvailableTest.php Payins/PayinsTest.php Payins/PayinQuotesTest.php - Receivers/ReceiversTest.php + Receivers/ReceiversTest.php # No Customers test file yet; Receivers is exercised until callers migrate Wallets/BlockchainWalletsTest.php Wallets/OfframpWalletsTest.php ... (one test file per resource) @@ -440,7 +442,7 @@ Create a new file in `src/Types/`. Use `readonly class` with `fromArray()` and o Sub-resources are used when a resource has logically grouped child endpoints. There are two wrapper patterns: -### Pattern A: Delegation wrapper (Payins, Receivers) +### Pattern A: Delegation wrapper (Payins, Customers, Receivers) The wrapper delegates the base resource's methods AND exposes sub-resources as public properties. @@ -507,7 +509,7 @@ readonly class WalletsWrapper } ``` -Usage: `$blindpay->wallets->blockchain->list($receiverId)` +Usage: `$blindpay->wallets->blockchain->list($customerId)` ## 8. Testing @@ -682,4 +684,4 @@ When translating an OpenAPI spec change to SDK code: ### Nested endpoints -Nested endpoints like `instances/{id}/receivers/{receiverId}/bank-accounts` are modeled as sub-resources accessed via wrappers: `$blindpay->receivers->bankAccounts->list($receiverId)`. +Nested endpoints like `instances/{id}/customers/{customerId}/bank-accounts` are modeled as sub-resources accessed via wrappers: `$blindpay->customers->bankAccounts->list($customerId)`. diff --git a/src/BlindPay.php b/src/BlindPay.php index 9cf0a24..f5d9f14 100644 --- a/src/BlindPay.php +++ b/src/BlindPay.php @@ -42,7 +42,7 @@ class BlindPay implements ApiClientInterface { private const BASE_URL = 'https://api.blindpay.com/v1/'; - private const VERSION = '2.4.0'; + private const VERSION = '2.5.0'; private Client $httpClient; diff --git a/src/Resources/Customers/Customers.php b/src/Resources/Customers/Customers.php index 98c3ccc..63fe240 100644 --- a/src/Resources/Customers/Customers.php +++ b/src/Resources/Customers/Customers.php @@ -135,7 +135,7 @@ public static function fromArray(array $data): self return new self( id: $data['id'], instanceId: $data['instance_id'], - customerId: $data['customer_id'], + customerId: $data['receiver_id'], role: OwnerRole::from($data['role']), firstName: $data['first_name'], lastName: $data['last_name'], @@ -1192,7 +1192,7 @@ public static function fromArray(array $data): self { return new self( id: $data['id'], - customerId: $data['customer_id'], + customerId: $data['receiver_id'], status: LimitIncreaseRequestStatus::from($data['status']), daily: (float) $data['daily'], monthly: (float) $data['monthly'], diff --git a/src/Resources/Payins/Payins.php b/src/Resources/Payins/Payins.php index 122fcf4..a8b5878 100644 --- a/src/Resources/Payins/Payins.php +++ b/src/Resources/Payins/Payins.php @@ -287,6 +287,7 @@ public static function fromArray(array $data): self public function __construct( public ?TransactionStatus $status = null, public ?string $receiverId = null, + public ?string $customerId = null, ?int $limit = null, ?int $offset = null, ?string $startingAfter = null, @@ -307,6 +308,10 @@ public function toArray(): array $params['receiver_id'] = $this->receiverId; } + if ($this->customerId !== null) { + $params['customer_id'] = $this->customerId; + } + return $params; } } diff --git a/src/Resources/Payouts/Payouts.php b/src/Resources/Payouts/Payouts.php index 4722b56..e90cae9 100644 --- a/src/Resources/Payouts/Payouts.php +++ b/src/Resources/Payouts/Payouts.php @@ -193,6 +193,7 @@ public static function fromArray(array $data): self { public function __construct( public ?string $receiverId = null, + public ?string $customerId = null, ?int $limit = null, ?int $offset = null, ?string $startingAfter = null, @@ -209,6 +210,10 @@ public function toArray(): array $params['receiver_id'] = $this->receiverId; } + if ($this->customerId !== null) { + $params['customer_id'] = $this->customerId; + } + return $params; } } diff --git a/src/Resources/Webhooks/Webhooks.php b/src/Resources/Webhooks/Webhooks.php index a514c74..bba3a75 100644 --- a/src/Resources/Webhooks/Webhooks.php +++ b/src/Resources/Webhooks/Webhooks.php @@ -10,8 +10,20 @@ enum WebhookEvents: string { + /** + * @deprecated 2.5.0 Use CUSTOMER_NEW instead. Will be removed in v3.0.0. + * See https://www.blindpay.com/changelog/2026-06-04-customers-rename + */ case RECEIVER_NEW = 'receiver.new'; + + /** + * @deprecated 2.5.0 Use CUSTOMER_UPDATE instead. Will be removed in v3.0.0. + * See https://www.blindpay.com/changelog/2026-06-04-customers-rename + */ case RECEIVER_UPDATE = 'receiver.update'; + case CUSTOMER_NEW = 'customer.new'; + case CUSTOMER_UPDATE = 'customer.update'; + case CUSTOMER_DELETE = 'customer.delete'; case BANK_ACCOUNT_NEW = 'bankAccount.new'; case PAYOUT_NEW = 'payout.new'; case PAYOUT_UPDATE = 'payout.update'; diff --git a/src/Types/WebhookEvent.php b/src/Types/WebhookEvent.php deleted file mode 100644 index 2fc91bf..0000000 --- a/src/Types/WebhookEvent.php +++ /dev/null @@ -1,33 +0,0 @@ -assertEquals('we_000000000000', $response->data->id); } + #[Test] + public function it_creates_a_webhook_endpoint_with_customer_events(): void + { + $mockedWebhookEndpoint = [ + 'id' => 'we_000000000000', + ]; + + $this->mockResponse($mockedWebhookEndpoint); + + $input = new CreateWebhookEndpointInput( + url: 'https://example.com/webhook', + events: [WebhookEvents::CUSTOMER_NEW, WebhookEvents::CUSTOMER_UPDATE, WebhookEvents::CUSTOMER_DELETE] + ); + + $response = $this->blindpay->instances->webhookEndpoints->create($input); + + $this->assertTrue($response->isSuccess()); + $this->assertNull($response->error); + $this->assertEquals('we_000000000000', $response->data->id); + } + #[Test] public function it_lists_webhook_endpoints(): void { From 38f6025bb65d5b1b6be809753ed661243a3a20fe Mon Sep 17 00:00:00 2001 From: Eric Viana Date: Mon, 27 Jul 2026 13:17:08 -0300 Subject: [PATCH 2/2] fix(customers): restore receiver.delete webhook event, fix owner/limit-increase id parsing RECEIVER_DELETE was dropped from the active WebhookEvents enum when WebhookEvent.php was deleted, silently breaking the only way to subscribe to receiver.delete, which the API still emits. Restored it as @deprecated. LimitIncreaseRequest::fromArray read receiver_id, but the response middleware only ever supplies customer_id on this route; switched to customer_id. Owner::fromArray read receiver_id, but nested owners[] elements are not covered by the response middleware, so customer_id is not present there today. Made customerId nullable and read customer_id first, falling back to receiver_id, so it works both before and after the customer_id migration lands server-side. Claude-Session: https://claude.ai/code/session_01F1stiNzuNtJXoXtiW9ZCbs --- src/Resources/Customers/Customers.php | 10 +++++----- src/Resources/Webhooks/Webhooks.php | 6 ++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Resources/Customers/Customers.php b/src/Resources/Customers/Customers.php index 63fe240..955cd4a 100644 --- a/src/Resources/Customers/Customers.php +++ b/src/Resources/Customers/Customers.php @@ -107,7 +107,6 @@ enum OwnerTaxType: string public function __construct( public string $id, public string $instanceId, - public string $customerId, public OwnerRole $role, public string $firstName, public string $lastName, @@ -127,7 +126,8 @@ public function __construct( public string $proofOfAddressDocFile, public ?int $ownershipPercentage = null, public ?string $title = null, - public ?OwnerTaxType $taxType = null + public ?OwnerTaxType $taxType = null, + public ?string $customerId = null ) {} public static function fromArray(array $data): self @@ -135,7 +135,6 @@ public static function fromArray(array $data): self return new self( id: $data['id'], instanceId: $data['instance_id'], - customerId: $data['receiver_id'], role: OwnerRole::from($data['role']), firstName: $data['first_name'], lastName: $data['last_name'], @@ -155,7 +154,8 @@ public static function fromArray(array $data): self proofOfAddressDocFile: $data['proof_of_address_doc_file'], ownershipPercentage: isset($data['ownership_percentage']) ? (int) $data['ownership_percentage'] : null, title: $data['title'] ?? null, - taxType: isset($data['tax_type']) ? OwnerTaxType::from($data['tax_type']) : null + taxType: isset($data['tax_type']) ? OwnerTaxType::from($data['tax_type']) : null, + customerId: $data['customer_id'] ?? $data['receiver_id'] ?? null ); } @@ -1192,7 +1192,7 @@ public static function fromArray(array $data): self { return new self( id: $data['id'], - customerId: $data['receiver_id'], + customerId: $data['customer_id'], status: LimitIncreaseRequestStatus::from($data['status']), daily: (float) $data['daily'], monthly: (float) $data['monthly'], diff --git a/src/Resources/Webhooks/Webhooks.php b/src/Resources/Webhooks/Webhooks.php index bba3a75..9d9a1a2 100644 --- a/src/Resources/Webhooks/Webhooks.php +++ b/src/Resources/Webhooks/Webhooks.php @@ -21,6 +21,12 @@ enum WebhookEvents: string * See https://www.blindpay.com/changelog/2026-06-04-customers-rename */ case RECEIVER_UPDATE = 'receiver.update'; + + /** + * @deprecated 2.5.0 Use CUSTOMER_DELETE instead. Will be removed in v3.0.0. + * See https://www.blindpay.com/changelog/2026-06-04-customers-rename + */ + case RECEIVER_DELETE = 'receiver.delete'; case CUSTOMER_NEW = 'customer.new'; case CUSTOMER_UPDATE = 'customer.update'; case CUSTOMER_DELETE = 'customer.delete';