diff --git a/src/Traits/Contact.php b/src/Traits/Contact.php index b3aa068..b9ff01f 100644 --- a/src/Traits/Contact.php +++ b/src/Traits/Contact.php @@ -183,6 +183,39 @@ public function bulkCreateContacts(array $data = [], string $version = 'v1'): mi return $this->execute(); } + /** + * Bulk creates or updates contacts, with their nested records, for a specified API version. + * + * Each contact is identified by its `contact_number`: one the platform does not hold is + * created, one it holds is updated. Nested products and bank accounts are matched by `id`, + * then by the caller's own `local_identifier` scoped to that contact, and created when + * neither matches — which is what makes this usable before the caller knows any platform + * ids at all. `bulkUpdateContact` cannot do that: it skips any nested record with no `id`. + * + * A product may name a bank account created in the same request through + * `contact_bank_account_local_identifier`, since that account has no id yet. + * + * @param array $data (Optional) An associative array with a `contacts` key holding the contacts + * to upsert. Keys and values match the contact model's attributes. + * @param string $version (Optional) The version of the API to target. Defaults to 'v1'. + * @return mixed The response from the API, shaped as `success` and `errors` keyed by the index + * each contact was sent at — the same shape the other bulk endpoints return. + * + * @throws \Exception + */ + public function bulkUpsertContacts(array $data = [], string $version = 'v1'): mixed + { + $this->init(); + $this->setVersion($version); + $this->setData([ + 'json' => $data, + ]); + $this->setEndpoint('contacts/bulkUpsert'); + $this->setRequestType('PUT'); + + return $this->execute(); + } + /** * Retrieves the allowed statuses for contacts. * diff --git a/tests/Traits/ContactTest.php b/tests/Traits/ContactTest.php index 7d619d2..941686b 100644 --- a/tests/Traits/ContactTest.php +++ b/tests/Traits/ContactTest.php @@ -105,6 +105,43 @@ expect($result)->toBe($expectedResult); }); +test('bulkUpsertContacts calls expected methods and returns result', function () { + $mock = $this->getMockBuilder(PaymentPlatformAPI::class) + ->onlyMethods(['init', 'setVersion', 'setData', 'setEndpoint', 'setRequestType', 'execute']) + ->getMock(); + + $data = ['contacts' => [['contact_number' => '0000000005012']]]; + $version = 'v3.0'; + + $mock->expects($this->once())->method('init')->willReturnSelf(); + $mock->expects($this->once())->method('setVersion')->with($version)->willReturnSelf(); + $mock->expects($this->once())->method('setData')->with(['json' => $data])->willReturnSelf(); + $mock->expects($this->once())->method('setEndpoint')->with('contacts/bulkUpsert')->willReturnSelf(); + // PUT, not POST: an upsert is idempotent, so it matches bulkUpdate rather than bulkCreate. + $mock->expects($this->once())->method('setRequestType')->with('PUT')->willReturnSelf(); + + $expectedResult = 'upsert-contacts-result'; + $mock->expects($this->once())->method('execute')->willReturn($expectedResult); + + $result = $mock->bulkUpsertContacts($data, $version); + expect($result)->toBe($expectedResult); +}); + +test('bulkUpsertContacts defaults to v1', function () { + $mock = $this->getMockBuilder(PaymentPlatformAPI::class) + ->onlyMethods(['init', 'setVersion', 'setData', 'setEndpoint', 'setRequestType', 'execute']) + ->getMock(); + + $mock->expects($this->once())->method('setVersion')->with('v1')->willReturnSelf(); + $mock->method('init')->willReturnSelf(); + $mock->method('setData')->willReturnSelf(); + $mock->method('setEndpoint')->willReturnSelf(); + $mock->method('setRequestType')->willReturnSelf(); + $mock->method('execute')->willReturn(null); + + $mock->bulkUpsertContacts([]); +}); + test('createContact calls expected methods and returns result', function () { $mock = $this->getMockBuilder(PaymentPlatformAPI::class) ->onlyMethods(['init', 'setVersion', 'setData', 'setEndpoint', 'setRequestType', 'execute'])