Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- Paginated list models no longer throw a `TypeError` when the gateway returns an entry that is not an object: `webhooks`, `company_statements`, `clients`, `client recurring tokens`, `billing_templates` and `billing template clients` now skip non-array entries instead of failing inside the `fromArray()` closure (`Argument #1 ($w) must be of type array, string given`).
- `account->balance()` and `account->turnover()` return `[]` when the response is not an object, instead of a `TypeError` from the declared `array` return type (`Return value must be of type array, string returned`).

## [2.1.1] - 2026-10-23

### Fixed
Expand Down
8 changes: 6 additions & 2 deletions lib/Model/Billing/BillingTemplateClientList.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@ class BillingTemplateClientList implements \JsonSerializable
public static function fromArray(array $data): self
{
$list = new self();
$list->results = isset($data['results']) && is_array($data['results'])
? array_map(fn (array $r) => BillingTemplateClient::fromArray($r), $data['results'])
$results = $data['results'] ?? null;
$list->results = is_array($results)
? array_values(array_map(
fn (array $r) => BillingTemplateClient::fromArray($r),
array_filter($results, 'is_array')
))
: null;
$list->next = $data['next'] ?? null;
$list->previous = $data['previous'] ?? null;
Expand Down
8 changes: 6 additions & 2 deletions lib/Model/Billing/BillingTemplateList.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@ class BillingTemplateList implements \JsonSerializable
public static function fromArray(array $data): self
{
$list = new self();
$list->results = isset($data['results']) && is_array($data['results'])
? array_map(fn (array $r) => BillingTemplate::fromArray($r), $data['results'])
$results = $data['results'] ?? null;
$list->results = is_array($results)
? array_values(array_map(
fn (array $r) => BillingTemplate::fromArray($r),
array_filter($results, 'is_array')
))
: null;
$list->next = $data['next'] ?? null;
$list->previous = $data['previous'] ?? null;
Expand Down
11 changes: 7 additions & 4 deletions lib/Model/ClientList.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@ class ClientList implements \JsonSerializable
public static function fromArray(array $data): self
{
$list = new self();
$list->results = array_map(
fn (array $c) => ClientDetails::fromArray($c),
$data['results'] ?? []
);
$results = $data['results'] ?? [];
$list->results = is_array($results)
? array_values(array_map(
fn (array $c) => ClientDetails::fromArray($c),
array_filter($results, 'is_array')
))
: [];
$list->next = $data['next'] ?? null;
$list->previous = $data['previous'] ?? null;

Expand Down
11 changes: 7 additions & 4 deletions lib/Model/ClientRecurringTokenList.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@ class ClientRecurringTokenList implements \JsonSerializable
public static function fromArray(array $data): self
{
$list = new self();
$list->results = array_map(
fn (array $t) => ClientRecurringToken::fromArray($t),
$data['results'] ?? []
);
$results = $data['results'] ?? [];
$list->results = is_array($results)
? array_values(array_map(
fn (array $t) => ClientRecurringToken::fromArray($t),
array_filter($results, 'is_array')
))
: [];
$list->next = $data['next'] ?? null;
$list->previous = $data['previous'] ?? null;

Expand Down
11 changes: 7 additions & 4 deletions lib/Model/CompanyStatementList.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@ class CompanyStatementList implements \JsonSerializable
public static function fromArray(array $data): self
{
$list = new self();
$list->results = array_map(
fn (array $s) => CompanyStatement::fromArray($s),
$data['results'] ?? []
);
$results = $data['results'] ?? [];
$list->results = is_array($results)
? array_values(array_map(
fn (array $s) => CompanyStatement::fromArray($s),
array_filter($results, 'is_array')
))
: [];
$list->next = $data['next'] ?? null;
$list->previous = $data['previous'] ?? null;

Expand Down
11 changes: 7 additions & 4 deletions lib/Model/WebhookList.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@ class WebhookList implements \JsonSerializable
public static function fromArray(array $data): self
{
$list = new self();
$list->results = array_map(
fn (array $w) => Webhook::fromArray($w),
$data['results'] ?? []
);
$results = $data['results'] ?? [];
$list->results = is_array($results)
? array_values(array_map(
fn (array $w) => Webhook::fromArray($w),
array_filter($results, 'is_array')
))
: [];
$list->next = $data['next'] ?? null;
$list->previous = $data['previous'] ?? null;

Expand Down
10 changes: 8 additions & 2 deletions lib/Resource/AccountResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@ public function balance(array $filters = []): array
]);

$json = json_encode($response);
$decoded = json_decode($json !== false ? $json : '[]', true);

return json_decode($json !== false ? $json : '[]', true);
// These endpoints answer with an object; a scalar or plain-text body must
// not turn into a TypeError from the declared array return type.
return is_array($decoded) ? $decoded : [];
}

/**
Expand All @@ -38,7 +41,10 @@ public function turnover(array $filters = []): array
]);

$json = json_encode($response);
$decoded = json_decode($json !== false ? $json : '[]', true);

return json_decode($json !== false ? $json : '[]', true);
// These endpoints answer with an object; a scalar or plain-text body must
// not turn into a TypeError from the declared array return type.
return is_array($decoded) ? $decoded : [];
}
}
34 changes: 34 additions & 0 deletions tests/ResponseShapeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -176,4 +176,38 @@ public function testClientsIterateStopsOnNonStringNext(): void

$this->assertSame([], iterator_to_array($api->clients->iterate()));
}

public function testListSkipsNonArrayResults(): void
{
$api = $this->api([new Response(200, [], '{"results": ["x", {"id": "wh_1"}], "next": null, "previous": null}')]);

$list = $api->webhooks->list();

$this->assertIsArray($list->results);
$this->assertCount(1, $list->results);
$this->assertSame('wh_1', $list->results[0]->id);
}

public function testBillingTemplateListSkipsNonArrayResults(): void
{
$api = $this->api([new Response(200, [], '{"results": ["x"], "next": null, "previous": null}')]);

$list = $api->billing->listTemplates();

$this->assertSame([], $list->results);
}

public function testBalanceReturnsEmptyArrayForNonObjectBody(): void
{
$api = $this->api([new Response(200, [], '"not-an-object"')]);

$this->assertSame([], $api->account->balance());
}

public function testTurnoverReturnsEmptyArrayForNonObjectBody(): void
{
$api = $this->api([new Response(200, [], '"not-an-object"')]);

$this->assertSame([], $api->account->turnover());
}
}
Loading