Merge branch 'fix/elastic-migrations-idempotency' of https://github.com/turbo124/invoiceninja into fix/elastic-migrations-idempotency

This commit is contained in:
David Bomba 2025-11-26 13:34:43 +11:00
commit 019a688047
20 changed files with 322 additions and 205 deletions

View File

@ -1 +1 @@
5.12.34
5.12.35

View File

@ -335,12 +335,11 @@ class RebuildElasticIndexes extends Command
$this->line(" Waiting for our {$expectedJobCount} jobs to complete...");
$this->line(" (Tracking: pending + processing jobs)", 'comment');
$maxWaitSeconds = 600;
$startTime = time();
$lastReportedDelta = -1;
$stableCount = 0;
while ((time() - $startTime) < $maxWaitSeconds) {
while (true) {
try {
$currentJobCount = $this->getTotalActiveJobCount($connection, $queueName);
$delta = $currentJobCount - $baselineJobCount;
@ -371,13 +370,6 @@ class RebuildElasticIndexes extends Command
return;
}
}
try {
$finalCount = $this->getTotalActiveJobCount($connection, $queueName);
$this->warn(" ⚠ Timeout after {$maxWaitSeconds}s (active: {$finalCount}, baseline: {$baselineJobCount})");
} catch (\Exception $e) {
$this->warn(" ⚠ Timeout after {$maxWaitSeconds}s - continuing");
}
}
protected function getTotalActiveJobCount(string $connection, string $queueName): int

View File

@ -135,13 +135,13 @@ class LoginController extends BaseController
if (strlen($request->input('one_time_password')) == 0 || !$google2fa->verifyKey(decrypt($user->google_2fa_secret), $request->input('one_time_password'))) {
return response()
->json(['message' => ctrans('texts.invalid_one_time_password')], 401)
->json(['message' => ctrans('texts.invalid_one_time_password')], 422)
->header('X-App-Version', config('ninja.app_version'))
->header('X-Api-Version', config('ninja.minimum_client_version'));
}
} elseif (strlen($user->google_2fa_secret ?? '') > 2 && !$request->has('one_time_password')) {
return response()
->json(['message' => ctrans('texts.invalid_one_time_password')], 401)
->json(['message' => ctrans('texts.invalid_one_time_password')], 422)
->header('X-App-Version', config('ninja.app_version'))
->header('X-Api-Version', config('ninja.minimum_client_version'));
}

View File

@ -26,9 +26,16 @@ class EmailPreferencesController extends Controller
{
public function index(string $entity, string $invitation_key, Request $request): \Illuminate\View\View
{
request()->session()->invalidate();
request()->session()->regenerate(true);
request()->session()->regenerateToken();
$class = "\\App\\Models\\".ucfirst(Str::camel($entity)).'Invitation';
$invitation = $class::where('key', $invitation_key)->firstOrFail();
auth()->guard('contact')->loginUsingId($invitation->contact->id, true);
$data['receive_emails'] = $invitation->contact->is_locked ? false : true;
$data['company'] = $invitation->company;

View File

@ -294,6 +294,8 @@ class InvoicePay extends Component
'payable_invoices' => $payable_invoices,
]);
$this->dispatch(self::CONTEXT_READY);
}
public function render(): \Illuminate\Contracts\View\Factory|\Illuminate\View\View

View File

@ -28,42 +28,49 @@ class InvoiceSummary extends Component
public $gateway_fee;
public $isReady = false;
#[On(self::CONTEXT_READY)]
public function onContextReady(): void
{
$this->isReady = true;
$this->loadContextData();
}
public function mount()
{
$_context = $this->getContext();
$contact = $_context['contact'] ?? auth()->guard('contact')->user();
$this->invoices = $_context['payable_invoices'];
$this->amount = Number::formatMoney($_context['amount'], $contact->client);
$this->gateway_fee = isset($_context['gateway_fee']) ? Number::formatMoney($_context['gateway_fee'], $contact->client) : false;
if (!empty($_context)) {
$this->isReady = true;
$this->loadContextData();
}
}
private function loadContextData(): void
{
$_context = $this->getContext();
if (empty($_context)) {
return;
}
$contact = $_context['contact'] ?? auth()->guard('contact')->user();
$this->invoices = $_context['payable_invoices'] ?? [];
$this->amount = isset($_context['amount']) ? Number::formatMoney($_context['amount'], $contact->client) : '';
$this->gateway_fee = isset($_context['gateway_fee']) ? Number::formatMoney($_context['gateway_fee'], $contact->client) : false;
}
#[On(self::CONTEXT_UPDATE)]
public function onContextUpdate(): void
{
$_context = $this->getContext();
// refactor logic for updating the price for eg if it changes with under/over pay
$contact = $_context['contact'] ?? auth()->guard('contact')->user();
$this->invoices = $_context['payable_invoices'];
$this->amount = Number::formatMoney($_context['amount'], $contact->client);
$this->gateway_fee = isset($_context['gateway_fee']) ? Number::formatMoney($_context['gateway_fee'], $contact->client) : false;
$this->loadContextData();
}
#[On('payment-view-rendered')]
public function handlePaymentViewRendered()
public function handlePaymentViewRendered(): void
{
$_context = $this->getContext();
$contact = $_context['contact'] ?? auth()->guard('contact')->user();
$this->amount = Number::formatMoney($_context['amount'], $contact->client);
$this->gateway_fee = isset($_context['gateway_fee']) ? Number::formatMoney($_context['gateway_fee'], $contact->client) : false;
$this->loadContextData();
}
public function downloadDocument($invoice_hashed_id)
@ -91,10 +98,13 @@ class InvoiceSummary extends Component
public function render(): \Illuminate\Contracts\View\Factory|\Illuminate\View\View
{
$contact = $this->getContext()['contact'] ?? auth()->guard('contact')->user();
$_context = $this->getContext();
$contact = $_context['contact'] ?? auth()->guard('contact')->user();
return render('flow2.invoices-summary', [
'client' => $contact->client,
'client' => $contact->client ?? null,
'isReady' => $this->isReady,
]);
}

