Appearance
Nexi XPay Greece — Hosted Payment Page Integration
Flow ID: IN-23 | Module(s):
checkout,gift_cards,job,eshop| Complexity: High Last Updated: 2026-05-21
Business Overview
Nexi XPay Greece is a card-payment gateway used by Greek merchants. The integration uses the Hosted Payment Page (HPP) model: the platform creates an order on the Nexi API, receives a redirect URL, and hands the browser off to Nexi's own checkout page. Nexi handles all card-number entry and 3-D Secure challenges. When the customer finishes, Nexi fires a server-to-server webhook and redirects the browser back to the merchant return URLs.
Two independent payment surfaces share the same gateway class:
- Regular checkout (
checkoutmodule) — for standard shop orders stored in theorderstable. - Gift card checkout (
gift_cardsmodule) — for gift card purchases stored ingift_card_orders.
Both environments (sandbox and production) are supported via a single boolean toggle. URLs:
- Sandbox:
https://xpaysandbox.nexigroup.com/api/phoenix-0.0/psp/(src/PaymentGateways/NexiXPay/XPay.php:12) - Production:
https://xpay.nexigroup.com/api/phoenix-0.0/psp/(src/PaymentGateways/NexiXPay/XPay.php:11)
API Reference
HPP Order Creation
Request — POST api/v1/orders/hpp
Headers:
| Header | Value |
|---|---|
X-API-KEY | Merchant API key from registry |
Correlation-Id | UUID v4 generated per request |
Content-Type | application/json |
Payload shape (src/PaymentGateways/NexiXPay/XPay.php:85-103):
order.orderId → merchant order serial
order.amount → cents (EUR×100)
order.currency → 'EUR'
order.customerId → optional
order.description → optional
order.customerInfo.cardHolderEmail → optional
order.customerInfo.mobilePhoneCountryCode → optional
order.customerInfo.mobilePhone → optional
paymentSession.actionType → 'PAY'
paymentSession.recurrence.action → 'NO_RECURRING'
paymentSession.amount → cents
paymentSession.language → ISO 639-2 (default 'ELL' for Greek)
paymentSession.paymentService → 'cards'
paymentSession.resultUrl → success redirect URL
paymentSession.cancelUrl → cancel redirect URL
paymentSession.notificationUrl → server-to-server webhook URLSuccess response (HTTP 200):
| Field | Description |
|---|---|
hostedPage | URL the browser must be redirected to |
orderId | Nexi-side order identifier (stored as tran_ticket) |
securityToken | Opaque token echoed back in every webhook notification |
If the response lacks hostedPage, the method returns null and logs an error (src/PaymentGateways/NexiXPay/XPay.php:156-158).
Order Status Query
Request — GET api/v1/orders/{xpayOrderId} (src/PaymentGateways/NexiXPay/XPay.php:351)
Returns the latest operation from the operations array (index 0). The caller uses operationResult to determine the current payment state.
operationResult Status Mapping
XPay::mapOperationResultToStatus() (src/PaymentGateways/NexiXPay/XPay.php:403-421):
Nexi operationResult | Internal status |
|---|---|
AUTHORIZED, EXECUTED | PAID |
DECLINED, DENIED_BY_RISK, THREEDS_FAILED, VOIDED, FAILED, CANCELED | CANCELED |
THREEDS_VALIDATED, PENDING | PENDING |
REFUNDED | REFUNDED |
| anything else | UNKNOWN |
Webhook Notification Shape
Nexi delivers a JSON body with two top-level keys — operation and order. Required fields:
operation.operationResult(string)order.orderId(merchant order serial)securityToken(root level — echoed from HPP creation response)
XPay::parsePaymentNotification() throws \InvalidArgumentException when operation, order, operationResult, or orderId is absent (src/PaymentGateways/NexiXPay/XPay.php:226-239).
Code Flow
Regular Checkout — Initiation
Entry: Adv_checkout::process_order() dispatches case 'xpay' to xpay() (ecommercen/checkout/controllers/Adv_checkout.php:156).
- Calls
getXPaySettings()to load API key and environment flag (Adv_checkout.php:3422). - Loads order and customer records; redirects to
preview_orderon lookup failure (Adv_checkout.php:3425-3440). - Builds return URLs:
successUrl→checkout/get_response/xpay/success/{serial}(Adv_checkout.php:3445)cancelUrl→checkout/get_response/xpay/cancel/{serial}(Adv_checkout.php:3446)notificationUrl→checkout/xPayHook(Adv_checkout.php:3447)
- Splits customer phone via
PhoneHelper::splitInternationalAndNational()(Adv_checkout.php:3450-3465). - Calls
XPay::createHostedPaymentOrder()with amount as$orderData->total_vatin EUR (Adv_checkout.php:3471-3479). - On failure: logs
REQUEST_FAILED, sets session error, redirects topreview_order(Adv_checkout.php:3482-3487). - On success: logs
REQUESTand redirects browser to$response['hostedPage'](Adv_checkout.php:3491-3493).
Regular Checkout — Webhook (POST checkout/xPayHook)
Adv_checkout::xPayHook() (Adv_checkout.php:3502):
- Decodes
input_stream()JSON; returns HTTP 400 on failure (Adv_checkout.php:3504-3511). - Logs a
CALLBACKrow toxpay_loggingvialogXPayTransaction()before any verification (Adv_checkout.php:3515). - Calls
XPay::parsePaymentNotification(); returns HTTP 400 on\Exception(Adv_checkout.php:3517-3524). - Calls
XPay::verifyNotificationSecurityToken(); returns HTTP 401 on failure (Adv_checkout.php:3526-3533). - Loads order; returns HTTP 404 if not found (
Adv_checkout.php:3536-3542). - Ignores
REFUNDEDnotifications with an info log (Adv_checkout.php:3548-3554). - State machine — updates when the transition is safe (
Adv_checkout.php:3560-3574):PENDING + PAID→ updatePENDING + CANCELED→ updatePAID + CANCELED→ update (reversal)CANCELED + PAID→ update (resurrection)
- Delegates to
processXPayPaymentResult()which callsorder_model->set_status()/set_is_paid()/cancelOrder()and triggers post-hooks (email, SMS, stock alert, loyalty, ERP hook) (Adv_checkout.php:3722-3779).
Regular Checkout — Return URLs
xpayResponse() dispatches get_response/xpay/{action} to xPaySuccess() or xPayCancel() (Adv_checkout.php:3820-3832).
xPaySuccess() (Adv_checkout.php:3588):
- Reads
order_serialfrom URI segment 5 andpaymentidfrom query string. - Updates
tran_ticketon the order row if not already set (Adv_checkout.php:3601-3603). - Calls
XPay::getOrderStatus()with the serial; falls through toxPayCancel()on failure. - Maps
operationResult; ifPAID, callsprocessXPayPaymentResult()then renders the success view (checkoutXPaySuccess). - If not
PAID, delegates toxPayCancel().
xPayCancel() (Adv_checkout.php:3675):
- Updates
tran_ticket; cancels the order if stillPENDINGviacancelOrder(). - Renders the failure view (
checkoutXPayFail).
Regular Checkout — Cron Reconciliation
AdvCancelIncompleteOrders::handlePendingXpayOrders() (ecommercen/job/libraries/AdvCancelIncompleteOrders.php:190):
- Dispatched from the
case 'xpay'branch of the per-order payway switch (AdvCancelIncompleteOrders.php:51). - Calls
XPay::getOrderStatus()with the order serial. - If
PAID, updates the order toPAIDand fires the ERP hook (AdvCancelIncompleteOrders.php:202-203). - Otherwise cancels the order, returns loyalty points, fires the cancel hook, and marks it
order_debris(AdvCancelIncompleteOrders.php:196-199).
Gift Card Checkout — Initiation
AdvGiftCardPage::getPaywayFormData() dispatches case 'xpay' to xpayFormData() (ecommercen/gift_cards/controllers/AdvGiftCardPage.php:115).
xpayFormData() (AdvGiftCardPage.php:1118):
- Calls
getXPaySettings()and builds a customer payload fromgift_card_orders+ phone splitting. - Calls
XPay::createHostedPaymentOrder()with the gift-card amount from$postData['giftCard'], the current store currency code, and the'gift_card'flow discriminator (AdvGiftCardPage.php:1141-1150). - Stores
$response['orderId']astran_ticketon the gift card order row (AdvGiftCardPage.php:1159). - Returns a
['type' => 'redirect', 'url' => $response['hostedPage']]envelope.
Gift Card Checkout — Webhook (POST gift-card/xPayHook)
AdvGiftCardPage::xPayHook() (AdvGiftCardPage.php:1218):
- Decodes
input_stream()JSON; returns HTTP 400 on failure. - Parses and verifies
securityTokenwith flow'gift_card'(AdvGiftCardPage.php:1239); returns HTTP 401 on failure. - Ignores
REFUNDED(AdvGiftCardPage.php:1256). - Resolves
GiftCardStatusenum from$orderObj->gift_card_status; logs error and returns on unknown value. - Dispatches
xpayWebhookAction()result:'accept'→acceptGiftCard()+acceptPostActions()(issues coupon, marks Completed)'cancel'→cancelGiftCard()+cancelPostActions()'noop'→ logs and returns without modification
Gift Card — State Machine
AdvGiftCardPage::xpayWebhookAction(GiftCardStatus, string): string (AdvGiftCardPage.php:1309):
| Current status | XPay status | Action |
|---|---|---|
Pending | PAID | accept |
Pending | CANCELED | cancel |
Completed | CANCELED | cancel (reversal) |
Completed | PAID | noop (idempotency guard) |
Canceled | any | noop (terminal) |
| any | PENDING / REFUNDED / unknown | noop |
REFUNDED is filtered upstream in xPayHook() before xpayWebhookAction() is called (AdvGiftCardPage.php:1256).
Gift Card — Cron Reconciliation
AdvCancelPendingGiftCards::cancelPendingXpayOrders() (ecommercen/gift_cards/jobs/AdvCancelPendingGiftCards.php:91):
- Queries pending gift card orders with
payway='xpay'andcreated_at < now() - giftCardDateTimeIntervalToDrop. - Calls
XPay::getOrderStatus()for each; accepts ifPAID, cancels otherwise. - XPay orders are excluded from the bulk-cancel query that handles all other payways (
AdvCancelPendingGiftCards.php:30).
Data Model
xpay_logging table
Created by database/migrations/20251117205429_create_xpay_logging_table.php.
| Column | Type | Notes |
|---|---|---|
id | unsigned int, auto-increment PK | |
order_serial | VARCHAR(50) NOT NULL | Merchant order serial |
flow | VARCHAR(20) NOT NULL | 'order' or 'gift_card' |
transaction_id | VARCHAR(100) NULL | Nexi-side order ID (populated on RESPONSE rows) |
transaction_type | VARCHAR(50) | REQUEST, RESPONSE, CALLBACK, PROCESSED, STATUS_CHECK, REQUEST_FAILED |
request_data | TEXT NULL | JSON-encoded request payload |
response_data | TEXT NULL | JSON-encoded response payload (contains securityToken on successful RESPONSE rows) |
status | VARCHAR(50) NULL | SUCCESS, FAILED, or null |
created_at | TIMESTAMP DEFAULT CURRENT_TIMESTAMP |
Indexes (migration:20-31):
| Name | Columns | Purpose |
|---|---|---|
idx_order_serial | order_serial | Order lookup |
idx_transaction_id | transaction_id | Nexi-side ID lookup |
idx_created_at | created_at | Time-range queries |
idx_token_lookup | (order_serial, flow, transaction_type, status, created_at) | Covering index for getStoredSecurityToken() query |
Write sources
xpay_logging is written from two separate code paths:
XPay::logToDatabase()(src/PaymentGateways/NexiXPay/XPay.php:520) — writesREQUESTandRESPONSErows (withflowandsecurityToken) duringcreateHostedPaymentOrder().Adv_checkout::logXPayTransaction()(ecommercen/checkout/controllers/Adv_checkout.php:3790) — writesREQUEST,CALLBACK,PROCESSED,STATUS_CHECK, andREQUEST_FAILEDrows but does not populateflowortransaction_id.
orders table (regular flow)
tran_ticketis updated on the success return URL with thepaymentidquery parameter (Adv_checkout.php:3601-3603).statusis set toPAIDorCANCELEDbyprocessXPayPaymentResult().is_paidflag is set to1onPAID(Adv_checkout.php:3740).
gift_card_orders table (gift card flow)
tran_ticketis written with the Nexi-sideorderIdat HPP creation time (AdvGiftCardPage.php:1159).gift_card_statustransitions viaacceptGiftCard()/cancelGiftCard().
Domain Layer
Modern Layer
src/PaymentGateways/NexiXPay/XPay.php — PSR-4 class under Advisable\PaymentGateways\NexiXPay. Constructor accepts optional ?CI_DB_query_builder $db and ?Client $httpClient for testability; production callers use new XPay($config) and receive the autowired DI-container DB handle (XPay.php:37-54).
Public API:
| Method | Description |
|---|---|
createHostedPaymentOrder(...) | Creates an HPP order on the Nexi API; logs REQUEST and RESPONSE to xpay_logging |
getOrderStatus(string $xpayOrderId) | Polls GET api/v1/orders/{id}; returns parsed response or null on failure |
parsePaymentNotification(array $notification) | Validates and normalises a webhook payload; throws on missing required fields |
verifyNotificationSecurityToken(array $notification, string $orderSerial, string $flow = 'order') | Compares inbound securityToken against stored value using hash_equals; fail-closed |
mapOperationResultToStatus(string $operationResult) | Maps Nexi enum values to internal PAID / CANCELED / PENDING / REFUNDED / UNKNOWN |
generateCorrelationId() | Returns UUID v4 as required by the Nexi API header |
formatAmount(float $amount, string $currency) | Converts to integer cents via (int)round($amount * 100) |
Legacy Layer
| File | Role |
|---|---|
ecommercen/checkout/controllers/Adv_checkout.php | Regular checkout — xpay(), xPayHook(), xPaySuccess(), xPayCancel(), xpayResponse(), processXPayPaymentResult(), logXPayTransaction() |
ecommercen/gift_cards/controllers/AdvGiftCardPage.php | Gift card checkout — xpayFormData(), xPayHook(), xPaySuccess(), xPayCancel(), xpayWebhookAction() |
ecommercen/job/libraries/AdvCancelIncompleteOrders.php | Regular order cron reconciliation — handlePendingXpayOrders(), lazy xPay() accessor |
ecommercen/gift_cards/jobs/AdvCancelPendingGiftCards.php | Gift card cron reconciliation — cancelPendingXpayOrders() |
ecommercen/helpers/registry_helper.php | getXPaySettings() helper — reads XPAY.API_KEY and XPAY.IS_PRODUCTION from the registry (registry_helper.php:347-355) |
ecommercen/eshop/libraries/AdvPaymentsRegistry.php | Registers the xpay provider config with two settings: API_KEY (password type, required) and IS_PRODUCTION (checkbox, default false) (AdvPaymentsRegistry.php:697-706) |
Configuration
All XPay settings are stored in the XPAY registry group and surfaced in the admin payment-settings view (ecommercen/settings/controllers/Adv_settings.php:1206-1207).
| Registry Key | Type | Required | Description |
|---|---|---|---|
XPAY.API_KEY | password | Yes | Merchant API key issued by Nexi |
XPAY.IS_PRODUCTION | boolean | No (default false) | false = sandbox, true = live production |
getXPaySettings() reads both keys and returns an array consumed by new XPay($config) (ecommercen/helpers/registry_helper.php:347-355).
Gift card timeout — $config['giftCardDateTimeIntervalToDrop'] (PHP DateInterval string, default 'PT180M') controls when the AdvCancelPendingGiftCards cron drops unresolved pending XPay gift card orders (application/config/app.php:514).
Client Extension Points
The xpay case is handled inside the switch blocks of Adv_checkout::process_order() and Adv_checkout::get_response(). Client repos that override application/core/Front_c.php or create subclasses of Adv_checkout can:
- Override
processXPayPaymentResult()(markedprotected) to add custom post-payment hooks (Adv_checkout.php:3722). - Override
logXPayTransaction()(markedprotected) to route audit rows to a different table or logging backend (Adv_checkout.php:3790). - Override
AdvGiftCardPage::xpayWebhookAction()(markedpublic static) or replace the gift card controller entirely, since the state machine is a pure static helper. - Inject a custom
\GuzzleHttp\ClientintoXPayto wrap HTTP calls with tenancy-level configuration (proxy, timeout, etc.).
Business Rules
- The Nexi XPay Greece HPP model transfers 3-D Secure and card-number responsibility to Nexi. The merchant never sees raw card data.
- Currency is hard-coded to
'EUR'in the regular checkout path (Adv_checkout.php:3474). The gift card path uses$this->currentCurrency->code(AdvGiftCardPage.php:1144). - Amount must be expressed in integer cents.
XPay::formatAmount()converts via(int)round($amount * 100)to avoid floating-point drift (XPay.php:430-434). - The platform's only documented webhook-authenticity mechanism is the
securityTokenecho-back. There is no HMAC.verifyNotificationSecurityToken()is fail-closed: rejects when either the inbound token or the stored token is missing or empty (XPay.php:282-298). REFUNDEDnotifications are explicitly ignored on both webhook handlers — they log and return without touching order state (Adv_checkout.php:3548-3554,AdvGiftCardPage.php:1256).acceptGiftCard()is not idempotent: it always inserts a new coupon row. ThexpayWebhookAction()state machine guards against re-issuing by returning'noop'for(Completed, PAID)(AdvGiftCardPage.php:1314-1323).- Cross-flow token collision is prevented by the
flowdiscriminator column onxpay_logging. Theidx_token_lookupcovering index ensures the discriminated lookup is efficient (migration:23-31). 'xpay'is registered ingetGiftCardPayWays()(ecommercen/helpers/eshop_helper.php:286-299) and in the gift-card VuepayWayInstallmentsmap with an empty array (AdvGiftCardPage.php:811), matching theiris/alpha/ethniki/vivawalletpattern for payways that carry no installments.
Known Issues & Security Gaps
Duplicate writes to
xpay_loggingon HPP creation.XPay::createHostedPaymentOrder()writesREQUESTandRESPONSErows vialogToDatabase()(XPay.php:131,145). The callerAdv_checkout::xpay()then writes a secondREQUESTrow (orREQUEST_FAILED) vialogXPayTransaction()(Adv_checkout.php:3484,3491). A single HPP creation thus produces twoREQUESTrows with different schemas (the gateway-level row includesflow,transaction_id, and full payload inrequest_data; the controller-level row lacksflowand stores the response envelope instead). This makes forensic queries onxpay_loggingambiguous for the HPP-creation event.logXPayTransaction()never writes theflowcolumn. The controller-level helper (Adv_checkout.php:3790-3811) builds$logDatawithout aflowkey, so the column receives its default value. All rows written byCALLBACK,PROCESSED,STATUS_CHECK, andREQUEST_FAILEDevents haveflow = NULL(or the column default). Theidx_token_lookupindex and thegetStoredSecurityToken()query filter onflow, meaning these controller-level rows cannot serve as token sources and do not interfere with token lookup, but the schema contract is violated for audit purposes.xPayHook()does not stampmeta_dataon the order. Other webhook handlers (JCC, Viva legacy) write an identifying string toshop_order.meta_datato record which code path caused the status change.xPayHook()andprocessXPayPaymentResult()omit this, making cross-provider forensic queries harder. Thexpay_loggingtable provides a parallel audit trail butmeta_datais inconsistent. (Adv_checkout.php:3502-3582)xPayCancel()does not guard against a missingpaymentidquery parameter. The guard at the top ofxPayCancel()checks!$orderSerial || !$orderData || !$paymentIdand callsinactive_payment()(Adv_checkout.php:3682-3685). However, Nexi's cancellation redirect may not include apaymentidparameter in all edge cases (browser back-button, session expiry). A customer returning withoutpaymentidlands oninactive_payment()rather than a graceful "payment cancelled" page, with no order state update.xPaySuccess()makes an extragetOrderStatus()API call on every browser redirect. The HPP flow already delivers a server-to-server webhook before the browser redirect completes. If the webhook arrives first and updates the order toPAID, thegetOrderStatus()call inxPaySuccess()is redundant (Adv_checkout.php:3606). In the reverse case (webhook delayed), the status check is necessary. There is no mechanism to short-circuit when the order is alreadyPAIDfrom the webhook, so every return-URL visit incurs an outbound API call.handlePendingXpayOrders()evaluates the null-check after the dependent map call. InAdvCancelIncompleteOrders::handlePendingXpayOrders(),$status = $this->xPay()->mapOperationResultToStatus($xpayOrderStatus['operationResult'] ?? '')is evaluated on line 193 even when$xpayOrderStatusisnull. The null guardif (!$xpayOrderStatus || $status !== 'PAID')on line 195 catches this because the short-circuit||ensures the block is entered, butmapOperationResultToStatus('')returns'UNKNOWN'— a different control flow path than an explicit null check would produce. The order is still cancelled correctly, but the intent is obscured. (ecommercen/job/libraries/AdvCancelIncompleteOrders.php:192-199)Regular checkout
xpayResponse()does not return HTTP 200 to the Nexi webhook.xPayHook()sets explicit status codes for error paths (400, 401, 404) but does not explicitly set 200 for the success path. CodeIgniter's default response is 200, so this is not a runtime bug, but the lack of an explicitset_status_header(200)is inconsistent with the error paths and could cause a silent issue if the framework default changes. (Adv_checkout.php:3502-3582)No integration or end-to-end tests for the controller-layer XPay code paths.
tests/Unit/PaymentGateways/NexiXPay/XPayTest.phpcovers the standaloneXPayclass (57 tests).tests/Legacy/GiftCards/AdvGiftCardPageTest.phpcovers thexpayWebhookAction()state machine only (12 tests). The controller methodsxpay(),xPayHook(),xPaySuccess(),xPayCancel(),processXPayPaymentResult(), and the gift cardxpayFormData(),xPayHook(),xPaySuccess(),xPayCancel()have no test coverage. The cron reconciliation helpershandlePendingXpayOrders()andcancelPendingXpayOrders()are also untested.
Tests
tests/Unit/PaymentGateways/NexiXPay/XPayTest.php — 57 tests
Runs in isolated processes (#[RunTestsInSeparateProcesses], #[PreserveGlobalState(false)]) because the XPay constructor reads config_item('ISO639-2') and config_item('language_abbr') which require a CI3 boot (XPayTest.php:40-41).
Coverage groups:
| Group | Description |
|---|---|
| Constructor | Sandbox/production URL selection, IS_PRODUCTION truthy-string coercion, default-empty API key (XPayTest.php:85-139) |
generateCorrelationId | UUID v4 validity and uniqueness across 10 consecutive calls (XPayTest.php:143-165) |
mapOperationResultToStatus | Data-provider across 11 mapped values plus 2 UNKNOWN-fallthrough cases (13 data-provider rows total) including the UNKNOWN fallback (XPayTest.php:169-195) |
parsePaymentNotification | Happy path (all fields), four InvalidArgumentException paths, optional-field nullability, rawNotification preservation, status map threading (XPayTest.php:197-343) |
verifyNotificationSecurityToken | 10 tests: match, mismatch, missing token, empty token, non-string token, no stored row, empty response_data, missing securityToken key in stored row, DB exception, query-shape regression including flow filter and gift-card flow isolation (XPayTest.php:379-531) |
HTTP-mocked createHostedPaymentOrder | Happy path, missing hostedPage, ClientException, ServerException, ConnectException, endpoint assertion, cents conversion, header shape, optional customer data present/absent, language resolution from ISO 639-2 map (XPayTest.php:566-755) |
HTTP-mocked getOrderStatus | Happy path with multiple operations (latest = index 0), empty operations, correct endpoint, ClientException, ServerException (XPayTest.php:760-841) |
| API key redaction | Verifies ***REDACTED*** appears and the raw key never appears in log output via a Monolog\TestHandler + DI container swap (XPayTest.php:845-886) |
tests/Legacy/GiftCards/AdvGiftCardPageTest.php — 12 tests
Covers the pure static xpayWebhookAction() helper only.
| Test | Description |
|---|---|
| Data-driven decision matrix | 10 (GiftCardStatus, xpayStatus) pairs verifying expected action string (AdvGiftCardPageTest.php:28-67) |
| Exhaustive 3×6 matrix walk | Asserts only 'accept', 'cancel', 'noop' ever escape the function (AdvGiftCardPageTest.php:70-91) |
| Regression guard | Verifies 'accept' is never returned outside the (Pending, PAID) path (AdvGiftCardPageTest.php:94-114) |
Related Flows
- CF-08 Payment Processing — covers the full checkout payment dispatch including XPay's slot in
process_order() - CF-09 Payment Webhooks — covers the webhook authenticity model,
securityTokensequence diagram, and cross-provider comparison - CF-23 Gift Cards — covers the gift card purchase flow end-to-end, including all supported payways
- SY-03 Incomplete Order Cancellation — covers the
AdvCancelIncompleteOrdersjob that drives XPay cron reconciliation for regular orders - AD-23 Gift Cards Admin — covers
AdvCancelPendingGiftCardsand gift card admin operations