3 Commits
Author SHA1 Message Date
Joel Male 9002e0ebca 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.
2026-07-27 07:03:21 +03:00
pixel-workflows[bot] 473103c243 chore: update dependabot configuration 2026-05-27 06:18:22 +00:00
Joel Male f59295773d Create SECURITY.md 2026-05-27 14:48:52 +10:00
5 changed files with 132 additions and 60 deletions
+33
View File
@@ -0,0 +1,33 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 10
groups:
actions:
patterns: ["*"]
commit-message:
prefix: "chore"
include: "scope"
labels:
- "dependencies"
- package-ecosystem: "composer"
directory: "/"
schedule:
interval: "weekly"
day: "tuesday"
open-pull-requests-limit: 10
groups:
php-minor-patch:
update-types:
- "minor"
- "patch"
commit-message:
prefix: "chore"
include: "scope"
labels:
- "dependencies"
+11
View File
@@ -0,0 +1,11 @@
# Security Policy
**PLEASE DON'T DISCLOSE SECURITY-RELATED ISSUES PUBLICLY, [SEE BELOW](#reporting-a-vulnerability).**
## Reporting a Vulnerability
If you discover a security vulnerability, please report it privately using the below channel:
**GitHub Private Vulnerability Reporting** — go to the repository's **Security** tab and click **"Report a vulnerability"**. This creates a private advisory visible only to maintainers and provides a structured workflow for triage, fix coordination, and CVE assignment.
All security vulnerabilities will be promptly addressed.
+57 -57
View File
@@ -1,61 +1,61 @@
{
"name": "wearepixel/laravel-quickbooks",
"version": "2.0.0",
"description": "Laravel Client for QuickBooks.",
"keywords": [
"quickbooks",
"client",
"laravel",
"wearepixel"
],
"license": "MIT",
"authors": [
{
"name": "Pixel",
"email": "hello@wearepixel.com.au"
}
],
"require": {
"php": "^8.3",
"illuminate/database": "^10.0|^11.0|^12.0",
"illuminate/http": "^10.0|^11.0|^12.0",
"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"
},
"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/"
"name": "wearepixel/laravel-quickbooks",
"version": "2.0.1",
"description": "Laravel Client for QuickBooks.",
"keywords": [
"quickbooks",
"client",
"laravel",
"wearepixel"
],
"license": "MIT",
"authors": [
{
"name": "Pixel",
"email": "hello@wearepixel.com.au"
}
],
"require": {
"php": "^8.3",
"illuminate/database": "^10.0|^11.0|^12.0",
"illuminate/http": "^10.0|^11.0|^12.0",
"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"
},
"files": [
"tests/Overrides.php"
]
},
"extra": {
"laravel": {
"providers": [
"Wearepixel\\QuickBooks\\Providers\\ServiceProvider",
"Wearepixel\\QuickBooks\\Providers\\ClientServiceProvider"
]
}
},
"config": {
"allow-plugins": {
"pestphp/pest-plugin": true
"require-dev": {
"laravel/pint": "^1.2",
"mockery/mockery": "^1.6",
"pestphp/pest": "^3.0",
"psy/psysh": "^0.12"
},
"sort-packages": true
},
"prefer-stable": true
"autoload": {
"psr-4": {
"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
* 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
*/
@@ -203,7 +209,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
@@ -214,7 +220,7 @@ protected function makeDataService(): DataService
'QBORealmID' => $this->token->realm_id,
'refreshTokenKey' => $this->token->refresh_token,
]),
);
)->useJson();
$oauth_token = $data_service->getOAuth2LoginHelper()->refreshToken();
@@ -226,7 +232,7 @@ protected function makeDataService(): DataService
}
// Create new...
return DataService::Configure($this->parseDataConfigs());
return DataService::Configure($this->parseDataConfigs())->useJson();
}
/**
+22
View File
@@ -1,5 +1,6 @@
<?php
use QuickBooksOnline\API\Core\Http\Serialization\SerializationFormat;
use QuickBooksOnline\API\DataService\DataService;
use QuickBooksOnline\API\Exception\ServiceException;
use Wearepixel\QuickBooks\Client;
@@ -130,6 +131,27 @@
it('returns a report service using the data service')
->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');