Speed up QR create/update by cutting billing round-trips.
Deploy Ladill QR Plus / deploy (push) Successful in 2m2s
Deploy Ladill QR Plus / deploy (push) Successful in 2m2s
Create was making three sequential public HTTPS billing calls (~400ms each) while holding a DB transaction open for image generation. Use loopback DNS forcing, cache balances briefly, return balance from debit, regenerate images only when style changes, and keep remote work outside the DB transaction.
This commit is contained in:
@@ -36,6 +36,8 @@ class QrCodeManagerService
|
||||
{
|
||||
$wallet = $this->walletFor($user);
|
||||
|
||||
// Fast path: one cached balance read. Debit is still authoritative (402 if
|
||||
// funds are short). Avoids a second can-afford HTTP round-trip.
|
||||
if (! $wallet->canCreateQr()) {
|
||||
throw new RuntimeException('Add at least GHS ' . number_format(QrWallet::pricePerQr(), 2) . ' to your QR wallet before creating codes.');
|
||||
}
|
||||
@@ -44,7 +46,8 @@ class QrCodeManagerService
|
||||
$validated = $this->payloadValidator->validateForCreate($type, $data);
|
||||
$style = QrStyleDefaults::merge($data['style'] ?? null);
|
||||
|
||||
return DB::transaction(function () use ($user, $wallet, $data, $type, $validated, $style) {
|
||||
// Persist the code first (short DB transaction — no remote billing/image work).
|
||||
$qrCode = DB::transaction(function () use ($user, $data, $type, $validated, $style) {
|
||||
$documentId = null;
|
||||
$content = $validated['content'];
|
||||
|
||||
@@ -175,7 +178,7 @@ class QrCodeManagerService
|
||||
$payload['wifi_encoded'] = \App\Support\Qr\QrWifiPayload::encode($content);
|
||||
}
|
||||
|
||||
$qrCode = QrCode::create([
|
||||
return QrCode::create([
|
||||
'user_id' => $user->id,
|
||||
'short_code' => $shortCode,
|
||||
'type' => $type,
|
||||
@@ -186,12 +189,20 @@ class QrCodeManagerService
|
||||
'is_active' => true,
|
||||
'destination_updated_at' => now(),
|
||||
]);
|
||||
|
||||
$this->billing->debitForQrCreation($wallet, $qrCode);
|
||||
$this->imageGenerator->generateAndStore($qrCode);
|
||||
|
||||
return $qrCode->fresh(['document']);
|
||||
});
|
||||
|
||||
try {
|
||||
$this->billing->debitForQrCreation($wallet, $qrCode);
|
||||
} catch (RuntimeException $e) {
|
||||
// Roll back the unpaid code so a failed debit cannot leave a free QR.
|
||||
$qrCode->delete();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// Image render is local CPU work — keep it outside the DB transaction.
|
||||
$this->imageGenerator->generateAndStore($qrCode);
|
||||
|
||||
return $qrCode->fresh(['document']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -331,9 +342,10 @@ class QrCodeManagerService
|
||||
$content['cover_path'] = $this->storeMenuBrandImage($qrCode->user, $data['itinerary_cover'], 'itinerary-covers');
|
||||
}
|
||||
|
||||
$previousStyle = $style;
|
||||
|
||||
if (isset($data['style']) && is_array($data['style'])) {
|
||||
$style = QrStyleDefaults::merge(array_merge($style, $data['style']));
|
||||
$regenerate = true;
|
||||
}
|
||||
|
||||
if (($data['logo'] ?? null) instanceof UploadedFile) {
|
||||
@@ -341,11 +353,16 @@ class QrCodeManagerService
|
||||
$regenerate = true;
|
||||
}
|
||||
|
||||
if (($data['remove_logo'] ?? false) && $style['logo_path']) {
|
||||
if (($data['remove_logo'] ?? false) && ($style['logo_path'] ?? null)) {
|
||||
$style['logo_path'] = null;
|
||||
$regenerate = true;
|
||||
}
|
||||
|
||||
// Only re-render the PNG/SVG when style actually changed (forms always post style[]).
|
||||
if (! $regenerate && $this->styleChanged($previousStyle, $style)) {
|
||||
$regenerate = true;
|
||||
}
|
||||
|
||||
$qrCode->destination_url = $destinationUrl;
|
||||
// Preserve frozen keys (e.g. wifi_encoded) so the printed WiFi code stays put.
|
||||
$payload = (array) ($qrCode->payload ?? []);
|
||||
@@ -361,6 +378,20 @@ class QrCodeManagerService
|
||||
return $qrCode->fresh(['document']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $before
|
||||
* @param array<string, mixed> $after
|
||||
*/
|
||||
private function styleChanged(array $before, array $after): bool
|
||||
{
|
||||
$before = QrStyleDefaults::merge($before);
|
||||
$after = QrStyleDefaults::merge($after);
|
||||
ksort($before);
|
||||
ksort($after);
|
||||
|
||||
return $before !== $after;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject uploaded item images into the sections array.
|
||||
* Form field: item_images[sectionIndex][itemIndex] (UploadedFile)
|
||||
|
||||
Reference in New Issue
Block a user