Adds 7 keys referenced by the payware gateway UI in invoiceninja/ui#2967:
- payment_period / payment_period_help (TTL field label + help)
- payware_partner_id_label / payware_partner_id_help
- payware_vpos_id_label / payware_vpos_id_help
- payware_public_key_help
Companion to backend PR #11697 (already merged), which registered the
payware gateway (key b0a6294fca4488c2bab58f3e11e3c623) but did not add
labels/help for its credential and settings fields.
Resolve conflict in database/seeders/PaymentLibrariesSeeder.php: upstream
hid LawPay (gateway id 66) while this branch added payware MOBILE_PAYMENT
(gateway id 67) on the same visible-gateways line. Drop 66, keep 67.
The earlier circuit breaker (60s cooldown after 401) capped the cascade
toward the 5-strike vPOS lockout but didn't prevent it: a misconfigured
gateway saved without testing still trickle-locked the merchant. This
commit closes the loop by reusing IN's existing 'Health Check'
infrastructure (button in invoiceninja/ui Credentials.tsx, route
company_gateways.test in api.php, dispatch to driver->auth()).
Changes:
- Driver implements auth(): real round-trip against /vpos/login,
returns 'ok' or 'error' as a translation key for the React side
to render.
- New PaywareApi::verifyConnection() bypasses the circuit breaker so
admin-initiated tests always get a fresh result, never gated by a
prior failure marker.
- Successful login (admin or customer-flow) now Cache::forget()s the
failure marker. Effect: when an admin fixes broken credentials and
clicks Health Check, customer traffic resumes that instant - no
60-second wait for the cooldown to expire.
- SystemLogger::dispatch(EVENT_GATEWAY_FAILURE) on createTransaction
failure in BankTransfer::paymentData(). Login problems and other
transient gateway failures now show up in the System Logs view with
the technical detail, instead of being only visible via nlog.
- Customer-facing PaymentFailed message replaced with a generic,
translated string (gateway_temporarily_unavailable, en + bg).
Customers no longer see leaked details like 'HTTP 401' or
'previous attempt failed, awaiting cooldown'.
No UI changes are needed: the Health Check button already exists in
the IN admin gateway form, gated on isGatewaySaved.
Modified files:
- app/PaymentDrivers/Payware/BankTransfer.php
- app/PaymentDrivers/Payware/PaywareApi.php
- app/PaymentDrivers/PaywarePaymentDriver.php
- lang/bg/texts.php
- lang/en/texts.php
Webhook integrity (security hardening alignment with server-side updates):
- Verify body via SHA-256 hash carried in JWT contentSha256 header. The
driver was looking up contentMd5 against an md5() hash; the server now
emits SHA-256 per recent server-side hardening, so the prior check was
silently a no-op. Fail-closed if the header is absent.
- Tighten iat freshness to asymmetric +60s/-300s (was symmetric 300s,
allowed future-dated tokens).
- Reject empty transactionId webhooks early (was defaulting to '' and
would have poisoned the new dedup query).
- Filter on callbackType == TRANSACTION_FINALIZED so PROCESSED callbacks
no longer overwrite local status mid-flight.
Idempotency (PR review item 1):
- Check existing Payment by transaction_reference + company_id before
createPayment in the CONFIRMED branch. Prevents duplicate Payment rows
when payware retries up to 15 times on slow IN responses.
Status enum:
- Drop local 'PENDING' string in favour of server's 'ACTIVE'. Aligns the
polling, frontend, and webhook handler on one set of names
(ACTIVE / CONFIRMED / DECLINED / FAILED / CANCELLED / EXPIRED).
Browser compatibility (PR review item 2 plus broader audit):
- Pass event explicitly to copy handler. Was relying on deprecated
window.event, which is undefined in Firefox inside a Promise.then -
the copy feedback was already silently broken there.
- Feature-detect navigator.clipboard + isSecureContext, fall back to
document.execCommand('copy') for plain-HTTP self-hosters.
- Vendor qrcode.js into public/vendor/qrcodejs/ instead of loading from
cdnjs (no SRI, no fallback, blocked under strict CSP). Added an
onerror fallback that displays the payware:// URL as text.
- Drift-free countdown via Date.now() instead of a 1s setInterval that
browsers throttle in background tabs.
- Chained setTimeout polling with AbortController instead of overlapping
setInterval(fetch, 3000). Cancels in-flight fetches on beforeunload.
- 'No compatible app installed' helper text under the mobile pay button.
- Inline English fallback strings replaced with ctrans keys.
Confirmation flow:
- On CONFIRMED, JS now submits #server-response so paymentResponse is
the live confirmation handler that performs the redirect (matches
btcpay/razorpay livewire pattern). Removes the dead AJAX-poll branch
and closes the gap where the redirect URL was client-controlled.
Currency precision:
- PaywareApi::createTransaction accepts currencyPrecision; driver pulls
from client->currency()->precision. JPY (0 decimals) and BHD/KWD
(3 decimals) now serialize correctly.
Login circuit breaker:
- 60s cooldown after a failed /vpos/login. Caps cascading attempts
against payware's 5-strike vPOS lockout when credentials are
misconfigured (without it, 5 customer page loads can lock out the
merchant's vPOS for 15 minutes across all channels).
New 'Mobile Payment' payment type (id 53):
- Generic payment_type for mobile-initiated A2A payments. Mirrors the
existing GatewayType::MOBILE_PAYMENT (id 30) and follows the
precedent of MOLLIE_BANK_TRANSFER (34) and STRIPE_BANK_TRANSFER (50).
Migration extended to seed the row idempotently. Driver now stamps
payments with this type instead of INSTANT_BANK_PAY (which is
GoCardless's brand for their A2A flow). Companion change for the
React side will follow in invoiceninja/ui.
Translations:
- New keys (payment_was_not_completed, no_compatible_app_installed,
payment_type_Mobile Payment) added to lang/en/texts.php as the source
of truth, plus lang/bg/texts.php for completeness. Other locales fall
back to en until community translators sync.
Modified files:
- app/Models/PaymentType.php
- app/PaymentDrivers/Payware/BankTransfer.php
- app/PaymentDrivers/Payware/PaywareApi.php
- app/PaymentDrivers/PaywarePaymentDriver.php
- database/migrations/2026_02_15_000000_add_payware_gateway.php
- lang/bg/texts.php
- lang/en/texts.php
- resources/views/portal/ninja2020/gateways/payware/pay_livewire.blade.php
New file:
- public/vendor/qrcodejs/qrcode.min.js (MIT, qrcodejs 1.0.0)