View File

@ -27,10 +27,6 @@ class ClientGatewayTokenRepository extends BaseRepository
$client_gateway_token->company_gateway_id = $data['company_gateway_id'];
}
if (isset($data['is_default']) && !boolval($data['is_default'])) {
$client_gateway_token->is_default = false;
}
$client_gateway_token->save();
if (isset($data['is_default']) && boolval($data['is_default'])) {
@ -45,6 +41,7 @@ class ClientGatewayTokenRepository extends BaseRepository
ClientGatewayToken::withTrashed()
->where('company_id', $client_gateway_token->company_id)
->where('client_id', $client_gateway_token->client_id)
->where('id', '!=', $client_gateway_token->id)
->update(['is_default' => false]);
$client_gateway_token->is_default = true;

View File

@ -14,10 +14,11 @@ namespace App\Services\EDocument\Standards\Verifactu;
use Illuminate\Support\Facades\Http;
use App\Services\EDocument\Standards\Verifactu\ResponseProcessor;
use App\Services\EDocument\Standards\Verifactu\Signing\SigningService;
class AeatClient
{
private string $base_url = 'https://www1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP';
private string $base_url = 'https://www1.agenciatributaria.gob.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP';
private string $sandbox_url = 'https://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP';
@ -61,8 +62,34 @@ class AeatClient
return $this;
}
/**
* Sign SOAP envelope with XML Digital Signature
*
* @param string $xml - Unsigned SOAP envelope
* @return string - Signed SOAP envelope
*/
private function signSoapEnvelope(string $xml): string
{
try {
$signingService = new SigningService(
$xml,
file_get_contents($this->ssl_key),
file_get_contents($this->certificate)
);
return $signingService->sign();
} catch (\Exception $e) {
nlog("Error signing SOAP envelope: " . $e->getMessage());
throw $e;
}
}
public function send($xml): array
{
// Sign the SOAP envelope before sending
$signed_xml = $this->signSoapEnvelope($xml);
nlog("AEAT Request URL: " . $this->base_url);
nlog("Signed SOAP envelope size: " . strlen($signed_xml) . " bytes");
$response = Http::withHeaders([
'Content-Type' => 'text/xml; charset=utf-8',
@ -74,11 +101,13 @@ class AeatClient
'verify' => false,
'timeout' => 30,
])
->withBody($xml, 'text/xml')
->withBody($signed_xml, 'text/xml')
->post($this->base_url);
$success = $response->successful();
nlog("AEAT Response HTTP Code: " . $response->status());
$responseProcessor = new ResponseProcessor();
$parsedResponse = $responseProcessor->processResponse($response->body());

View File

@ -957,6 +957,7 @@ class PdfMock
'$contact.first_name' => 'Geo',
'$company.vat_number' => 'vat number',
'$contact.signature' => '',
'$verifactu_qr_code' => '',
'$product.tax_name1' => '',
'$product.tax_name2' => '',
'$product.tax_name3' => '',

View File

@ -168,7 +168,7 @@ class TaxProvider
private function taxShippingAddress(): bool
{
if ($this->client->shipping_country_id == "840" && strlen($this->client->shipping_postal_code) > 3) {
if ($this->client->shipping_country_id == "840" && strlen($this->client->shipping_postal_code ?? '') > 3) {
return true;
}

View File

@ -49,24 +49,24 @@ class ClientGatewayTokenTransformer extends EntityTransformer
{
$casted = new stdClass();
if (property_exists($meta, 'exp_month')) {
$casted->exp_month = (string) $meta->exp_month;
if ($exp_month = data_get($meta, 'exp_month')) {
$casted->exp_month = (string) $exp_month;
}
if (property_exists($meta, 'exp_year')) {
$casted->exp_year = (string) $meta->exp_year;
if ($exp_year = data_get($meta, 'exp_year')) {
$casted->exp_year = (string) $exp_year;
}
if (property_exists($meta, 'brand')) {
$casted->brand = (string) $meta->brand;
if ($brand = data_get($meta, 'brand')) {
$casted->brand = (string) $brand;
}
if (property_exists($meta, 'last4')) {
$casted->last4 = (string) $meta->last4;
if ($last4 = data_get($meta, 'last4')) {
$casted->last4 = (string) $last4;
}
if (property_exists($meta, 'type')) {
$casted->type = (int) $meta->type;
if ($type = data_get($meta, 'type')) {
$casted->type = (int) $type;
}
return $casted;

View File

@ -17,6 +17,7 @@ use Illuminate\Support\Str;
trait WithSecureContext
{
public const CONTEXT_UPDATE = 'secureContext.updated';
public const CONTEXT_READY = 'flow2.context.ready';
/**
* @throws \Psr\Container\ContainerExceptionInterface
@ -24,19 +25,9 @@ trait WithSecureContext
*/
public function getContext(): mixed
{
$context = \Illuminate\Support\Facades\Cache::get(session()->getId()) ?? false;
if (!$context) {
usleep(300000); //@monitor - inject delay to catch delays in cache updating
$context = \Illuminate\Support\Facades\Cache::get(session()->getId()) ?? [];
}
$context = \Illuminate\Support\Facades\Cache::get(session()->getId()) ?? [];
return $context;
}
public function setContext(string $property, $value): array

View File

@ -220,14 +220,9 @@
"url": "https://github.com/beganovich/php-ansible"
},
{
"type": "vcs",
"url": "https://github.com/turbo124/snappdf"
},
{
"type": "vcs",
"url": "https://github.com/invoiceninja/admin-api"
"type": "path",
"url": "../admin-api"
}
],
"minimum-stability": "dev",
"prefer-stable": true

240
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "b2d1e12e4e351e18abb37f43b9da125f",
"content-hash": "9a8f043b7584acdd2884d1ede7909e33",
"packages": [
{
"name": "afosto/yaac",
@ -61,16 +61,16 @@
},
{
"name": "apimatic/core",
"version": "0.3.14",
"version": "0.3.16",
"source": {
"type": "git",
"url": "https://github.com/apimatic/core-lib-php.git",
"reference": "c3eaad6cf0c00b793ce6d9bee8b87176247da582"
"reference": "ae4ab4ca26a41be41718f33c703d67b7a767c07b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/apimatic/core-lib-php/zipball/c3eaad6cf0c00b793ce6d9bee8b87176247da582",
"reference": "c3eaad6cf0c00b793ce6d9bee8b87176247da582",
"url": "https://api.github.com/repos/apimatic/core-lib-php/zipball/ae4ab4ca26a41be41718f33c703d67b7a767c07b",
"reference": "ae4ab4ca26a41be41718f33c703d67b7a767c07b",
"shasum": ""
},
"require": {
@ -82,7 +82,8 @@
"ext-libxml": "*",
"php": "^7.2 || ^8.0",
"php-jsonpointer/php-jsonpointer": "^3.0.2",
"psr/log": "^1.1.4 || ^2.0.0 || ^3.0.0"
"psr/log": "^1.1.4 || ^2.0.0 || ^3.0.0",
"symfony/http-foundation": "^5.4 || ^6.0 || ^7.0 || ^8.0"
},
"require-dev": {
"phan/phan": "5.4.5",
@ -109,9 +110,9 @@
],
"support": {
"issues": "https://github.com/apimatic/core-lib-php/issues",
"source": "https://github.com/apimatic/core-lib-php/tree/0.3.14"
"source": "https://github.com/apimatic/core-lib-php/tree/0.3.16"
},
"time": "2025-02-27T06:03:30+00:00"
"time": "2025-11-25T04:42:27+00:00"
},
{
"name": "apimatic/core-interfaces",
@ -496,16 +497,16 @@
},
{
"name": "aws/aws-sdk-php",
"version": "3.363.0",
"version": "3.363.2",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
"reference": "b2f78a0787a73801957eb329048d52b4181e9660"
"reference": "f8b5f125248daa8942144b4771c041a63ec41900"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/b2f78a0787a73801957eb329048d52b4181e9660",
"reference": "b2f78a0787a73801957eb329048d52b4181e9660",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/f8b5f125248daa8942144b4771c041a63ec41900",
"reference": "f8b5f125248daa8942144b4771c041a63ec41900",
"shasum": ""
},
"require": {
@ -587,9 +588,9 @@
"support": {
"forum": "https://github.com/aws/aws-sdk-php/discussions",
"issues": "https://github.com/aws/aws-sdk-php/issues",
"source": "https://github.com/aws/aws-sdk-php/tree/3.363.0"
"source": "https://github.com/aws/aws-sdk-php/tree/3.363.2"
},
"time": "2025-11-21T19:41:10+00:00"
"time": "2025-11-25T19:04:55+00:00"
},
{
"name": "babenkoivan/elastic-adapter",
@ -1011,13 +1012,13 @@
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/turbo124/snappdf.git",
"reference": "73997afb327fb9cd99686368769d2f0562cb3a9f"
"url": "https://github.com/beganovich/snappdf.git",
"reference": "340e877e63ef98db82766a8d8a853d7759cf79fa"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/turbo124/snappdf/zipball/73997afb327fb9cd99686368769d2f0562cb3a9f",
"reference": "73997afb327fb9cd99686368769d2f0562cb3a9f",
"url": "https://api.github.com/repos/beganovich/snappdf/zipball/340e877e63ef98db82766a8d8a853d7759cf79fa",
"reference": "340e877e63ef98db82766a8d8a853d7759cf79fa",
"shasum": ""
},
"require": {
@ -1042,16 +1043,7 @@
"Beganovich\\Snappdf\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Test\\Snappdf\\": "tests/"
}
},
"scripts": {
"tests": [
"@php vendor/bin/phpunit --testdox"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
@ -1063,9 +1055,10 @@
],
"description": "Convert webpages or HTML into the PDF file using Chromium or Google Chrome.",
"support": {
"source": "https://github.com/turbo124/snappdf/tree/master"
"issues": "https://github.com/beganovich/snappdf/issues",
"source": "https://github.com/beganovich/snappdf/tree/v5.0.1"
},
"time": "2025-01-04T00:35:22+00:00"
"time": "2024-11-20T17:31:20+00:00"
},
{
"name": "braintree/braintree_php",
@ -1118,16 +1111,16 @@
},
{
"name": "brick/math",
"version": "0.14.0",
"version": "0.14.1",
"source": {
"type": "git",
"url": "https://github.com/brick/math.git",
"reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2"
"reference": "f05858549e5f9d7bb45875a75583240a38a281d0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/brick/math/zipball/113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2",
"reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2",
"url": "https://api.github.com/repos/brick/math/zipball/f05858549e5f9d7bb45875a75583240a38a281d0",
"reference": "f05858549e5f9d7bb45875a75583240a38a281d0",
"shasum": ""
},
"require": {
@ -1166,7 +1159,7 @@
],
"support": {
"issues": "https://github.com/brick/math/issues",
"source": "https://github.com/brick/math/tree/0.14.0"
"source": "https://github.com/brick/math/tree/0.14.1"
},
"funding": [
{
@ -1174,7 +1167,7 @@
"type": "github"
}
],
"time": "2025-08-29T12:40:03+00:00"
"time": "2025-11-24T14:40:29+00:00"
},
{
"name": "btcpayserver/btcpayserver-greenfield-php",
@ -3035,16 +3028,16 @@
},
{
"name": "google/apiclient-services",
"version": "v0.420.1",
"version": "v0.421.0",
"source": {
"type": "git",
"url": "https://github.com/googleapis/google-api-php-client-services.git",
"reference": "f1200dbf48d02dcfa36c5771f4dbc0433655a7ab"
"reference": "d84e7301a52405677807564dab6b1a112dfd03bd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/f1200dbf48d02dcfa36c5771f4dbc0433655a7ab",
"reference": "f1200dbf48d02dcfa36c5771f4dbc0433655a7ab",
"url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/d84e7301a52405677807564dab6b1a112dfd03bd",
"reference": "d84e7301a52405677807564dab6b1a112dfd03bd",
"shasum": ""
},
"require": {
@ -3073,9 +3066,9 @@
],
"support": {
"issues": "https://github.com/googleapis/google-api-php-client-services/issues",
"source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.420.1"
"source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.421.0"
},
"time": "2025-11-17T01:06:15+00:00"
"time": "2025-11-23T01:06:22+00:00"
},
{
"name": "google/auth",
@ -4835,16 +4828,16 @@
},
{
"name": "josemmo/facturae-php",
"version": "v1.8.3",
"version": "v1.8.4",
"source": {
"type": "git",
"url": "https://github.com/josemmo/Facturae-PHP.git",
"reference": "f4099c9479fb770bd03f9c559c054c0fea86fa44"
"reference": "21283e460d2a24d58c06454596fcaecc63c8f123"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/josemmo/Facturae-PHP/zipball/f4099c9479fb770bd03f9c559c054c0fea86fa44",
"reference": "f4099c9479fb770bd03f9c559c054c0fea86fa44",
"url": "https://api.github.com/repos/josemmo/Facturae-PHP/zipball/21283e460d2a24d58c06454596fcaecc63c8f123",
"reference": "21283e460d2a24d58c06454596fcaecc63c8f123",
"shasum": ""
},
"require": {
@ -4886,9 +4879,15 @@
],
"support": {
"issues": "https://github.com/josemmo/Facturae-PHP/issues",
"source": "https://github.com/josemmo/Facturae-PHP/tree/v1.8.3"
"source": "https://github.com/josemmo/Facturae-PHP/tree/v1.8.4"
},
"time": "2025-06-22T08:30:43+00:00"
"funding": [
{
"url": "https://github.com/josemmo",
"type": "github"
}
],
"time": "2025-11-24T14:05:18+00:00"
},
{
"name": "kmukku/php-iso11649",
@ -5079,16 +5078,16 @@
},
{
"name": "laravel/framework",
"version": "v11.46.1",
"version": "v11.46.2",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "5fd457f807570a962a53b403b1346efe4cc80bb8"
"reference": "d6b16e72a98c2ad3257ec6b3f1f00532c3b1c2fc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/5fd457f807570a962a53b403b1346efe4cc80bb8",
"reference": "5fd457f807570a962a53b403b1346efe4cc80bb8",
"url": "https://api.github.com/repos/laravel/framework/zipball/d6b16e72a98c2ad3257ec6b3f1f00532c3b1c2fc",
"reference": "d6b16e72a98c2ad3257ec6b3f1f00532c3b1c2fc",
"shasum": ""
},
"require": {
@ -5290,7 +5289,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2025-09-30T14:51:32+00:00"
"time": "2025-11-25T19:02:06+00:00"
},
{
"name": "laravel/octane",
@ -5384,16 +5383,16 @@
},
{
"name": "laravel/prompts",
"version": "v0.3.7",
"version": "v0.3.8",
"source": {
"type": "git",
"url": "https://github.com/laravel/prompts.git",
"reference": "a1891d362714bc40c8d23b0b1d7090f022ea27cc"
"reference": "096748cdfb81988f60090bbb839ce3205ace0d35"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/prompts/zipball/a1891d362714bc40c8d23b0b1d7090f022ea27cc",
"reference": "a1891d362714bc40c8d23b0b1d7090f022ea27cc",
"url": "https://api.github.com/repos/laravel/prompts/zipball/096748cdfb81988f60090bbb839ce3205ace0d35",
"reference": "096748cdfb81988f60090bbb839ce3205ace0d35",
"shasum": ""
},
"require": {
@ -5409,7 +5408,7 @@
"require-dev": {
"illuminate/collections": "^10.0|^11.0|^12.0",
"mockery/mockery": "^1.5",
"pestphp/pest": "^2.3|^3.4",
"pestphp/pest": "^2.3|^3.4|^4.0",
"phpstan/phpstan": "^1.12.28",
"phpstan/phpstan-mockery": "^1.1.3"
},
@ -5437,22 +5436,22 @@
"description": "Add beautiful and user-friendly forms to your command-line applications.",
"support": {
"issues": "https://github.com/laravel/prompts/issues",
"source": "https://github.com/laravel/prompts/tree/v0.3.7"
"source": "https://github.com/laravel/prompts/tree/v0.3.8"
},
"time": "2025-09-19T13:47:56+00:00"
"time": "2025-11-21T20:52:52+00:00"
},
{
"name": "laravel/scout",
"version": "v10.22.0",
"version": "v10.22.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/scout.git",
"reference": "5f629471eed80c97a1fa2f12a2fb213c7e09f729"
"reference": "13ed8e0eeaddd894bf360b85cb873980de19dbaf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/scout/zipball/5f629471eed80c97a1fa2f12a2fb213c7e09f729",
"reference": "5f629471eed80c97a1fa2f12a2fb213c7e09f729",
"url": "https://api.github.com/repos/laravel/scout/zipball/13ed8e0eeaddd894bf360b85cb873980de19dbaf",
"reference": "13ed8e0eeaddd894bf360b85cb873980de19dbaf",
"shasum": ""
},
"require": {
@ -5473,10 +5472,9 @@
"algolia/algoliasearch-client-php": "^3.2|^4.0",
"meilisearch/meilisearch-php": "^1.0",
"mockery/mockery": "^1.0",
"orchestra/testbench": "^7.31|^8.11|^9.0|^10.0",
"orchestra/testbench": "^7.31|^8.36|^9.15|^10.8",
"php-http/guzzle7-adapter": "^1.0",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^9.3|^10.4|^11.5",
"typesense/typesense-php": "^4.9.3"
},
"suggest": {
@ -5520,20 +5518,20 @@
"issues": "https://github.com/laravel/scout/issues",
"source": "https://github.com/laravel/scout"
},
"time": "2025-11-12T17:00:55+00:00"
"time": "2025-11-25T15:19:35+00:00"
},
{
"name": "laravel/serializable-closure",
"version": "v2.0.6",
"version": "v2.0.7",
"source": {
"type": "git",
"url": "https://github.com/laravel/serializable-closure.git",
"reference": "038ce42edee619599a1debb7e81d7b3759492819"
"reference": "cb291e4c998ac50637c7eeb58189c14f5de5b9dd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/serializable-closure/zipball/038ce42edee619599a1debb7e81d7b3759492819",
"reference": "038ce42edee619599a1debb7e81d7b3759492819",
"url": "https://api.github.com/repos/laravel/serializable-closure/zipball/cb291e4c998ac50637c7eeb58189c14f5de5b9dd",
"reference": "cb291e4c998ac50637c7eeb58189c14f5de5b9dd",
"shasum": ""
},
"require": {
@ -5542,7 +5540,7 @@
"require-dev": {
"illuminate/support": "^10.0|^11.0|^12.0",
"nesbot/carbon": "^2.67|^3.0",
"pestphp/pest": "^2.36|^3.0",
"pestphp/pest": "^2.36|^3.0|^4.0",
"phpstan/phpstan": "^2.0",
"symfony/var-dumper": "^6.2.0|^7.0.0"
},
@ -5581,20 +5579,20 @@
"issues": "https://github.com/laravel/serializable-closure/issues",
"source": "https://github.com/laravel/serializable-closure"
},
"time": "2025-10-09T13:42:30+00:00"
"time": "2025-11-21T20:52:36+00:00"
},
{
"name": "laravel/slack-notification-channel",
"version": "v3.6.0",
"version": "v3.7.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/slack-notification-channel.git",
"reference": "642677a57490eebccb7e9fb666f5a5379c6e3459"
"reference": "414aec57b487bfbac7f90fc30f50a2f0a2df4caa"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/slack-notification-channel/zipball/642677a57490eebccb7e9fb666f5a5379c6e3459",
"reference": "642677a57490eebccb7e9fb666f5a5379c6e3459",
"url": "https://api.github.com/repos/laravel/slack-notification-channel/zipball/414aec57b487bfbac7f90fc30f50a2f0a2df4caa",
"reference": "414aec57b487bfbac7f90fc30f50a2f0a2df4caa",
"shasum": ""
},
"require": {
@ -5644,22 +5642,22 @@
],
"support": {
"issues": "https://github.com/laravel/slack-notification-channel/issues",
"source": "https://github.com/laravel/slack-notification-channel/tree/v3.6.0"
"source": "https://github.com/laravel/slack-notification-channel/tree/v3.7.0"
},
"time": "2025-06-26T16:51:38+00:00"
"time": "2025-11-20T17:26:07+00:00"
},
{
"name": "laravel/socialite",
"version": "v5.23.1",
"version": "v5.23.2",
"source": {
"type": "git",
"url": "https://github.com/laravel/socialite.git",
"reference": "83d7523c97c1101d288126948947891319eef800"
"reference": "41e65d53762d33d617bf0253330d672cb95e624b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/socialite/zipball/83d7523c97c1101d288126948947891319eef800",
"reference": "83d7523c97c1101d288126948947891319eef800",
"url": "https://api.github.com/repos/laravel/socialite/zipball/41e65d53762d33d617bf0253330d672cb95e624b",
"reference": "41e65d53762d33d617bf0253330d672cb95e624b",
"shasum": ""
},
"require": {
@ -5675,9 +5673,9 @@
},
"require-dev": {
"mockery/mockery": "^1.0",
"orchestra/testbench": "^4.0|^5.0|^6.0|^7.0|^8.0|^9.0|^10.0",
"orchestra/testbench": "^4.18|^5.20|^6.47|^7.55|^8.36|^9.15|^10.8",
"phpstan/phpstan": "^1.12.23",
"phpunit/phpunit": "^8.0|^9.3|^10.4|^11.5"
"phpunit/phpunit": "^8.0|^9.3|^10.4|^11.5|^12.0"
},
"type": "library",
"extra": {
@ -5718,20 +5716,20 @@
"issues": "https://github.com/laravel/socialite/issues",
"source": "https://github.com/laravel/socialite"
},
"time": "2025-10-27T15:36:41+00:00"
"time": "2025-11-21T14:00:38+00:00"
},
{
"name": "laravel/tinker",
"version": "v2.10.1",
"version": "v2.10.2",
"source": {
"type": "git",
"url": "https://github.com/laravel/tinker.git",
"reference": "22177cc71807d38f2810c6204d8f7183d88a57d3"
"reference": "3bcb5f62d6f837e0f093a601e26badafb127bd4c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/tinker/zipball/22177cc71807d38f2810c6204d8f7183d88a57d3",
"reference": "22177cc71807d38f2810c6204d8f7183d88a57d3",
"url": "https://api.github.com/repos/laravel/tinker/zipball/3bcb5f62d6f837e0f093a601e26badafb127bd4c",
"reference": "3bcb5f62d6f837e0f093a601e26badafb127bd4c",
"shasum": ""
},
"require": {
@ -5782,9 +5780,9 @@
],
"support": {
"issues": "https://github.com/laravel/tinker/issues",
"source": "https://github.com/laravel/tinker/tree/v2.10.1"
"source": "https://github.com/laravel/tinker/tree/v2.10.2"
},
"time": "2025-01-27T14:24:01+00:00"
"time": "2025-11-20T16:29:12+00:00"
},
{
"name": "laravel/ui",
@ -8528,16 +8526,16 @@
},
{
"name": "open-telemetry/api",
"version": "1.7.0",
"version": "1.7.1",
"source": {
"type": "git",
"url": "https://github.com/opentelemetry-php/api.git",
"reference": "610b79ad9d6d97e8368bcb6c4d42394fbb87b522"
"reference": "45bda7efa8fcdd9bdb0daa2f26c8e31f062f49d4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/opentelemetry-php/api/zipball/610b79ad9d6d97e8368bcb6c4d42394fbb87b522",
"reference": "610b79ad9d6d97e8368bcb6c4d42394fbb87b522",
"url": "https://api.github.com/repos/opentelemetry-php/api/zipball/45bda7efa8fcdd9bdb0daa2f26c8e31f062f49d4",
"reference": "45bda7efa8fcdd9bdb0daa2f26c8e31f062f49d4",
"shasum": ""
},
"require": {
@ -8557,7 +8555,7 @@
]
},
"branch-alias": {
"dev-main": "1.7.x-dev"
"dev-main": "1.8.x-dev"
}
},
"autoload": {
@ -8590,11 +8588,11 @@
],
"support": {
"chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V",
"docs": "https://opentelemetry.io/docs/php",
"docs": "https://opentelemetry.io/docs/languages/php",
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
"source": "https://github.com/open-telemetry/opentelemetry-php"
},
"time": "2025-10-02T23:44:28+00:00"
"time": "2025-10-19T10:49:48+00:00"
},
{
"name": "open-telemetry/context",
@ -9480,16 +9478,16 @@
},
{
"name": "phpdocumentor/type-resolver",
"version": "1.11.1",
"version": "1.12.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git",
"reference": "f626740b38009078de0dc8b2b9dc4e7f749c6eba"
"reference": "92a98ada2b93d9b201a613cb5a33584dde25f195"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/f626740b38009078de0dc8b2b9dc4e7f749c6eba",
"reference": "f626740b38009078de0dc8b2b9dc4e7f749c6eba",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/92a98ada2b93d9b201a613cb5a33584dde25f195",
"reference": "92a98ada2b93d9b201a613cb5a33584dde25f195",
"shasum": ""
},
"require": {
@ -9532,22 +9530,22 @@
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
"support": {
"issues": "https://github.com/phpDocumentor/TypeResolver/issues",
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.11.1"
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.12.0"
},
"time": "2025-11-21T11:31:57+00:00"
"time": "2025-11-21T15:09:14+00:00"
},
{
"name": "phpoffice/phpspreadsheet",
"version": "2.4.1",
"version": "2.4.2",
"source": {
"type": "git",
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
"reference": "096ae6faf94b49b2cf53e92a0073133c941e1f57"
"reference": "931ad61fb2c229063fc4e7e665fb52b87249cc56"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/096ae6faf94b49b2cf53e92a0073133c941e1f57",
"reference": "096ae6faf94b49b2cf53e92a0073133c941e1f57",
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/931ad61fb2c229063fc4e7e665fb52b87249cc56",
"reference": "931ad61fb2c229063fc4e7e665fb52b87249cc56",
"shasum": ""
},
"require": {
@ -9568,7 +9566,7 @@
"maennchen/zipstream-php": "^2.1 || ^3.0",
"markbaker/complex": "^3.0",
"markbaker/matrix": "^3.0",
"php": "^8.1",
"php": ">=8.1.0 <8.6.0",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0",
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
@ -9577,7 +9575,7 @@
"dealerdirect/phpcodesniffer-composer-installer": "dev-main",
"dompdf/dompdf": "^2.0 || ^3.0",
"friendsofphp/php-cs-fixer": "^3.2",
"mitoteam/jpgraph": "^10.3",
"mitoteam/jpgraph": "^10.5",
"mpdf/mpdf": "^8.1.1",
"phpcompatibility/php-compatibility": "^9.3",
"phpstan/phpstan": "^1.1",
@ -9588,7 +9586,7 @@
},
"suggest": {
"dompdf/dompdf": "Option for rendering PDF with PDF Writer",
"ext-intl": "PHP Internationalization Functions",
"ext-intl": "PHP Internationalization Functions, required for NumberFormatter Wizard",
"mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers",
"mpdf/mpdf": "Option for rendering PDF with PDF Writer",
"tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer"
@ -9637,9 +9635,9 @@
],
"support": {
"issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/2.4.1"
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/2.4.2"
},
"time": "2025-09-01T18:41:37+00:00"
"time": "2025-11-24T15:59:19+00:00"
},
{
"name": "phpoption/phpoption",
@ -11532,16 +11530,16 @@
},
{
"name": "smalot/pdfparser",
"version": "v2.12.1",
"version": "v2.12.2",
"source": {
"type": "git",
"url": "https://github.com/smalot/pdfparser.git",
"reference": "98d31ba34ef5b5a98897ef4b6c3925d502ea53b1"
"reference": "370b7e983fafecb787a9bcfd73baab8038212ad1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/smalot/pdfparser/zipball/98d31ba34ef5b5a98897ef4b6c3925d502ea53b1",
"reference": "98d31ba34ef5b5a98897ef4b6c3925d502ea53b1",
"url": "https://api.github.com/repos/smalot/pdfparser/zipball/370b7e983fafecb787a9bcfd73baab8038212ad1",
"reference": "370b7e983fafecb787a9bcfd73baab8038212ad1",
"shasum": ""
},
"require": {
@ -11577,9 +11575,9 @@
],
"support": {
"issues": "https://github.com/smalot/pdfparser/issues",
"source": "https://github.com/smalot/pdfparser/tree/v2.12.1"
"source": "https://github.com/smalot/pdfparser/tree/v2.12.2"
},
"time": "2025-07-31T06:19:56+00:00"
"time": "2025-09-04T08:49:09+00:00"
},
{
"name": "socialiteproviders/apple",
@ -18353,7 +18351,7 @@
},
{
"name": "illuminate/json-schema",
"version": "v12.39.0",
"version": "v12.40.1",
"source": {
"type": "git",
"url": "https://github.com/illuminate/json-schema.git",

View File

@ -17,8 +17,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => env('APP_VERSION', '5.12.34'),
'app_tag' => env('APP_TAG', '5.12.34'),
'app_version' => env('APP_VERSION', '5.12.35'),
'app_tag' => env('APP_TAG', '5.12.35'),
'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', false),

File diff suppressed because one or more lines are too long

View File

@ -385,7 +385,7 @@
"src": "resources/js/setup/setup.js"
},
"resources/sass/app.scss": {
"file": "assets/app-9454bf28.css",
"file": "assets/app-55cdafc9.css",
"isEntry": true,
"src": "resources/sass/app.scss"
}

View File

@ -1,4 +1,22 @@
<div class="w-full">
@if(!$isReady)
<div class="rounded-lg border bg-card bg-white text-card-foreground shadow-sm overflow-hidden">
<div class="pt-6 px-6 flex flex-row items-start bg-muted/50">
<div class="grid gap-0.5">
<h3 class="font-semibold tracking-tight text-lg">{{ ctrans('texts.invoices') }}</h3>
</div>
</div>
<div class="p-6 flex items-center justify-center min-h-32">
<div class="flex flex-col items-center gap-2">
<svg class="animate-spin h-8 w-8 text-blue" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<p class="text-sm text-muted-foreground">{{ ctrans('texts.loading') }}</p>
</div>
</div>
</div>
@else
<div class="rounded-lg border bg-card bg-white text-card-foreground shadow-sm overflow-hidden" x-chunk="An order details card with order details, shipping information, customer information and payment information.">
<div class="pt-6 px-6 flex flex-row items-start bg-muted/50">
<div class="grid gap-0.5">
@ -105,4 +123,5 @@
</div>
</div>
</div>
@endif
</div>

View File

@ -1,6 +1,6 @@
@extends('portal.ninja2020.layout.clean') @section('meta_title',
ctrans('texts.preferences')) @section('body')
<div class="flex h-screen">
<div class="flex h-screen" x-data="{ modalOpen: false, actionType: '' }">
<div class="m-auto md:w-1/3 lg:w-1/5">
<div class="flex flex-col items-center">
<img
@ -12,15 +12,16 @@ ctrans('texts.preferences')) @section('body')
{{ ctrans('texts.email_preferences') }}
</h1>
<form class="my-4 flex flex-col items-center text-center" method="post">
@csrf @method('put')
<form id="preferencesForm" class="my-4 flex flex-col items-center text-center" method="post">
@csrf @method('put')
<input type="hidden" name="action" id="actionInput">
@if($receive_emails)
<p>{{ ctrans('texts.subscribe_help') }}</p>
<button
name="action"
value="unsubscribe"
type="button"
@click="modalOpen = true; actionType = 'unsubscribe'"
class="button button-secondary mt-4"
>
{{ ctrans('texts.unsubscribe') }}
@ -29,8 +30,8 @@ ctrans('texts.preferences')) @section('body')
<p>{{ ctrans('texts.unsubscribe_help') }}</p>
<button
name="action"
value="subscribe"
type="button"
@click="modalOpen = true; actionType = 'subscribe'"
class="button button-secondary mt-4"
>
{{ ctrans('texts.subscribe') }}
@ -39,5 +40,54 @@ ctrans('texts.preferences')) @section('body')
</form>
</div>
</div>
<!-- Confirmation Modal -->
<div x-show="modalOpen" class="fixed bottom-0 inset-x-0 px-4 pb-4 sm:inset-0 sm:flex sm:items-center sm:justify-center"
style="display:none;">
<div x-show="modalOpen" x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100" x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0"
class="fixed inset-0 transition-opacity" style="display:none;">
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
</div>
<div x-show="modalOpen" x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100" x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
class="bg-white rounded-lg px-4 pt-5 pb-4 overflow-hidden shadow-xl transform transition-all sm:max-w-lg sm:w-full sm:p-6">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<svg class="h-6 w-6 text-blue-600" stroke="currentColor" fill="none" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
<span x-show="actionType === 'unsubscribe'">{{ ctrans('texts.confirm') }}</span>
<span x-show="actionType === 'subscribe'">{{ ctrans('texts.confirm') }}</span>
</h3>
<div class="mt-2">
<p class="text-sm leading-5 text-gray-500">
<span>{{ ctrans('texts.are_you_sure') }}</span>
</p>
</div>
</div>
</div>
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<button @click="document.getElementById('actionInput').value = actionType; document.getElementById('preferencesForm').submit();"
type="button" class="button button-danger sm:ml-3 sm:w-auto">
<span x-show="actionType === 'unsubscribe'">{{ ctrans('texts.unsubscribe') }}</span>
<span x-show="actionType === 'subscribe'">{{ ctrans('texts.subscribe') }}</span>
</button>
<button @click="modalOpen = false" type="button" class="button button-secondary button-block sm:mt-0 sm:w-auto sm:ml-3">
{{ ctrans('texts.cancel') }}
</button>
</div>
</div>
</div>
</div>
@stop

View File

@ -98,6 +98,32 @@ class ClientGatewayTokenApiTest extends TestCase
}
public function testCompanyGatewaySettableOnTokenAndDefaultIsTrue()
{
$data = [
'client_id' => $this->client->hashed_id,
'company_gateway_id' => $this->cg->hashed_id,
'gateway_type_id' => GatewayType::CREDIT_CARD,
'token' => 'tokey',
'gateway_customer_reference' => 'reffy',
'meta' => '{}',
'is_default' => true,
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->postJson('/api/v1/client_gateway_tokens', $data);
$response->assertStatus(200);
$arr = $response->json();
$t1 = $arr['data']['id'];
$this->assertTrue($arr['data']['is_default']);
}
public function testCompanyGatewaySettableOnToken()
{