From 32266f6dd56adb836e03b60a51d5e5be7b97ece2 Mon Sep 17 00:00:00 2001 From: Joel Male Date: Mon, 27 Jul 2026 07:15:59 +0300 Subject: [PATCH] fix: revert JSON serialization workaround, upgrade SDK to 6.3.1 instead v2.0.1's useJson() workaround was wrong: the JSON request serializer silently drops nested single-object fields (e.g. CustomerRef, which QuickBooks requires), and the JSON response deserializer never returns a typed entity, so $result->Id is always null downstream. That breaks every dsCall() site, worse than the bug it was meant to fix. The actual root cause was that quickbooks/v3-php-sdk 5.4.7 predates QuickBooks Online's new invoice AllowOnlinePayPalPayment field, so its XML deserializer throws on responses that include it. SDK 6.3.1 declares the field, so upgrading resolves the original error while keeping the fully-supported XML path (and closes out the SDK bump Dependabot already had queued in #3). Verified: deserializing a response containing AllowOnlinePayPalPayment throws RuntimeException on 5.4.7 and returns a correctly typed IPPInvoice (with Id populated) on 6.3.1. --- composer.json | 4 ++-- src/Client.php | 12 +++--------- tests/ClientTest.php | 22 ---------------------- 3 files changed, 5 insertions(+), 33 deletions(-) diff --git a/composer.json b/composer.json index b67e9ed..85be75b 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "wearepixel/laravel-quickbooks", - "version": "2.0.1", + "version": "2.0.2", "description": "Laravel Client for QuickBooks.", "keywords": [ "quickbooks", @@ -22,7 +22,7 @@ "illuminate/routing": "^10.0|^11.0|^12.0", "illuminate/support": "^10.0|^11.0|^12.0", "nesbot/carbon": "^2.62.1|^3.0", - "quickbooks/v3-php-sdk": "^5.4.6" + "quickbooks/v3-php-sdk": "^6.3.1" }, "require-dev": { "laravel/pint": "^1.2", diff --git a/src/Client.php b/src/Client.php index 0b88f62..c18510d 100644 --- a/src/Client.php +++ b/src/Client.php @@ -181,12 +181,6 @@ public function hasValidRefreshToken(): bool * 2) Have valid refresh token, so renew access token & then use * 3) No existing token, so need to link account * - * useJson() is applied in every case: the SDK's default XML deserializer reflects - * declared properties on its Data classes and throws when QuickBooks adds a new - * response field the installed SDK version doesn't know about yet. The JSON - * deserializer builds instances from the class's own properties and silently - * ignores unrecognised keys instead, so it tolerates QuickBooks-side schema drift. - * * @throws SdkException * @throws ServiceException */ @@ -209,7 +203,7 @@ protected function makeDataService(): DataService 'QBORealmID' => $this->token->realm_id, 'refreshTokenKey' => $this->token->refresh_token, ]), - )->useJson(); + ); } // Have refresh, so update access & allow app to run @@ -220,7 +214,7 @@ protected function makeDataService(): DataService 'QBORealmID' => $this->token->realm_id, 'refreshTokenKey' => $this->token->refresh_token, ]), - )->useJson(); + ); $oauth_token = $data_service->getOAuth2LoginHelper()->refreshToken(); @@ -232,7 +226,7 @@ protected function makeDataService(): DataService } // Create new... - return DataService::Configure($this->parseDataConfigs())->useJson(); + return DataService::Configure($this->parseDataConfigs()); } /** diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 4110f23..ebd30b4 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -1,6 +1,5 @@ skip('Once we figure out how to test around the static DataService::Configure'); -it('configures the data service to use JSON serialization so unrecognised QuickBooks response fields do not throw', function () { - $this->token_mock - ->shouldReceive('getAttribute') - ->twice() - ->with('hasValidAccessToken') - ->andReturnFalse(); - - $this->token_mock - ->shouldReceive('getAttribute') - ->once() - ->with('hasValidRefreshToken') - ->andReturnFalse(); - - $format = $this->client - ->getDataService() - ->getServiceContext() - ->IppConfiguration->Message->Request->SerializationFormat; - - expect($format)->toBe(SerializationFormat::Json); -}); - it('has logging off by default') ->skip('Have to figure out how to test this with the new code in 5.x');