Title: Saga Payments for WooCommerce
Author: sagapay
Published: <strong>Май 23, 2026-ж.</strong>
Last modified: Май 27, 2026-ж.

---

Плагиндерди издөө

![](https://ps.w.org/saga-payments/assets/icon-256x256.png?rev=3545137)

# Saga Payments for WooCommerce

 Автору [sagapay](https://profiles.wordpress.org/sagapay/)

[Жүктөө](https://downloads.wordpress.org/plugin/saga-payments.4.0.420.zip)

 * [Кенен маалымат](https://ky.wordpress.org/plugins/saga-payments/#description)
 * [Сын-пикирлер](https://ky.wordpress.org/plugins/saga-payments/#reviews)
 *  [Орнотуу](https://ky.wordpress.org/plugins/saga-payments/#installation)
 * [Development](https://ky.wordpress.org/plugins/saga-payments/#developers)

 [Колдоо](https://wordpress.org/support/plugin/saga-payments/)

## Сүрөттөө

Saga Payments for WooCommerce is a **full-featured payment gateway** built for Nordic
merchants. Accept secure payments through multiple payment methods with enterprise-
grade features:

#### Payment Methods

 * **Card Payments** – Visa, Mastercard, American Express, Discover
 * **Vipps** – Norway’s most popular mobile payment app
 * **Klarna** – Buy now, pay later
 * **Apple Pay** – Fast checkout for Apple users
 * **Google Pay** – Fast checkout for Android users
 * **Swish** – Sweden’s most popular mobile payment app
 * **MobilePay** – Denmark and Finland’s popular mobile payment app

#### Features

 * **Easy Setup** – Just enter your Merchant ID, Terminal ID, and Public Key
 * **Saved Payment Methods** – Customers can save cards for faster checkout
 * **WooCommerce Subscriptions** – Full support for recurring payments with automatic
   renewals
 * **WooCommerce Pre-Orders** – Charge customers when pre-ordered products become
   available
 * **Authorize & Capture** – Authorize payments and capture later, or capture immediately
 * **Auto-Capture** – Automatically capture when order status changes to Processing/
   Completed
 * **Auto-Void** – Automatically void authorizations when orders are cancelled
 * **Express Checkout** – Apple Pay/Google Pay buttons on product pages and cart
 * **WooCommerce Blocks** – Full support for Blocks Checkout including saved cards
 * **HPOS Compatible** – Full support for High-Performance Order Storage
 * **Refunds** – Full and partial refunds directly from WooCommerce admin
 * **Professional Design** – Clean, responsive payment widget

#### Plugin Integrations

 * WooCommerce Subscriptions – Automatic recurring payments
 * WooCommerce Pre-Orders – Charge on release
 * WooCommerce Blocks – Full Blocks checkout support
 * WooCommerce HPOS – High-Performance Order Storage
 * **Saga Product Sync** – Bidirectional product catalogue synchronisation
 * Compatible with all major WordPress themes

#### Product Sync (Saga)

Automatic bidirectional synchronisation between your WooCommerce store and Saga:

 * **Saga is master** – pull products, prices, images, VAT rates and stock from 
   Saga into WooCommerce
 * **Push changes back** – WooCommerce product edits are automatically pushed to
   Saga in real-time
 * **Inventory sync** – relative stock deltas (STOCK_DOWN / STOCK_UP) on order completion,
   refund and cancellation
 * **Auto-retry** – failed inventory adjustments are queued and retried every 5 
   minutes (up to 20 attempts)
 * **Full field mapping** – prices (øre kroner), VAT rates (25 / 15 / 12 / 0 %),
   units, barcodes, cost price, images
 * **Admin panel** – WooCommerce -> Saga Product Sync with connection test, manual
   sync, and status dashboard
 * Configure via WooCommerce -> Saga Product Sync

#### Requirements

 * WordPress 5.8 or later
 * WooCommerce 7.0 or later
 * PHP 7.4 or later
 * SSL certificate (HTTPS)
 * Saga Payments merchant account

### External services

This plugin relies on the following external services to process payments. A Saga
Payments merchant account is required.

#### Saga Payments API

All payment operations (order creation, payment verification, refunds, captures,
voids, and subscription management) are handled through the Saga Payments API proxy
server. When a customer initiates a checkout, order data including amount, currency,
line items, and customer billing/shipping information is sent to this service via
server-side HTTP requests.

 * Service URL: https://sagapay-api-v3.kristoffer-afc.workers.dev/api
 * Provider: Saga Payments (Cloudflare Worker proxy to Surfboard Payments)
 * Website: https://sagapay.no
 * Terms of Service: https://www.sagapay.no/terms-of-service
 * Privacy Policy: https://www.sagapay.no/personvernserklaering

#### Saga Payments Merchant Dashboard and Documentation

The plugin settings screen links store administrators to Saga Payments dashboard
and documentation pages for API key, webhook, and product catalogue setup. These
links open only when an administrator clicks them. No customer or payment data is
sent automatically by these documentation links.

 * Dashboard URL: https://dashboard.sagapay.no
 * API key documentation URL: https://docs.sagapay.no/guides/getting-started/opprett-
   api-nokler
 * Webhook documentation URL: https://docs.sagapay.no/guides/getting-started/webhooks
 * Product catalogue documentation URL: https://docs.sagapay.no/api-reference/api-
   reference/product-catalogue/
 * Provider: Saga Payments
 * Terms of Service: https://www.sagapay.no/terms-of-service
 * Privacy Policy: https://www.sagapay.no/personvernserklaering

#### Saga OTP Verification Service

When a guest customer pays with a previously saved card, the plugin sends a one-
time password (OTP) verification email via a Cloudflare Worker. Only the customer’s
email address, a generated OTP code, the store name, and the cart total are sent
in the request. No card data is transmitted. The OTP email is delivered via the 
worker using the Resend email service.

 * Service URL: https://saga-otp-worker.kristoffer-afc.workers.dev
 * Provider: Saga Payments (Cloudflare Worker)
 * Website: https://sagapay.no
 * Terms of Service: https://www.sagapay.no/terms-of-service
 * Privacy Policy: https://www.sagapay.no/personvernserklaering

#### Surfboard Online SDK

The payment form displayed on your checkout page is rendered by the Surfboard Online
SDK. This JavaScript library is loaded from an external server into the customer’s
browser and handles the secure payment UI including card input fields, Vipps/MobilePay
popups, Klarna widget, and Apple Pay / Google Pay buttons. Card data is entered 
directly into Surfboard-hosted iframes and never touches your server.

 * SDK URL: https://thorium.surfgw.com/OnlineSDK.js
 * Provider: Surfboard Payments
 * Website: https://www.surfboardpayments.com
 * Terms of Service: https://www.surfboardpayments.com/terms-and-conditions
 * Privacy Policy: https://www.surfboardpayments.com/privacy-policy

#### Surfboard Hosted Payment Page

When the plugin is configured in redirect mode, or for certain subscription flows,
the customer’s browser is redirected to a hosted payment page operated by Surfboard
Payments. The order amount, currency, and return URLs are passed via the redirect.
All payment data is entered on the hosted page.

 * Service URL: https://pay.withsurfboard.com
 * Base URL used by redirects: https://pay.withsurfboard.com/
 * Provider: Surfboard Payments
 * Terms of Service: https://www.surfboardpayments.com/terms-and-conditions
 * Privacy Policy: https://www.surfboardpayments.com/privacy-policy

#### Surfboard Vipps/MobilePay Intermediary

For Vipps and MobilePay payment flows, the Surfboard SDK loads an intermediary page
in an iframe to handle the mobile payment authorization. This is managed automatically
by the Surfboard Online SDK and serves as the bridge between your store and the 
Vipps/MobilePay apps.

 * Service URL: https://vipps.withsurfboard.com
 * Provider: Surfboard Payments
 * Terms of Service: https://www.surfboardpayments.com/terms-and-conditions
 * Privacy Policy: https://www.surfboardpayments.com/privacy-policy

#### Google Pay API

When Google Pay is enabled as a payment method, the Google Pay JavaScript SDK is
loaded in the customer’s browser to render the Google Pay button and handle the 
payment sheet. Payment token data is returned to your store and forwarded to the
Saga Payments API for processing.

 * SDK URL: https://pay.google.com/gp/p/js/pay.js
 * Provider: Google
 * Website: https://pay.google.com
 * Terms of Service: https://payments.google.com/payments/apis-secure/get_legal_document?
   ldo=0&ldt=googlepaytos
 * Privacy Policy: https://policies.google.com/privacy

#### QR Code Generation

For Swish payments, if the payment gateway does not return a pre-rendered QR code
image, the plugin generates a QR code locally using the bundled qrcode-generator
library (MIT license, by Kazuhiko Arase). No external service is called for QR code
generation.

#### Saga Checkout SDK (Diagnostics Only)

The plugin diagnostics page displays the configured SDK endpoint for admin troubleshooting
purposes. The actual payment SDK loaded during checkout is the Surfboard Online 
SDK documented above.

 * Provider: Saga Payments
 * Website: https://sagapay.no
 * Terms of Service: https://www.sagapay.no/terms-of-service
 * Privacy Policy: https://www.sagapay.no/personvernserklaering

#### Saga Product Sync API

If the optional product synchronization feature is enabled, the plugin sends product
data (name, description, price, SKU, images, categories) from your WooCommerce catalog
to the Saga Payments product API for catalogue management. This is an opt-in feature
configured in the plugin settings.

 * Service URL: https://api.sagapay.no
 * Provider: Saga Payments
 * Terms of Service: https://www.sagapay.no/terms-of-service
 * Privacy Policy: https://www.sagapay.no/personvernserklaering

#### Payment Method Logos

Payment method logos (Visa, Mastercard, Amex, Vipps, Klarna, Apple Pay, Google Pay,
Swish, MobilePay) are bundled locally within the plugin in SVG format. No external
CDN is used for logo display.
 SVG files and inline SVG markup use the standard 
SVG namespace URI http://www.w3.org/2000/svg and xlink namespace URI http://www.
w3.org/1999/xlink as static XML identifiers; these are not external network requests.

## Орнотуу

 1. Upload the `saga-payments` folder to the `/wp-content/plugins/` directory
 2. Activate the plugin through the ‘Plugins’ menu in WordPress
 3. Go to WooCommerce > Settings > Payments > Saga Payments
 4. Enter your **Merchant ID** and **Store ID** (from your Saga Payments dashboard 
    at https://dashboard.sagapay.no) and click **Save changes**
 5. The plugin will automatically provision your **Terminal ID** and **Public Key**
    on save – no manual key handling required
 6. Enable the payment method and save

## FAQ.KG

### How do I get a Saga Payments account?

Contact Saga Payments at support@sagapayments.com to set up your merchant account.

### Does this support test mode?

Yes! Enable test mode in the plugin settings to test without processing real payments.

### Which currencies are supported?

The plugin supports NOK (Norwegian Krone), SEK (Swedish Krona), DKK (Danish Krone),
EUR and other currencies supported by Saga Payments.

### Can customers save their cards?

Yes! When “Enable Saved Cards” is turned on in settings, logged-in customers can
save their cards for faster future checkout.

### Does this work with WooCommerce Subscriptions?

Yes! Full integration with WooCommerce Subscriptions for automatic recurring payments,
payment method changes, and subscription lifecycle management.

### Can I authorize and capture later?

Yes! Set “Capture Mode” to “Manual” in settings. Payments will be authorized at 
checkout and you can capture from the order page when ready to ship.

## Сын-пикирлер

There are no reviews for this plugin.

## Contributors & Developers

“Saga Payments for WooCommerce” is open source software. The following people have
contributed to this plugin.

Мүчөлөрү

 *   [ sagapay ](https://profiles.wordpress.org/sagapay/)

[Translate “Saga Payments for WooCommerce” into your language.](https://translate.wordpress.org/projects/wp-plugins/saga-payments)

### Interested in development?

[Browse the code](https://plugins.trac.wordpress.org/browser/saga-payments/), check
out the [SVN repository](https://plugins.svn.wordpress.org/saga-payments/), or subscribe
to the [development log](https://plugins.trac.wordpress.org/log/saga-payments/) 
by [RSS](https://plugins.trac.wordpress.org/log/saga-payments/?limit=100&mode=stop_on_copy&format=rss).

## Өзгөртүүлөр

#### 4.0.420

 * Product sync: stop calling the `/product-catalogue/{catId}/products` Fetch Products
   variant without a productId query, which was raising `PC_0061: Missing minimum
   paramter to fetch product` on SagaPay’s PC_GET_PRODUCTS alerting. Only the documented`/
   product-catalogue/{catId}?storeId=...` endpoint is used to list products now.

#### 4.0.419

 * Product pull now enables WooCommerce stock tracking for tangible Saga catalogue
   products even when Saga’s fetch response omits inventory values, preserving a
   readable Woo stock baseline or using an in-stock baseline so Woo does not leave
   managed stock disabled.

#### 4.0.418

 * Product pull now activates WooCommerce stock tracking for existing Saga-managed
   products that already have a Woo stock value when the Saga API omits an absolute
   inventory quantity, preserving that value instead of leaving tracking disabled.

#### 4.0.417

 * Product pull now treats Surfboard inventory payloads as a stock-management signal
   even when the API omits a readable quantity, enabling WooCommerce stock tracking
   without overwriting existing stock with zero.

#### 4.0.416

 * Product statistics fallback now includes the connected `storeId`, so Surfboard
   stock can be imported when the product payload omits inventory fields.

#### 4.0.415

 * Product pull now guarantees WooCommerce `_manage_stock`, `_stock`, `_stock_status`
   and `_saga_synced_stock` meta are persisted after Surfboard stock quantities 
   are imported.

#### 4.0.414

 * Surfboard stock quantities from `inventoryStatus.currentStock` now activate WooCommerce
   stock management and write the remote stock quantity during product pull.

#### 4.0.413

 * STRICT CONNECTED STORE PRODUCT SYNC (wave10es) – Product sync is scoped back 
   to the connected Store ID only. Merchant-wide store discovery is not used for
   Woo catalogue import, SKU dedupe, cart authority checks or inventory retry pruning.

#### 4.0.412

 * STORE-AWARE PRODUCT WRITE PATHS (wave10er) – Products imported from another active
   Surfboard store now carry their owning store through related-products, variable/
   variant sync, stock updates, order/refund/cancel inventory safety nets and inventory
   retry queue entries. This prevents later Woo actions from writing back to the
   gateway store when the product belongs to another Surfboard store.

#### 4.0.411

 * ALL-STORE CATALOG PULL (wave10eq) – Product pull now discovers active stores 
   under the merchant via the live `/stores?merchantId=...` API and reads each store
   product catalogue, not only the gateway store. Imported products remember `_saga_store_id`
   so later delete/update operations can target the store that actually owns the
   Surfboard product.

#### 4.0.410

 * MAIN DESCRIPTION AUTHORITY (wave10ep) – WooCommerce full product description 
   is now the authoritative source for the Saga top-level `description` field on
   push. Short description is still preserved separately in `productProperties.shortDescription`,
   so editing the main Woo description no longer leaves Surfboard stuck with an 
   old short description.

#### 4.0.409

 * NATIVE GTIN PUSH FIX (wave10eo) – WooCommerce’s native GTIN/EAN field is now 
   read via `get_global_unique_id()` before fallback meta keys, and product admin
   saves run one final push after Woo has persisted product meta. This closes the
   live-tested gap where name, price and stock reached Surfboard immediately but
   a changed Woo GTIN could remain stale remotely until overwritten by the next 
   pull.

#### 4.0.408

 * LIVE BARCODE PAYLOAD FIX (wave10en) – Live Surfboard persists EAN/barcode updates
   from the lowercase `barcode` field, while the docs show `barCode`. Product and
   variant push payloads now send both field names so Woo-created or Woo-updated
   products keep EAN/GTIN in Surfboard and pull back into Woo’s native GTIN field.

#### 4.0.407

 * PRODUCT FIELD AUTHORITY (wave10em) – Pull now fetches each full Surfboard product
   by ID before mapping, so compact list responses cannot drop description, category,
   barcode/EAN/GTIN, HSN, tax, product properties, images or other documented fields.
   Saga categories are now applied as real WooCommerce product categories, barcode/
   EAN is written through Woo’s native GTIN setter when available and common EAN
   plugin meta keys are covered.
 * EMPTY SURFBOARD CATALOGUE AUTHORITY – If Surfboard/Saga returns zero products
   while Woo has active products, the pull treats the empty catalogue as authoritative
   and moves active Woo products out of the live catalogue instead of pushing them
   back up and resurrecting POS-deleted products.
 * VARIANT AND TAX HARDENING – Saga variants pulled from Surfboard are materialised
   as Woo variable product variations with attributes, variant IDs, stock, EAN/GTIN,
   HSN and raw variant payload metadata. Woo product updates now send the tax array
   as well as the existing VAT field, with a fallback retry that strips tax if an
   endpoint rejects tax on PATCH.

#### 4.0.406

 * STALE INVENTORY RETRY PRUNE (wave10el) – Inventory retry queue entries are now
   removed when a linked Saga product is deleted or orphan-cleaned from WooCommerce,
   and retry cron drops stale adjustments when the current Saga catalogue no longer
   contains the product ID. This prevents old STOCK_UP/STOCK_DOWN retries for deleted
   products from retrying for 24 hours and sending manual correction emails after
   the product is already gone upstream.

#### 4.0.405

 * CART/CATALOG AUTHORITY PRODUCT SYNC (wave10ek) – Existing WooCommerce cart sessions
   are now swept on cart load and checkout validation so Saga-managed products that
   were deleted upstream or removed from the publishable Woo catalogue cannot remain
   purchasable from stale sessions. Add-to-cart now verifies linked Saga products
   against the current catalogue before accepting the item.
 * MULTI-CATALOG PRODUCT ROUTING – Product pulls now parse live plural catalogue
   fields such as `productCatalogueIds`, store `_saga_catalogue_id` on Woo products/
   variations, report all catalogue counts in Test Connection, and use the owning
   catalogue ID for product updates, deletes, related-products, variants, product
   statistics, inventory deltas and retry-queue replays.

#### 4.0.404

 * WOO DELETE/STATUS PRODUCT SYNC (wave10ej) – WooCommerce product trash/delete 
   and publish-status changes now propagate to Surfboard/Saga by deleting the linked
   catalogue product when the Woo product leaves the publishable catalogue. Draft/
   private/pending products are no longer pushed into Surfboard from save hooks,
   and successful remote deletes clear the active `_saga_product_id` while keeping
   audit meta so a later restore/publish creates a clean new catalogue product instead
   of leaving POS with stale inventory.

#### 4.0.403

 * DELETE-SAFE MANUAL PRODUCT SYNC (wave10ei) – Manual Sync no longer pushes Woo
   products back into Surfboard/Saga in the same run after pull-side cleanup removed
   POS-deleted products from WooCommerce. This prevents remote deletions from being
   immediately resurrected by the push phase when the Surfboard catalogue contains
   fewer products than Woo.

#### 4.0.402

 * AUTHORITATIVE PRODUCT SYNC MIRROR (wave10eh) – Treats an empty Surfboard/Saga
   catalogue as an authoritative delete state after a previous sync or when Saga-
   managed products exist in WooCommerce. Manual Sync now refuses to push Woo products
   back into Saga immediately after an authoritative empty pull, so POS-side product
   deletion cannot be undone by the plugin. The pull cleanup can remove all active
   Woo products from the catalogue in this state, not only products still carrying
   a current `_saga_product_id`.
 * DOCUMENTED SURFBOARD PRODUCT ENDPOINTS – Product catalogue API calls now try 
   the documented `/catalog`, `/catalog/:catalogId/products`, product, variant, 
   related-products, statistics and inventory routes first, while keeping the older`/
   product-catalogue` routes as fallback. The catalogue ID cache namespace was refreshed
   so stores do not stay pinned to an old/parallel catalogue.
 * INVENTORY SYNC HARDENING – Product stock parsing now accepts `inventory.currentStock`,`
   stock`, `availableQuantity`, `inventoryQuantity` and related Surfboard field 
   shapes, and falls back to product statistics when the product list omits stock.
   Order completion, refund and cancellation safety nets now resolve variation inventory
   bindings and queue variant retry adjustments instead of only handling simple 
   product stock.

#### 4.0.401

 * WALLET GHOST-TRIGGER GUARD (wave10eg) – Prevents Apple Pay / Google Pay from 
   being launched again after the customer closes the wallet sheet. Classic checkout
   now treats wallet cancel/reset as a durable stale-attempt signal, blocks checkout-
   success and payment fallback paths from calling `initiatePayments()` without 
   a fresh trusted Place Order click, and skips automatic SDK remount immediately
   after a clean wallet cancellation.

#### 4.0.400

 * CLEAN CHECKOUT UTF-8 FALLBACKS (wave10ef) – Fixes mojibake in customer-facing
   classic checkout fallback messages, including the Apple Pay / Google Pay cancelled
   notice that could render as `PrÃ¸v igjen` instead of `Prøv igjen`. Classic checkout
   now normalizes known double-encoded fallback text before display and routes wallet
   cancel notices through the central i18n keys.

#### 4.0.399

 * ORDER ATTRIBUTION PAYMENT-DOMAIN GUARD (wave10ee) – Prevents WooCommerce Order
   Attribution from recording Saga/Surfboard payment domains such as `pay.withsurfboard.
   com` as the merchant’s traffic source after redirect/cancel/retry flows. Classic
   checkout now snapshots clean Woo attribution before Saga submission and restores
   it before order creation if the browser came back from the payment page; the 
   server also removes or restores polluted `_wc_order_attribution_*` meta for classic,
   Blocks and programmatic Saga orders.

#### 4.0.398

 * PRODUCT PULL REACTIVATION FIX (wave10ed) – Fixes the case where a Surfboard/Saga
   product is found during pull and the sync reports it as updated, but WooCommerce
   still does not show it because the matched Woo product stayed as draft/private/
   pending. Pull updates now publish and make visible any product returned by the
   Saga catalogue, because Saga is the pull-side source of truth.
 * RESTORE LINKED PRODUCTS FROM TRASH – If Saga returns a product whose `_saga_product_id`
   already exists in Woo trash, the pull lookup now finds it and restores/publishes
   it instead of creating another hidden/duplicate copy.

#### 4.0.397

 * PRODUCT VARIANT ENDPOINT COVERAGE (wave10ec) – WooCommerce variable products 
   are no longer skipped by product sync. Variable parent products are created/updated
   as Saga catalogue products, Woo variations are created/updated through the Saga
   variant endpoints, variation IDs are stored on each WC variation, and variation
   stock changes now use the Saga variant inventory endpoint instead of being ignored
   as unlinked stock changes. Variation save hooks are registered so admin edits
   to size/color variants push immediately.
 * DOCUMENTED PRODUCT ROUTE/VERB FALLBACKS – Product API calls now try the documented
   plural `/products/:productId` route shape and documented `PATCH` inventory methods
   first, while keeping the older singular `/product/:productId` and legacy `PUT`
   inventory calls as quiet fallbacks for existing deployments. This covers create/
   fetch/update/delete, related products, add/update variants, product inventory,
   variant inventory, and product statistics without breaking stores still served
   by the previous route aliases.
 * VARIABLE PRODUCT FIELD COVERAGE – Product mapping now derives parent prices from
   the minimum variation price when a variable parent has no direct regular price,
   and sends variant category metadata from Woo variation attributes so Surfboard/
   POS receives the variant dimensions needed to group child variants correctly.

#### 4.0.396

 * PRODUCT SYNC EMPTY-CATALOG ORPHAN FIX (wave10eb) – Fixes the exact case where
   Surfboard/Saga has zero products but WooCommerce still shows old products after
   manual sync. The pull orphan reconciliation now runs even when the upstream product
   list is empty; every active Woo product linked with `_saga_product_id` that is
   missing upstream is moved to Woo trash (not permanently deleted, so historical
   orders stay safe) and tagged with `_saga_orphaned_at`, `_saga_orphaned_saga_id`,
   and `_saga_orphan_action` for auditability. Manual sync now reports how many 
   products were removed from the active Woo catalogue.
 * PRODUCT FIELD/API COVERAGE – Extends product mapping to preserve `hsnCode`, unit
   type, product type, related products, variant categories, variants, billing/monthly
   plans, and campaign metadata, while continuing to sync barcode/EAN/GTIN, price,
   cost price, VAT, unit, images, dimensions, weight, stock and product properties.
   Adds catalogue API wrappers for fetch-by-id, related products, add/update variants,
   variant inventory, catalogue statistics and product statistics, and includes `
   storeId` in product catalogue request bodies/queries where required by the Surfboard
   product endpoint documentation.
 * RELATED PRODUCTS SYNC – Saga `relatedProducts` now pull into Woo upsells after
   all products are linked, and Woo upsell/cross-sell relationships are pushed back
   to Saga related products when related products already have Saga IDs.
 * PRODUCT SYNC INVARIANTS – Adds a dedicated product-sync invariant test that locks
   the empty-catalog cleanup, related-products mapping, required product API wrappers
   and field round-trip coverage so this regression cannot silently come back.

#### 4.0.395

 * CAPTURE/VOID ALLOWLIST (wave10ea) – Manual Capture and Void buttons (and the 
   matching WooCommerce order-actions, auto-capture-on-Processing/Completed and 
   auto-void-on-Cancelled hooks, plus their AJAX handlers) are now restricted to
   payment methods Saga actually exposes an authorise/capture/void lifecycle for:
   Card, Apple Pay and Google Pay. All other methods (Klarna, Vipps, Swish, MobilePay,
   etc.) are auto-captured by the upstream provider on authorisation — there is 
   no separate authorisation that can be voided, so the previous Void button silently
   failed and never returned funds to the customer. For those orders the Saga Payment
   Actions meta box now shows a clear notice telling the merchant to use the WooCommerce
   Refund button instead, and the order-actions dropdown no longer offers Capture
   or Void. Method is resolved from `_saga_payment_method_used` (API-confirmed) 
   with fallback to `_saga_payment_method` (checkout-time selection); REDIRECT placeholders
   are excluded.

#### 4.0.394

 * WALLET METHOD-SWITCH GUARD (wave10dz) – Adds a final synchronous DOM re-check
   immediately before calling `SDK.order.initiatePayments` in the wallet click handler.
   Reads the live `#saga_payment_method` hidden input value and aborts cleanly (
   no popup, no state mutation) if the customer has switched to a non-wallet method(
   CARD, Klarna, Vipps, Swish, MobilePay) in the same event tick. Prevents a rare
   scenario where users who switch from Google Pay or Apple Pay to Card payment 
   could see a stale Google Pay popup appear from a click that had already been 
   queued in the wallet handler. Wallet-only scope preserved — non-wallet flows 
   are unchanged because the wallet click handler is attached only for GP/AP and
   this re-check only runs inside that handler.

#### 4.0.393

 * MANUAL CANCEL ON “STARTER BETALING…” OVERLAY (wave10dy) – If the customer closes
   the Google Pay or Apple Pay popup (or the popup otherwise fails to complete),
   the “Starter betaling… Vennligst vent mens betalingsvinduet åpnes” overlay no
   longer leaves them permanently stuck. An “Avbryt betaling” button now appears
   on that overlay after a 2.5-second delay (delay prevents the button from flashing
   during a normal fast popup open). Clicking it calls `resetPaymentForRetry` which
   stops all wallet/payment monitoring, clears in-flight flags, and lets the user
   pick another method or retry. Mechanism: added `cancelDelayMs` option to `showProcessingOverlay`
   so a cancel button can be shown after a configurable delay; the “Starter betaling…”
   call now passes `{ cancel: true, cancelDelayMs: 2500 }` instead of the previous`{
   cancel: false }`. The “Behandler bestillingen…” overlay is unchanged (still no
   cancel) because that runs before the SDK is even involved.

#### 4.0.392

 * GOOGLE PAY / APPLE PAY — CLEAN ONE-CLICK CHECKOUT (wave10dx, wallet path only)–
   Removes the yellow/green “klikk Fullfør bestilling på nytt” notice introduced
   in 4.0.391. Replaces it with a silent button-readiness gate: when Google Pay 
   or Apple Pay is the selected method but `SurfboardOnlineSDK.order.initiatePayments`
   is not yet callable, the Place Order button is briefly faded (pointer-events:
   none + opacity 0.6) so the user physically cannot click before the SDK is ready.
   The gate polls every 120 ms and releases the instant the SDK is callable, so 
   the user’s very first real click opens the wallet popup synchronously from a 
   trusted gesture (no Firefox popup block, no “click again” prompt). Scope is strictly
   wallet-only — CARD, Klarna, Vipps, Swish, MobilePay are untouched because the
   gate is engaged only inside `attachWalletClickHandler` for GP/AP. If a click 
   somehow lands during the unready window it is now blocked silently (no inline
   notice, just `preventDefault` + `stopImmediatePropagation` + console.warn diagnostic)
   and the gate engages so the next click works.

#### 4.0.391

 * GOOGLE PAY / APPLE PAY “POPUP NEVER OPENS” FIX (wave10dw, wallet path only) –
   On classic checkout in Firefox (and any browser with strict popup-blocking under
   non-user-gesture context), the Place Order button could be clicked during the
   brief window where `updated_checkout` had torn down and was re-mounting the Saga
   SDK. The wallet click handler saw `SurfboardOnlineSDK.order.initiatePayments`
   was not yet callable, silently bailed, and the click fell through to WooCommerce’s
   asynchronous form submit. By the time the WC AJAX returned and tried to open 
   the wallet popup, the user gesture was gone and Firefox blocked the window. The
   bug presented as the “Starter betaling\u2026” overlay showing forever with no
   Google Pay popup appearing. This release replaces the silent bail with a synchronous
   block (`preventDefault` + `stopImmediatePropagation`), polls SDK readiness for
   up to 5 seconds, and surfaces an inline message asking the user to click Fullf\
   u00f8r bestilling again. The next click is a fresh trusted gesture so the popup
   opens immediately. Scope is strictly wallet-only (Google Pay + Apple Pay); CARD,
   Klarna, Vipps, Swish, and MobilePay flows are unchanged because the handler is
   only attached for wallet methods. Always-on console.warn diagnostics added so
   the bail path is visible without enabling `?saga_debug=1`.

#### 4.0.390

 * Verification release – bumps the version so that merchants on 4.0.389 see an 
   update available on the Updates screen. With the 4.0.389 root-cause fix in place(
   plugin header stripped from `saga-payments-gateway.php`, active_plugins migration
   moved to canonical), the Updates screen should now show exactly one Saga Payments
   row and never the duplicate that crashed the bulk upgrader in earlier releases.
   No functional code changes in this release.

#### 4.0.389

 * DUPLICATE PLUGIN ROW ROOT-CAUSE FIX – Removes the WordPress plugin header (Plugin
   Name, Version, Description, etc.) from `saga-payments-gateway.php` so that `get_plugins()`
   no longer indexes it as a separate plugin entry. The duplicate row appeared because
   WordPress’s plugin discovery scans every .php file with a `Plugin Name:` header
   and treats each match as a distinct plugin; we historically shipped two files
   with plugin headers in the same folder (the canonical `saga-payments.php` and
   the legacy `saga-payments-gateway.php` kept for backward compatibility with installations
   whose `active_plugins` option still recorded the legacy basename). Now the legacy
   file is a header-less compatibility loader that only does `require_once __DIR__.'/
   saga-payments.php';` so backward compatibility for un-migrated stores is preserved(
   WordPress validates active_plugins entries via `file_exists`, not via the plugin
   header, so the legacy basename still loads correctly). The `active_plugins` migration
   code that used to live in the legacy file has moved to canonical so it runs on
   every boot regardless of which entry point WordPress picked. The 4.0.387 / 4.0.388
   runtime filters (all_plugins, site_transient_update_plugins, CSS, JS) remain 
   in place as belt-and-suspenders defense but are no longer the primary mechanism.

#### 4.0.388

 * UPDATES SCREEN DUPLICATE ROW + BULK-UPDATE CRASH FIX – The 4.0.387 dedupe handled
   wp-admin Plugins list but did NOT touch the Updates screen (`update-core.php`),
   which reads from a separate WordPress transient (`update_plugins`) containing
   both basenames. When merchants ticked both rows and clicked Update, WordPress
   tried to upgrade the same `saga-payments/` folder twice and the second pass hit
   a half-written ZIP, crashing the upgrader. This release filters the `site_transient_update_plugins`
   and `transient_update_plugins` read paths at PHP_INT_MAX priority to scrub the
   legacy `saga-payments/saga-payments-gateway.php` entry from `response`, `no_update`,
   and `checked` buckets. A matching CSS+JS row-removal runs on `admin_head-update-
   core.php` so any stale-cached transient still renders as a single row. A one-
   shot `delete_site_transient('update_plugins')` on first 4.0.388 boot forces a
   fresh wp.org check to seed the now-filtered cache cleanly.

#### 4.0.387

 * PLUGINS LIST DUPLICATE ROW BULLETPROOF FIX – The 4.0.386 PHP `all_plugins` filter
   did not take effect on all sites (suspected causes: opcache serving stale bootstrap
   code after in-place update, third-party plugin re-adding the entry at a later
   filter priority, or WP_List_Table population paths that bypass the filter). This
   release adds two reinforcements: (a) the `all_plugins` filter now runs at PHP_INT_MAX
   priority so no other filter can revive the legacy row, and (b) a server-rendered
   CSS rule injected on `admin_head-plugins.php` hides the `<tr data-plugin="saga-
   payments/saga-payments-gateway.php">` row directly in the rendered table with`
   display:none !important`. The CSS path works even if opcache is serving stale
   PHP, because the new code is what writes the style tag in the first place. After
   updating, deactivate and reactivate Saga Payments once to force PHP-FPM to pick
   up the new bootstrap if opcache.revalidate_freq is high.

#### 4.0.386

 * PLUGINS LIST DUPLICATE ROW FIX – The legacy `saga-payments-gateway.php` compatibility
   entry-point no longer shows as a second “Saga Payments for WooCommerce” row on
   the wp-admin Plugins list. The 4.0.319 hide-filter that was supposed to suppress
   it was bailing out whenever the plugins.php URL carried any `action` / `action2`
   query parameter (added in 4.0.329 to protect upload-conflict detection), but 
   WP’s upload-conflict flow runs on update.php, not plugins.php, so the action-
   query guard was unnecessary and was causing the duplicate row to render whenever
   WordPress redirected back to plugins.php with an action parameter (e.g. after
   an activation, or when a host or security plugin added tracking params). The 
   filter now hides the legacy basename unconditionally on the plugins list render,
   while still allowing it through on update.php, plugin-install.php, AJAX, REST
   and WP-CLI so slug-collision / Replace-current upload flows continue to work.

#### 4.0.385

 * EXPRESS SWISH OPEN-APP BUTTON RESTORED – The express-checkout quickbuy popups(
   product, cart, checkout) now consistently render the “Åpne Swish-appen” button
   inside the Swish QR state on both mobile and desktop, matching the classic-checkout
   and Blocks behaviour added in 4.0.381 and 4.0.383. Two fixes were needed: (1)
   the express mobile branch no longer auto-redirects to swish:// before the user
   sees the modal – the QR modal with the Open-app button is now always shown when
   the SDK returns qrData, and the user’s tap on the button is the fresh user gesture
   iOS Safari requires for custom-scheme navigation; (2) a string-only deep scan
   of the SDK result was added as a third fallback for the swish:// URL (after the
   well-known getters/property keys and the qrData fallback), mirroring the helper
   added to checkout.js / blocks.js so the button still renders when the SDK tucks
   the deep link into a nested or non-standard field. Auto-redirect only kicks in
   now as fallback when the SDK did not return a QR payload at all.

#### 4.0.384

 * SWISH CLASSIC MOBILE FALL-THROUGH FIX – The classic-checkout mobile Swish branch
   no longer early-returns after mounting the QR modal and no longer raises a hard“
   Kunne ikke åpne Swish-appen. Prøv igjen” error when neither qrData nor a usable
   swish:// URL was returned by the SDK. Control now falls through to the natural
   post-initiatePayments polling setup just like the desktop branch did all along,
   so the modal is rendered AND the backend payment-status polling is armed in lockstep.
   If the SDK returns nothing at all the normal fall-through handles the verify_url
   redirect path without a duplicate error overlay.

#### 4.0.383

 * SWISH QR MODAL ALWAYS SHOWN – Classic checkout and WooCommerce Blocks now always
   render the Swish QR code modal with the “Åpne Swish-appen” button on BOTH mobile
   and desktop instead of auto-redirecting to the swish:// app on mobile before 
   the modal mounts. Customers now consistently see the QR code (so a second device
   can scan it) and an explicit button to launch the Swish app on the current device.
   The user’s tap on that button is its own fresh user gesture, so iOS Safari still
   accepts the subsequent swish:// navigation – the v4.0.380 user-gesture concern
   is preserved without hiding the visual QR confirmation. Auto-redirect only kicks
   in as fallback when the SDK did not return a QR payload at all.

#### 4.0.382

 * SWISH DEEP-SCAN SIDE-EFFECT GUARD – Removed the SDK getter invocations from the
   4.0.381 deep-scan helper. Generic getter names such as getRedirectUrl could trigger
   SDK side effects (auto-navigation toward the Saga payment page) on certain merchant
   configurations, breaking the Swish flow entirely – the checkout would briefly
   show “Betalingssystemet laster, vent litt” and then auto-navigate to the Saga
   verify_url page instead of opening the Swish app or rendering the QR modal. The
   deep scan now only walks string properties of the SDK result for a usable swish://
   or intent:// URL and never invokes additional getters. The regular code path 
   still calls the safe Swish-specific getters before deep scan kicks in.

#### 4.0.381

 * SWISH OPEN-APP BUTTON DEEP SCAN – Classic checkout and WooCommerce Blocks Swish
   QR modals now find the swish:// deep link even when the SDK tucks it into a nested
   or non-standard field. The mobile redirect path, the desktop QR modal, and the
   blocks modal all fall back to a recursive deep scan of the entire initiatePayments
   result for a usable swish:// / intent:// URL after the well-known getters/property
   keys and the qrData fallback have been exhausted. This fixes the case where 4.0.380
   surfaced the QR code on classic checkout and blocks but no Open Swish app button
   rendered because the SDK on the merchant’s terminal returned the QR payload as
   a data:image/png blob and exposed the deep link only via a nested field.
 * DIAGNOSTIC HARDENING – Added a saga_diag entry on the classic desktop Swish modal
   path capturing whether the SDK exposed a usable URL, the qrData shape (data:image
   vs raw URL) and the top-level result keys, so future merchant-specific SDK shapes
   can be diagnosed without code changes.

#### 4.0.380

 * SWISH MOBILE HOTFIX – Restores the iOS Safari user-gesture window for the swish://
   deep link on classic checkout and WooCommerce Blocks. In 4.0.379 the QR modal
   was mounted BEFORE the redirectToSagaAppUrl(swish://…) call on mobile, and the
   modal’s DOM activity invalidated the user-gesture allowance iOS Safari requires
   for custom-scheme navigation. The Swish app then silently failed to open, polling
   kicked in, and JS eventually navigated to the verify_url which made PHP show “
   Betalingen ble ikke bekreftet. Kontakt support hvis du ble belastet” almost instantly
   after Place Order. The mobile branch now redirects FIRST and only mounts the 
   QR modal as a true fallback (when the redirect dispatch fails or no usable swish
   URL exists).
 * SWISH URL NORMALISATION – Classic checkout (checkout.js) now mirrors the express-
   checkout normaliser: a bare Surfboard payment token returned by the SDK is converted
   into a real swish://paymentrequest?token=… URL before validation, matching what
   blocks.js and express-checkout.js already did. This ensures the Open Swish app
   button and the mobile auto-redirect work on the merchants whose SDK responses
   only contain the token.
 * SWISH QR-AS-DEEPLINK FALLBACK – In all three surfaces (classic, blocks, express)
   the QR data string is now also tried as a deep-link source for the Open Swish
   app button and the mobile redirect. Swish QR payloads commonly ARE the swish://
   URL itself, so when the SDK only populates the QR field (and not getSwishAppRedirectUrl/
   nswishAppRedirectUrl) the same string now drives the button — fixing the case
   where the express quickbuy Swish QR popup showed no Open Swish app button.

#### 4.0.379

 * SWISH CHECKOUT UI RESTORED – The Swish QR modal in both classic checkout and 
   WooCommerce Blocks now shows the real bundled Swish logo (assets/images/swish.
   svg) instead of a green text fallback, matching the design of the Swish express-
   checkout button. Added a dedicated “Open the Swish app” button to ALL Swish QR
   surfaces — classic checkout modal, blocks modal AND every express-checkout quickbuy
   popup (product, cart, checkout) — using the swish:// deep link from the Saga 
   SDK, so customers on mobile (and any desktop visitor with the Swish app installed)
   can manually launch the Swish app if the automatic redirect is blocked by the
   browser. The button uses the Swish brand colour (#50BF6F) with the same look 
   across all three surfaces. On classic mobile checkout the swish:// URL is now
   also passed through to the desktop QR modal path, so a single fallback path covers
   every device. Title text is unified across all three surfaces (i18n key scan_qr“
   Skann QR-koden med Swish-appen”). Customer-visible Norwegian/Swedish strings 
   are loaded via i18n with clean UTF-8 fallbacks (was previously double-encoded
   mojibake such as “Ã…pner Swish-appen…” / “Kunne ikke Ã¥pne Swish-appen. PrÃ¸v
   igjen.”).

#### 4.0.378

 * ADMIN VOID NO LONGER LOOPS – The 5-minute cancelled-order safety scan and the
   central paid-status alias rescue (try_complete_order_from_paid_api_status) now
   skip orders that an admin has explicitly voided (meta _saga_authorization_voided
   =yes). Previously a Klarna authorisation that the admin voided via the Void Authorization
   button would be reactivated five minutes later because Saga’s /orders/{id}/status
   endpoint still reports orderStatus=PAYMENT_COMPLETED for voided Klarna authorisations,
   putting the cancelled order back to on-hold and looping the merchant through 
   repeat void clicks. Card voids worked because Saga reflects them on the order
   status immediately; Klarna does not. With this fix the explicit admin void is
   authoritative and no scan will undo it.

#### 4.0.377

 * DELAYED CAPTURE EVENT MAPPING FIX – Identified the correct terminal event Saga
   emits in Authorize-only mode. Saga sends order.paymentprocessed (paymentStatus
   = PAYMENT_PROCESSED) as the customer-facing success event in delayCapture mode—
   no PAYMENT_COMPLETED webhook arrives until the merchant captures later. The webhook
   handler, central API success check (is_api_payment_success_status), polling and
   the PAYMENT_PROCESSED safety net now all treat PAYMENT_PROCESSED (and AUTHORIZED)
   as a successful terminal state ONLY when delayCapture is enabled. The complete_payment_with_emails()
   short-circuit still parks such orders on-hold with _saga_payment_status=AUTHORIZED
   instead of marking them paid, so this never marks an unauthorised order paid 
   in normal mode (where PAYMENT_PROCESSED remains intentionally ambiguous for Vipps/
   MobilePay). Also extends the cancel/fail webhook API safety check to honour these
   statuses, so a stray CANCELLED webhook will not cancel a successfully authorised
   order in delayCapture mode.

#### 4.0.376

 * DELAYED CAPTURE REDIRECT FIX – With Capture Mode = “Authorize only (capture later)”
   the customer is now correctly redirected to the order-received (thank-you) page
   after a successful authorisation. The central API success check (is_api_payment_success_status)
   now accepts AUTHORIZED as a verified terminal state when delayCapture is enabled,
   so finalize AJAX, the webhook handler, polling, cron self-heal and the wallet
   completion path all treat an authorised payment as a successful customer flow.
   The complete_payment_with_emails() short-circuit still parks such orders on-hold
   with _saga_payment_status=AUTHORIZED instead of marking them paid, and on-hold
   AUTHORIZED orders are now considered ready for the customer redirect. Previously
   the spinner could remain on the checkout indefinitely waiting for PAYMENT_COMPLETED
   that never arrives in authorise-only mode, and (worse) the 2-hour pending auto-
   resolve could eventually cancel the held order.
 * I18N FIX – Repaired mojibake in the inline “Click here to try again” recovery
   button (was rendering literal “Ã¥/Ã¸” instead of “å/ø”) so the Norwegian fallback
   string now matches the i18n lookup key and shows correctly.

#### 4.0.375

 * DELAYED CAPTURE END-TO-END – When “Capture Mode” is set to “Authorize only (capture
   later)” the plugin now sends controlFunctions.delayCapture=true on every order
   create and update, holds successfully authorised orders in on-hold with status
   AUTHORIZED instead of completing them, and uses the documented Saga capture endpoint
   POST /payments/{paymentId}/capture (replaces the previous /captures call that
   was not the canonical endpoint). Auto-capture continues to fire when a held order
   is moved to Processing or Completed, and Auto-Void continues to fire on Cancelled.
   Subscription / MIT renewals are unaffected and still capture immediately.
 * VOID ENDPOINT FIXED – The void/cancel call now uses the documented Saga endpoint
   PUT …

## Мета

 *  Нуска **4.0.420**
 *  Акыркы жаңыртуу **1 жума мурун**
 *  Активдүү орнотуулар **Fewer than 10**
 *  WordPress нускасы ** 5.8 же андан жогору **
 *  Tested up to **7.0**
 *  PHP нускасы ** 7.4 же андан жогору **
 *  Тил
 * [English (US)](https://wordpress.org/plugins/saga-payments/)
 * Тег:
 * [klarna](https://ky.wordpress.org/plugins/tags/klarna/)[mobilepay](https://ky.wordpress.org/plugins/tags/mobilepay/)
   [payment gateway](https://ky.wordpress.org/plugins/tags/payment-gateway/)[vipps](https://ky.wordpress.org/plugins/tags/vipps/)
   [woocommerce](https://ky.wordpress.org/plugins/tags/woocommerce/)
 *  [Advanced View](https://ky.wordpress.org/plugins/saga-payments/advanced/)

## Рейтинг

Азырынча эч кандай сын-пикир жок.

[Your review](https://wordpress.org/support/plugin/saga-payments/reviews/#new-post)

[See all reviews](https://wordpress.org/support/plugin/saga-payments/reviews/)

## Мүчөлөрү

 *   [ sagapay ](https://profiles.wordpress.org/sagapay/)

## Колдоо

Комментарийлер барбы? Жардам керекпи?

 [Колдоо форумун көрүү](https://wordpress.org/support/plugin/saga-payments/)