fix: switch DataService to JSON serialization to survive QuickBooks schema drift

The SDK's XML deserializer reflects on declared properties of its Data
classes and throws a RuntimeException when QuickBooks returns a field
the installed SDK version doesn't define yet (e.g. the new invoice
AllowOnlinePayPalPayment field), breaking every invoice create/update.
The JSON path builds instances from the class's own known properties
and silently ignores unrecognised response fields instead, so it
tolerates QuickBooks adding fields ahead of the SDK.
This commit is contained in:
Joel Male
2026-07-27 07:03:21 +03:00
parent 473103c243
commit 9002e0ebca
3 changed files with 88 additions and 60 deletions
+57 -57
View File
@@ -1,61 +1,61 @@
{ {
"name": "wearepixel/laravel-quickbooks", "name": "wearepixel/laravel-quickbooks",
"version": "2.0.0", "version": "2.0.1",
"description": "Laravel Client for QuickBooks.", "description": "Laravel Client for QuickBooks.",
"keywords": [ "keywords": [
"quickbooks", "quickbooks",
"client", "client",
"laravel", "laravel",
"wearepixel" "wearepixel"
], ],
"license": "MIT", "license": "MIT",
"authors": [ "authors": [
{ {
"name": "Pixel", "name": "Pixel",
"email": "hello@wearepixel.com.au" "email": "hello@wearepixel.com.au"
} }
], ],
"require": { "require": {
"php": "^8.3", "php": "^8.3",
"illuminate/database": "^10.0|^11.0|^12.0", "illuminate/database": "^10.0|^11.0|^12.0",
"illuminate/http": "^10.0|^11.0|^12.0", "illuminate/http": "^10.0|^11.0|^12.0",
"illuminate/routing": "^10.0|^11.0|^12.0", "illuminate/routing": "^10.0|^11.0|^12.0",
"illuminate/support": "^10.0|^11.0|^12.0", "illuminate/support": "^10.0|^11.0|^12.0",
"nesbot/carbon": "^2.62.1|^3.0", "nesbot/carbon": "^2.62.1|^3.0",
"quickbooks/v3-php-sdk": "^5.4.6" "quickbooks/v3-php-sdk": "^5.4.6"
},
"require-dev": {
"laravel/pint": "^1.2",
"mockery/mockery": "^1.6",
"pestphp/pest": "^3.0",
"psy/psysh": "^0.12"
},
"autoload": {
"psr-4": {
"Wearepixel\\QuickBooks\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Wearepixel\\QuickBooks\\": "tests/"
}, },
"files": [ "require-dev": {
"tests/Overrides.php" "laravel/pint": "^1.2",
] "mockery/mockery": "^1.6",
}, "pestphp/pest": "^3.0",
"extra": { "psy/psysh": "^0.12"
"laravel": {
"providers": [
"Wearepixel\\QuickBooks\\Providers\\ServiceProvider",
"Wearepixel\\QuickBooks\\Providers\\ClientServiceProvider"
]
}
},
"config": {
"allow-plugins": {
"pestphp/pest-plugin": true
}, },
"sort-packages": true "autoload": {
}, "psr-4": {
"prefer-stable": true "Wearepixel\\QuickBooks\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Wearepixel\\QuickBooks\\": "tests/"
},
"files": [
"tests/Overrides.php"
]
},
"extra": {
"laravel": {
"providers": [
"Wearepixel\\QuickBooks\\Providers\\ServiceProvider",
"Wearepixel\\QuickBooks\\Providers\\ClientServiceProvider"
]
}
},
"config": {
"allow-plugins": {
"pestphp/pest-plugin": true
},
"sort-packages": true
},
"prefer-stable": true
} }
+9 -3
View File
@@ -181,6 +181,12 @@ public function hasValidRefreshToken(): bool
* 2) Have valid refresh token, so renew access token & then use * 2) Have valid refresh token, so renew access token & then use
* 3) No existing token, so need to link account * 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 SdkException
* @throws ServiceException * @throws ServiceException
*/ */
@@ -203,7 +209,7 @@ protected function makeDataService(): DataService
'QBORealmID' => $this->token->realm_id, 'QBORealmID' => $this->token->realm_id,
'refreshTokenKey' => $this->token->refresh_token, 'refreshTokenKey' => $this->token->refresh_token,
]), ]),
); )->useJson();
} }
// Have refresh, so update access & allow app to run // Have refresh, so update access & allow app to run
@@ -214,7 +220,7 @@ protected function makeDataService(): DataService
'QBORealmID' => $this->token->realm_id, 'QBORealmID' => $this->token->realm_id,
'refreshTokenKey' => $this->token->refresh_token, 'refreshTokenKey' => $this->token->refresh_token,
]), ]),
); )->useJson();
$oauth_token = $data_service->getOAuth2LoginHelper()->refreshToken(); $oauth_token = $data_service->getOAuth2LoginHelper()->refreshToken();
@@ -226,7 +232,7 @@ protected function makeDataService(): DataService
} }
// Create new... // Create new...
return DataService::Configure($this->parseDataConfigs()); return DataService::Configure($this->parseDataConfigs())->useJson();
} }
/** /**
+22
View File
@@ -1,5 +1,6 @@
<?php <?php
use QuickBooksOnline\API\Core\Http\Serialization\SerializationFormat;
use QuickBooksOnline\API\DataService\DataService; use QuickBooksOnline\API\DataService\DataService;
use QuickBooksOnline\API\Exception\ServiceException; use QuickBooksOnline\API\Exception\ServiceException;
use Wearepixel\QuickBooks\Client; use Wearepixel\QuickBooks\Client;
@@ -130,6 +131,27 @@
it('returns a report service using the data service') it('returns a report service using the data service')
->skip('Once we figure out how to test around the static DataService::Configure'); ->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') it('has logging off by default')
->skip('Have to figure out how to test this with the new code in 5.x'); ->skip('Have to figure out how to test this with the new code in 5.x');