Title: Saga Payments for WooCommerce
Author: sagapay
Published: <strong>مې 23, 2026</strong>
Last modified: سپتمبر 17, 2026

---

Search plugins

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

# Saga Payments for WooCommerce

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

[Download](https://downloads.wordpress.org/plugin/saga-payments.4.0.751.zip)

 * [Details](https://ps.wordpress.org/plugins/saga-payments/#description)
 * [Reviews](https://ps.wordpress.org/plugins/saga-payments/#reviews)
 *  [Installation](https://ps.wordpress.org/plugins/saga-payments/#installation)
 * [Development](https://ps.wordpress.org/plugins/saga-payments/#developers)

 [Support](https://wordpress.org/support/plugin/saga-payments/)

## Description

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

#### Saga Regnskap (Accounting) Integration

When the store owner completes merchant OTP verification (or clicks the “Koble til
regnskap” button in the gateway settings), the plugin sends a one-time registration
request to the Saga Regnskap accounting service so the merchant’s bookkeeping can
be reconciled against the store. Only store-level facts are sent: the Surfboard 
merchant ID, the store URL, the activation reference, plugin/WordPress/WooCommerce
versions, currency, tax display setting and which gift card system is installed.
No customer data, order contents or payment credentials are transmitted. After registration,
the accounting service reads order and refund TOTALS (amounts, tax rates, Saga payment
references â€” never names, emails, phones, addresses or IPs) from a read-only, 
token-authenticated REST endpoint served by this plugin at /wp-json/saga-accounting/
v1/. This applies only to merchants with a Saga Regnskap agreement; stores without
an agreement store nothing.

 * Service URL: https://regnskap.sagapay.no (fallback: https://saga-regnskap-api.
   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.

## Installation

 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

### 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.

## Reviews

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.

Contributors

 *   [ 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).

## Changelog

#### 4.0.751

 * Product sync: a WooCommerce sale is no longer frozen into the catalogue product.
   Since 4.0.556 the sale price was also written into productProperties (_campaignPrice/
   _campaignActive / salePrice) for the till to charge — but those properties are
   create-only upstream, so a product created while on sale kept the sale price 
   in the catalogue after the sale had ended in the shop (measured 2026-09-17), 
   and a till reading that field would keep charging it. The sale now travels only
   as the catalogue’s campaign entity (created and deleted with the sale since 4.0.748),
   which the till is being updated to read.

#### 4.0.750

 * Product sync: a SKU set at the till reaches WooCommerce. SagaPOS stores a product
   SKU of exactly 4, 6 or 8 digits as the catalogue’s hsnCode and any other SKU 
   as productProperties._sku; the pull read neither, so every product created on
   the till arrived in WooCommerce without a SKU. Both are read now (confirmed with
   the SagaPOS team). A variation’s SKU is read from variantProperties._sku; a variation’s
   hsnCode is the till’s HSN column and is never taken as a SKU.
 * Product sync: a SKU set in WooCommerce reaches the till by the same rule — 4/
   6/8 digits as hsnCode (unless a real HSN code is set on the product, in which
   case the SKU travels as productProperties._sku), anything else as productProperties.
   _sku — so the register can show it. A variation’s SKU is sent as variantProperties.
   _sku.

#### 4.0.749

 * Product sync: a variation deleted in WooCommerce now leaves the till. The catalogue
   has no DELETE on …/variants/{id}; a variant is removed by DELETE /catalog/{catalogue}/
   products/{variantId} — the variant id in the product slot, storeId as a query
   parameter. Measured with the Saga API team. The same call with the parent id 
   would delete the whole product, so the plugin refuses to send it unless the id
   is a stored variant id that differs from the parent’s.
 * Product sync: a variation’s barcode reaches the till. The catalogue drops the
   barcode on variant create in either casing and only takes it via a PATCH afterwards;
   the plugin now sends that PATCH right after the create.
 * Product sync: the product barcode is sent under the lowercase key only — a camel-
   cased key in an update is rejected by the catalogue (PC_0038).
 * Product sync: a product deleted at the till (or in the catalogue) no longer comes
   back from WooCommerce under a new id. The push’s link check unpinned every linked
   product whose id the catalogue list no longer held and re-created it — measured
   on panta.no: a product deleted upstream at 15:57 was back at 16:00:54 with a 
   new id, while the orphan check was correctly waiting out its grace. The link 
   check now only unpins products pinned to ANOTHER catalogue (a moved store); a
   product pinned to this catalogue and gone from it is left to the orphan check,
   which retires the shop’s copy — the till’s deletion wins, and restoring it from
   the trash re-creates it. Links without a catalogue stamp are not judged within
   15 minutes of their last push (the list does not always show a product created
   seconds ago).

#### 4.0.748

 * Product sync: a WooCommerce sale now reaches the till. The campaign call used
   a portal-style path (…/product-catalogue/…/product/…/product-campaign) that the
   catalogue API answers with 404; the route is /catalog/{catalogue}/products/{product}/
   campaign with the campaign name and a fixed amount in minor units. Verified against
   the API together with the Saga API team.
 * Product sync: the product barcode/GTIN now reaches the till. The catalogue stores
   it under `barcode` (lowercase); the plugin sent `barCode`, which the catalogue
   accepted and silently dropped, so every GTIN came back empty.

#### 4.0.747

 * Product sync: a product without a SKU is recognised by its name when pushed, 
   the way the pull has always recognised it. The push’s duplicate check was SKU-
   only, so a SagaPOS-born product with no SKU that also existed in WooCommerce 
   was pushed as a NEW catalogue product — the till showed it twice, and the shop
   was linked to the copy. Measured: thirteen such copies on the test store, now
   healed. Exactly one catalogue product with the same name that nobody else owns
   is linked; an ambiguous name is not guessed.

#### 4.0.746

 * Product sync: a product the shop put in the trash does not come back on its own.
   Every WooCommerce lookup the pull used to recognise a product is blind to the
   trash, so a catalogue row whose shop product had been trashed (the withdrawal
   failed, or the row was a second copy) matched nothing and was created again as
   a new product on the next pull — measured on 2026-09-10, two products trashed
   at 11:35 were back, published, at 11:36. A trashed product with the same SKU,
   or the same exact name, now stops the create; the log says which product and 
   what to do (restore it in WooCommerce, or delete the row in SagaPOS).

#### 4.0.745

 * Product sync: a variation’s price now goes up through the tax lens like every
   other price. The variant payload was built outside the mapper and skipped the
   lens, so for a shop storing prices without tax the pull read a variant through
   the lens while the push sent it bare — the two disagreed by the rate on every
   run (measured on the live tax drive).

#### 4.0.744

 * Product sync: the catalogue’s VAT is the shop’s own echo when it is what the 
   push sent. A product pushed in a tax class the nominal map does not know (a Norwegian
   shop’s `redusert-sats`, say) while that class had no rate went up with “VAT 25”;
   the first pull read 25, had never seen a rate on the product, called that a change
   made at the till and set the class to standard — the merchant’s class gone, silently,
   and every later pull priced the product at the wrong rate. A rate that equals
   what the push would send for the class the product has is not a change and is
   not a mismatch; only a rate that differs from that, and from the last one seen,
   reaches the shop.

#### 4.0.743

 * Product sync: a variable product that manages one stock count for all its variations
   sent that count up once PER VARIANT (measured: a parent with 7 units and two 
   variations put 14 on the till), and the pull then switched each variation to 
   its own stock management with the parent’s number (the shop would have sold 21).
   The parent’s count now goes up once, on the product; a parent-managed variation
   is left as the merchant set it.

#### 4.0.742

 * Product sync: the tax lens on the pull is the class the product HAS in the shop,
   not the class the payload’s nominal VAT maps to. Measured on the live tax drive:
   a product in the reduced class was pushed at 80 × 1,15 = 92,00 and pulled back
   as 92,00, because the payload said VAT 25, the map said standard, and standard
   had no rate.
 * Product sync: the till is told the VAT the shop really charges for the product’s
   class (the sum of its base rates), and a VAT from the catalogue resolves to the
   shop’s own class with that rate before the nominal map (a Norwegian shop’s `redusert-
   sats` at 15 %, not the English slug that does not exist there).

#### 4.0.741

 * Product sync: no single-product push while a sync runs in another process. Three
   times on 2026-09-07 a product got two catalogue entries: the deferred push created
   it while “Synk nå” was pulling, and that request’s create loop, half a minute
   later, still did not see the link — through a purged cache and through a direct
   database read alike. A deferred or admin-save push that finds the pull or push
   lock held by another process now waits 90 seconds instead; by then the sync has
   either created the product (the push finds the link and updates) or finished 
   without it (the push creates, alone).

#### 4.0.740

 * Product sync: the SKU index is rebuilt for every push run, so a product another
   process created seconds earlier is found by its SKU instead of being created 
   again.
 * Product sync: right before any create, the log now records what every source 
   says about the product’s link (the loaded object, the database, a fresh meta 
   read, the row count, the connection’s isolation level and the SKU index age) —
   a second catalogue product was still measured on 4.0.739, and the next occurrence
   must be a measurement, not a theory.

#### 4.0.739

 * Product sync: the database is the only re-read. 4.0.736 purged WordPress’s post-
   meta cache before re-checking a product’s link; measured on 4.0.738, a second
   catalogue product was still created for a product the deferred push had linked
   25 seconds earlier — WooCommerce keeps its own per-object meta cache, and WordPress
   caches query results per request, so the “re-read” was this request’s stale copy
   on two more levels. Both create paths now ask the database directly right before
   creating, and the unlinked-products query is never answered from the request’s
   query cache.

#### 4.0.738

 * Product sync: an empty barcode is no barcode. The catalogue answers `"barCode":""`
   for a product it holds no barcode for; the pull took that as a barcode and emptied
   the merchant’s GTIN/EAN (`global_unique_id`, `_barcode`, `_ean`, `_gtin`) on 
   every pull. Only a non-empty value is a barcode now.
 * Product sync: the till cannot count below zero. A product sold on backorder (
   negative stock in WooCommerce) was set to 0 and marked OUT OF STOCK by the pull,
   and the web stopped selling it. The shop’s negative is kept when the till reports
   0, and every stock status now follows WooCommerce’s own rule (‘onbackorder’ when
   backorders are allowed).
 * Product sync: the push-side SKU dedupe never read `productProperties.wcSku` —
   the field this plugin itself writes — so it was blind to every product it had
   created, and a product that lost its link (a repaired shared link, for example)
   was given a second catalogue product. The index now reads it, and a product whose
   stored payload names a catalogue product that still exists and has no other owner
   is relinked to it instead.

#### 4.0.737

 * Product sync: the catalogue may still hold the figure an earlier build pushed
   without the tax lens (4.0.736). For a shop that stores prices without tax, the
   first pull after upgrading would have read that figure through the lens and cut
   every shop price by a fifth. A catalogue price that equals the shop’s stored 
   price to the øre, while the lens applies, is now recognised as the shop’s own
   echo: the price is kept and sent up again through the lens.
 * Product sync: variation prices go through the tax lens like the parent’s, on 
   both the push and the pull.
 * Product sync: a variation’s `salePrice` in the catalogue is what the shop itself
   wrote when a sale was pushed, and cannot be cleared by a later push. Reading 
   it back re-applied a sale the merchant had ended (the defect 4.0.732 fixed for
   simple products). Only the till’s own discount is now a sale on a variation; 
   a sale the merchant set is left alone; a sale that came from the till is cleared
   when the till drops it.

#### 4.0.736

 * Product sync: one catalogue product per push. When a product saved outside wp-
   admin was carried up by the deferred push while “Synk nå” was running, the running
   push created the same product AGAIN — it re-read the link from its own memory,
   which another process cannot update. Measured: three products doubled in one 
   run. Both push paths now re-read the link from the database after taking the 
   lock.
 * Product sync: a catalogue row whose SKU already belongs to a product linked to
   another catalogue id is a duplicate upstream, not a new product. The pull used
   to refuse the relink and then create a copy in the shop anyway; it now reports
   the duplicate and creates nothing.
 * Product sync: a name longer than 255 characters is fitted on the way out, so 
   the catalogue’s copy never matched the shop’s title and the duplicate guard could
   not see it. Fitted names are now matched by prefix and compared as the push would
   send them.
 * Product sync: the deferred push and the five-minute cron could fall due in the
   same cron request and send the same body twice. Whichever runs second now sees
   the product was pushed after its last edit and sends nothing.
 * Product sync: a linked external or grouped product is taken off the till by the
   pull, whether or not it was edited since the last push (4.0.734 only did so for
   edited products).
 * Product sync: the till’s price is what the customer pays. A shop that stores 
   prices WITHOUT tax and adds it at the checkout (WooCommerce’s default) was sending
   the bare figure to the till — 20 % under the web at 25 % VAT — and the pull wrote
   the till’s consumer price into the ex-VAT field, 25 % over. Prices now pass through
   the shop’s real base tax rates both ways; shops storing prices including tax,
   and shops with no rates, are untouched.

#### 4.0.735

 * Product sync: “Sync now” says when it did not run. A second click while a sync
   was already running was answered “Synk fullført — 0 opprettet, 0 oppdatert …”,
   which reads as “it ran and found nothing”; it had been refused by the lock. It
   now says that a sync is already running and to try again shortly.

#### 4.0.734

 * Product sync: a product that does not manage stock in WooCommerce – a service,
   a virtual or downloadable item, anything the merchant left untracked – is no 
   longer forced out of stock by the pull. The catalogue’s count for such a product
   is 0, and the pull turned stock management on and wrote that 0, so the product
   could not be bought (measured on the test shop: a virtual product was out of 
   stock two minutes after it was created). Stock now follows the till only where
   the shop tracks it.
 * Product sync: external and grouped products are no longer sent to the till. An
   external product is sold on another site and a grouped product has no price of
   its own; both were pushed as ordinary catalogue products (a partner’s item and
   a 0 kr phantom on the till). They are skipped, and any that an earlier build 
   pushed are withdrawn.
 * Product sync: a product name longer than the catalogue’s 255-character limit 
   is fitted on a word boundary instead of being refused every five minutes (“Length
   validation failed”). The shop keeps its full name; the pull recognises the fitted
   copy as its own.
 * Product sync: data the catalogue refuses outright – a price above 999 999,99 
   kr, for instance (“Expecting values between 0-99999999”) – is no longer re-sent
   every five minutes. It is left alone for an hour, or until the merchant edits
   the product, and the reason stays on the sync page.
 * Product sync: a product the sync had retired (its catalogue entry gone) and the
   merchant then restored from the trash is no longer retired again by the next 
   pull. The dead catalogue link and the retirement stamps are dropped on restore,
   and the next push creates it in the catalogue afresh.
 * Product sync: a SKU that already belongs to another shop product’s catalogue 
   entry is no longer linked to it. Two shop products on one catalogue product would
   overwrite each other upstream and pull each other’s data; the newcomer now gets
   its own entry. A product in the trash yields its entry to the newcomer.

#### 4.0.733

 * Product sync: a variation deleted in WooCommerce no longer comes back. Deleting
   a variation told the catalogue nothing, and the next pull – finding a catalogue
   variant with no variation behind it – created it again: measured on the test 
   shop, a variation deleted at 10:07 was back at 10:09 with the same catalogue 
   id. The deletion is now remembered on the product, so the pull never re-creates
   that variant whatever the catalogue lists, and it is withdrawn from the catalogue
   too, with the catalogue’s answer in the log if it refuses.
 * Product sync: a sale the merchant ended in WooCommerce no longer comes back from
   the catalogue. The plugin writes the sale price into the catalogue’s product 
   properties when a product is created, and those properties never change afterwards–
   so the pull kept reading the sale the product was born with and put it back every
   fifteen minutes after the merchant had ended it (measured: ended at 12:26, re-
   applied at 12:41). That echo is no longer read as a sale. The till’s own signals–
   its documented discount and, where adopted, its campaign price – still reach 
   the shop, and the pull now clears only a sale it set from the till, never one
   the merchant set in the shop.
 * Product sync: the sync page keeps showing why a sale was refused by the catalogue
   until the refusal is actually resolved; a later successful product update no 
   longer wipes the reason.
 * Product sync: a sale the catalogue refuses is now retried once an hour, not on
   every run. 4.0.728 marked such a product as unsynced so it would be retried, 
   which meant the product update and the campaign call were both sent every five
   minutes for as long as the refusal lasted. Measured on the test shop: Surfboard’s
   campaign endpoint currently answers 404 (“The requested endpoint does not exist”)
   for every product, so a shop with fifty products on sale would have sent 1,200
   calls an hour into a dead route and been rate-limited for everything else. The
   shop keeps its sale throughout, the sync page names the catalogue’s reason, and
   a sale the merchant changes or ends in the meantime is sent at once.
 * Product sync: NOTE for merchants – sales set in WooCommerce do not currently 
   reach the till, because the catalogue’s campaign endpoint answers 404. The plugin
   keeps the shop’s sale and reports the refusal; the endpoint is Surfboard’s to
   restore.

#### 4.0.732

 * Product sync: a sale set in WooCommerce is no longer cleared by the pull before
   it has reached the till. A sale is sent to the catalogue as a campaign, on a 
   separate call a minute after the save, and the 15-minute pull could land in that
   minute: measured on the test shop, a 70 kr sale was read by the pull as present
   and cleared to nothing, because an unrelated price push a second earlier had 
   marked the product as synced. The pull now keeps a sale that WooCommerce holds
   but the catalogue has no matching campaign for, and lets the push carry it up.

#### 4.0.731

 * Product sync: when the catalogue refuses a sale (campaign), the reason is now
   recorded instead of “(no message)”. The campaign call reported nothing on failure,
   so a refused sale could not be told apart from a duplicate or a missing route
   on the sync page or in the log. The catalogue’s own words and the HTTP status
   are now carried through.

#### 4.0.730

 * Product sync: a SKU changed in WooCommerce after the product was created is no
   longer reverted by the pull. The SKU travels in the catalogue’s product properties,
   which the update call does not carry, so the catalogue keeps the day-one SKU 
   and the pull wrote it back over the merchant’s change. The catalogue’s copy now
   only fills an empty SKU.
 * Product sync: a tax class changed in WooCommerce is no longer silently reverted
   by the pull. The catalogue takes no VAT change after creation, so the shop’s 
   change could never reach the till, and the next pull put the till’s rate back
   without a word. The tax class now follows the catalogue only when the catalogue’s
   rate differs from the rate the shop last saw there – that is, when the till changed
   it – and a shop/till mismatch is written to the log once a day, naming both rates,
   so the side that is wrong can be corrected.

#### 4.0.729

 * Diagnostics: the inventory probe now reads the whole catalogue, page by page,
   instead of the first page only. On any shop with more than one page of products(
   about seventy) it answered “product not found in catalogue” for every product
   past the first page – a false answer, measured on the test shop for product after
   product. An empty page, or a server that repeats page one, ends the read, and
   a not-found answer now says how many products were actually read.

#### 4.0.728

 * Product sync: the pull no longer downloads a second copy of the shop’s own product
   image. The push sends the featured image as a URL on the shop’s own site, the
   catalogue hands it back, and the pull imported it again – measured on the test
   shop: one product, one image, two media-library items after a single round trip,
   and the product’s featured image switched to the copy. A URL that resolves to
   one of the shop’s own attachments is now used as it is, for the featured image
   and the gallery alike, and an image the merchant already chose is never replaced
   by it.
 * Product sync: a sale set in WooCommerce is no longer wiped by the pull when the
   catalogue refused the campaign. The product had already been stamped as pushed
   by the time the campaign call failed, so the pull saw no pending edit and cleared
   the sale because the catalogue had none – measured: a 7 kr sale was gone from
   the shop eight minutes after it was set. A push that falls short now leaves the
   product unpushed, with the catalogue’s reason on the sync page, so the next run
   retries and the shop keeps its prices meanwhile.
 * Product sync: saving variations no longer pushes the whole product once per variation.
   The hook fires for every variation WooCommerce saves, and each firing pushed 
   the parent and all its variants immediately – four full pushes for a four-variation
   product created over the REST API, the first three sent before the later variations
   existed. Outside wp-admin the parent now gets one deferred push a minute later;
   in wp-admin the push still happens at once, but once, after the last variation
   of the request has been saved.

#### 4.0.727

 * Product sync: a “<” that does not start a tag – “under <10 stk”, “a < b” – no
   longer takes the rest of the sentence with it on the way to the till. PHP’s tag
   stripping treats such a “<” as a tag on some PHP versions and not others (measured:
   the test shop’s PHP kept the text, PHP 8.3 returned “under “), so what reached
   the till depended on which PHP the merchant’s host runs. The character is now
   shielded before stripping. Measured on the catalogue: a bare & < > is accepted
   and stored HTML-encoded, and the pull’s comparison sees through that encoding.

#### 4.0.726

 * Product sync: a description with a straight double quote or a backtick now reaches
   the till. Measured on the test shop across 24 products, one character class each:
   the catalogue refuses exactly those two characters in the description and nothing
   else – emoji, HTML, long text, newlines, guillemets and every other punctuation
   mark went through. A merchant who wrote “beste kvalitet” had a product that was
   refused every five minutes and never reached the iPad. The push now sends the
   Norwegian typographic forms instead.
 * Product sync: the pull no longer rewrites a merchant’s product text with the 
   catalogue’s copy of it. The catalogue only ever holds the tag-free copy the push
   sent, and writing it back replaced every bold, list and line break in the shop’s
   own description within fifteen minutes of saving. Text that matches the shop’s
   own outbound copy is left alone; only text changed on the till is applied.
 * Product sync: an edit made in WooCommerce that has not been pushed yet is no 
   longer overwritten by the pull. The push runs a minute after a save and the pull
   every fifteen, and whenever the push was late, throttled or refused, the pull
   wrote the catalogue’s older name, price and text back over the edit and then 
   stamped the product as pushed – so the edit was lost from both systems. The merchant’s
   name, prices, text and dimensions are now kept while an edit is waiting, and 
   the stamp is withheld so the push still carries it up. Stock keeps following 
   the till throughout. The same rule applies to each variation.
 * Product sync: the pull no longer republishes – or untrashes – a product the merchant
   set to draft, private or trash. That revival was meant for products the sync 
   itself had retired, which carry a retirement stamp; without the stamp the status
   was the merchant’s decision and now stands. Catalogue visibility follows the 
   same rule.
 * Product sync: a short description, weight or dimension changed after the product
   was created is no longer reverted by the pull. Those fields travel in the catalogue’s
   product properties, which the update call does not carry (by Surfboard’s own 
   documentation), so what the catalogue holds is the copy frozen at creation. The
   pull now uses that copy only to fill an empty field, never to overwrite a later
   edit.
 * Product sync: a product created in the catalogue is no longer sent a second time
   in the same run. The create path never set the marker the dirty query looks for,
   so every new product was created and then immediately updated with identical 
   data.

#### 4.0.725

 * Product sync: the pull and the push no longer keep each other busy. Measured 
   on the test shop over one hour: 298 variant updates sent to the catalogue, 297
   of them for variants where nothing had changed, and 57 rate-limit errors. The
   pull saved every variable product on every run whether or not anything differed,
   which stamped it as modified, which made the push re-send every one of its variants.
   The pull now compares attributes before saving, and the push remembers what the
   catalogue last accepted for each variant and does not send it again unchanged.
 * Product sync: a catalogue page that is not empty but repeats page one is now 
   recognised for what it is – a server ignoring the page header, with everything
   past page one unreadable – and logged as a short read. Measured: Surfboard sends
   no total in its response headers, so this is the only signal there is; the end
   of a list is an empty page, which is still treated as the end. Discarding a short
   read stays behind the saga_catalog_refuse_short_reads filter, off by default,
   so a server that repeats page one on a one-page catalogue cannot stop a shop’s
   pull.

#### 4.0.724

 * Product sync: the short-read check from 4.0.723 now measures before it enforces.
   What the server’s declared total actually counts – products, variants, or every
   store – has not yet been read from a live catalogue, and if it counts more than
   the listing, discarding every “short” read would have stopped the pull for every
   shop. So by default a short read is now logged at error level, with both numbers,
   and nothing is discarded; the guard that stops a sync-retired product being deleted
   from the catalogue keeps a wrong retirement non-destructive in the meantime. 
   Discarding can be switched on with the saga_catalog_refuse_short_reads filter
   once the header is understood. A shop whose products keep returning to the trash
   will now have the reason in its log after one pull: “declared N products but 
   only M were read”.

#### 4.0.723

 * Product sync: a catalogue read that comes up short is now detected instead of
   being treated as the whole catalogue. Surfboard declares its total in a response
   header that the plugin never read, so a server that ignored the page header and
   answered every page with page one was indistinguishable from a complete read –
   and the orphan check then judged every product not on that page as deleted upstream
   and moved it to the trash. A merchant restoring those products found them back
   in the trash after the next pull, because the read was short every time. A short
   read is now treated exactly like a failed page: nothing is retired on the strength
   of it, and the log says how many the server declared against how many were read.
   When the server declares no total, nothing changes.
 * Product sync: a product the sync itself retired is no longer deleted from the
   catalogue when WordPress purges the trash. WordPress empties the trash after 
   30 days on a cron; the plugin hooked that to the same handler as a merchant’s
   own deletion, whose only guard did not apply on a cron, so every product the 
   sync had parked in the trash was a month later deleted from the real POS catalogue
   by the plugin. If the retirement was right the delete was noise; if it was wrong–
   a short read, a catalogue migration – it destroyed a product the till still sold.
   The push delete-sweep had already been fixed for exactly this after an earlier
   incident; this hook was the other door to the same room. Both now key on the 
   same stamp.
 * The retirement stamp is cleared when the catalogue lists the product again, so
   a product that came back does not carry it for life and have the merchant’s own
   later deletion of it refused.
 * …

## Meta

 *  Version **4.0.751**
 *  Last updated **20 ساعتونه ago**
 *  Active installations **Fewer than 10**
 *  WordPress version ** 5.8 or higher **
 *  Tested up to **7.1.1**
 *  PHP version ** 7.4 or higher **
 *  Language
 * [English (US)](https://wordpress.org/plugins/saga-payments/)
 * Tags
 * [klarna](https://ps.wordpress.org/plugins/tags/klarna/)[mobilepay](https://ps.wordpress.org/plugins/tags/mobilepay/)
   [payment gateway](https://ps.wordpress.org/plugins/tags/payment-gateway/)[vipps](https://ps.wordpress.org/plugins/tags/vipps/)
   [woocommerce](https://ps.wordpress.org/plugins/tags/woocommerce/)
 *  [Advanced View](https://ps.wordpress.org/plugins/saga-payments/advanced/)

## Ratings

No reviews have been submitted yet.

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

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

## Contributors

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

## Support

Got something to say? Need help?

 [View support forum](https://wordpress.org/support/plugin/saga-payments/)