Saga Payments for WooCommerce

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

Changelog

4.0.710

  • CRITICAL fix (a host that cannot take database locks would have taken payments and recorded none): the plugin asks the database for an advisory lock before completing a payment, and treated “no lock” as “another process is already completing this one” – which is correct when the lock is genuinely held by someone else, and catastrophic when the database simply cannot provide one. Connection multiplexers used by several managed hosts (AWS RDS Proxy, ProxySQL and similar) put that lock mechanism out of reach entirely. On such a host EVERY payment completion would have returned early: money taken at the payment system, no order marked paid, and nothing in the shop saying why. The two cases are now separated – real contention still backs off, an unavailable lock falls back to the completion marker the plugin has always kept on the order. On any normal host the behaviour is unchanged.
  • Same fix applied to the scheduled subscription renewal and the pre-order release, where yesterday’s lock hardening had introduced the same trap: on a host without lock support every renewal and every release would have been skipped on every pass, silently and forever. Both now fall back to the transient and order-meta guards that shipped before the lock existed.
  • The lock probe is a single cheap query per request with a zero-second timeout, so it can never itself wait on anything, and a database that refuses it is treated as “no locks available” rather than being allowed to break the site.

4.0.709

  • Hardening (the two automated charge paths were guarded by something that can vanish): the scheduled subscription renewal and the pre-order release each held a per-order lock stored as a WordPress transient. On a shop with a persistent object cache a transient lives in memory and is evicted under memory pressure or dropped entirely by any plugin that flushes the cache – so on a busy store the lock protecting an unattended charge could simply not be there. The safety net behind it does not close the gap on its own, because it reads the last-payment date, and that date is only written after the charge returns; two passes racing inside those few seconds both see “not charged recently”. Both paths now also take a MySQL advisory lock, which no cache can evict and which the database releases by itself if the PHP process dies mid-charge. The merchant-facing charge buttons have worked this way since 4.0.470 – it was the automated paths, the ones nobody is watching, that had only the transient.
  • The transient check is kept in front of it as a cheap first pass, and the last-charge safety window is kept behind it. Three layers, and only the middle one now depends on the object cache.
  • Every exit path releases the new lock, including a fatal mid-charge, so a double-charge guard cannot turn into a stuck subscription.

4.0.708

  • Fix (the “Charge Customer” button had the same half-guard the renewal button had): 4.0.706 taught it to recognise a charge whose confirmation timed out and stop calling that a failure – but it only recorded the payment system’s order id and nothing ever read it back. So the merchant was told to check before charging again, and if they pressed again anyway the plugin charged straight over a charge whose outcome nobody knew. Every path to a stored card now resolves an unconfirmed earlier charge with the payment system first: if it went through it is written into the books as its own order, if it definitively failed a fresh charge is allowed, and if nobody knows yet nothing is charged.
  • Fix: a manual charge discovered on reconciliation now creates the same WooCommerce order an immediate success would, with the same fee line, totals and payment id. Previously a charge that resolved late could have moved money with no order behind it – money the merchant could not see, refund or account for.
  • Internal: the amount of a submitted-but-unconfirmed manual charge is now recorded alongside its id, because the order that records it cannot be created without knowing how much moved.

4.0.707

  • Fix (a subscriber could be charged twice, found by driving a real renewal): 4.0.706 taught the “Test Renewal Now” button to recognise a charge that was submitted but never confirmed, park it, and tell the merchant “It will be reconciled automatically before any new attempt – the card will not be charged twice.” That promise was only true of the scheduled renewal. The button calls the payment API directly and never passes through the scheduled path, so the next press charged straight over the parked charge without ever asking what became of it. Measured on a live test subscription: one press parked an unconfirmed charge, the next press billed the card again. The reconciliation now lives in one place that every path to the card goes through, so the note and the behaviour agree.
  • Fix: the “force” checkbox on the same button skipped the double-charge safety net as well as the is-it-due check. A monthly subscription was measurably charged twice within four minutes. Force still overrides the due date – that is what it is for – but a subscription charged inside the same billing cycle is now refused whichever checkbox is ticked. To take a deliberate extra payment, use “Charge Customer”, which is an explicit ad-hoc charge with its own amount.
  • This was the first renewal ever charged against a real card in testing. Everything else about it held: one press produced exactly one renewal order, the renewal carried its own payment id distinct from the purchase, and an immediate second press was refused.

4.0.706

  • Fix (the same double-charge risk in the two places a merchant triggers a charge by hand): after fixing the pre-order release in 4.0.705, every remaining place that charges a card on file was audited for the same fault. Two more had it. The “charge saved card” button reported a plain “Payment failed” when the charge had in fact been submitted and only its confirmation had timed out – and a merchant who reads “failed” clicks it again. It now says the charge was submitted, names the Saga order so the merchant can check the portal, and tells them not to charge again until they have. The “test renewal” button routed the same case into the retry-scheduling failure handler, which is a retry on a card that may already have been debited; it now parks the charge for automatic reconciliation and schedules nothing.
  • All six places in the plugin that charge a stored card now treat “we did not get an answer” as its own outcome, never as a decline. A new test finds those places in the source rather than working from a list, so a seventh one cannot be added without the same handling.

4.0.705

  • Fix (a pre-order could charge the same card twice): releasing a pre-order charges a stored card with nobody watching. When the charge was submitted but its confirmation never arrived – a verification timeout, not a decline – the order was marked “failed” while the money may already have left the customer’s account. WooCommerce Pre-Orders lets a merchant retry a failed pre-order, and that retry was a second charge on top of one that had already gone through. A timeout is now recognised as its own outcome: the Saga order id is kept, the order goes on hold rather than failed, and the next release attempt asks the payment system what actually happened to that charge before the card is touched again. If it went through, it is recorded as the payment and nothing is charged; if its fate is still unknown, the release waits rather than gambling. The subscription renewal engine has worked this way since 4.0.471 – the pre-order release had never been given the same treatment.
  • Fix: a pre-order release could also be fired more than once – a double-clicked “complete”, a retried request, a batch run twice – and nothing checked whether the card had already been charged. An order already holding a completed payment is now never charged again, and two releases arriving together can no longer both get past that check.
  • No change for pre-orders that release normally, and a genuinely declined card still fails the order exactly as before.

4.0.704

  • Diagnostics: when the payment system refuses an order update, the reason it gave is now written to the debug log. The server has always sent it – which code, which error, which order – and the browser was throwing it away and logging only the sentence shown to the customer, so a real trace read “Could not update the order” and nothing about why. No change to what the customer sees.

4.0.703

  • Fix (4.0.702 could cancel an Apple Pay payment while the customer was still paying): yesterday’s fix released a stuck checkout after 25 seconds. A payment sheet is exactly where people take their time, and elapsed time cannot tell a dead wallet from an unhurried customer – so a customer standing in the Apple Pay sheet had their payment cancelled underneath them.
  • Nothing is cancelled automatically any more. If a wallet payment goes quiet, the customer is simply offered a way out – the same Avbryt betaling button Klarna has always shown – and they decide. Somebody mid-payment ignores it and finishes; somebody stuck clicks it and gets their checkout back. Only that click resets anything, and it still asks the server first, so a payment that went through a second earlier still completes.

4.0.702

  • Fix (an Apple Pay attempt could leave the checkout dead): if the Apple Pay sheet opened and then closed without the payment system reporting back, WooCommerce’s Place Order button kept spinning and the shopper could neither pay nor try again until they reloaded the page. Reported from a real attempt, where the payment system crashed inside its own cancel handler and so never told the shop anything. The checkout now releases the shopper on its own if a wallet payment goes quiet.
  • It still cannot tell a paying customer they have not paid: an Apple Pay or Google Pay sheet closes on success as well as on cancel, so the release asks the server whether the payment landed before concluding anything, and waits long enough for Face ID, a slow network and a late confirmation to finish first. A payment that did go through still completes normally.

4.0.701

  • SECURITY / DOUBLE-SHIPMENT FIX: one shopper’s payment could mark a different shopper’s order as paid. A shopper whose card had been declined, whose checkout tab was still open, could have their unpaid order completed by the next shopper’s payment – leaving two orders marked paid for one payment. A shop with automated fulfilment would ship two parcels and be paid for one. Reproduced end to end on the test store before the fix and refused after it.
  • The cause: several endpoints checked that the caller owned the ORDER but never that they owned the PAYMENT they named. Those are two different questions, and only one of them was being asked. Every endpoint that accepts both an order id and a payment id now verifies with the payment system that the payment belongs to that order before touching it – the same check the finalise endpoint has had since 4.0.493, now applied to all five of its siblings.
  • No change to normal payments: a shopper’s browser sends the payment id its own order already carries, which matches, and nothing extra is requested. The verification only runs when the two disagree.

4.0.700

  • The same guard added in 4.0.699 now also applies where the Blocks checkout reports which method was used. That value comes from the browser and lands in the payment method title that accounting exports group on, so it gets checked the same way as the one the payment system reports. No change to normal payments – this closes the other half of the same gap.

4.0.699

  • Fix (the order list showed “LOAD” instead of the payment method): the payment system reports the type of a transaction that is still open in the same field a finished payment reports its method in, and the plugin took it at face value. Two Klarna orders whose flow did not finish came out labelled LOAD – on one of them, where it should have said Klarna – and the same value went into the payment method title that accounting exports group on. Tokens that are not payment methods are now refused and logged, and what the order already knew is kept. A payment method the plugin has not heard of yet still displays as before, so a genuinely new one is not swallowed.
  • Fix (shoppers who never used a card were told to check their card details): every failed payment ended with “You have not been charged. Check your card details or choose another payment method.” A shopper refused by Klarna on credit has no card details to check, and the one thing they needed to read – choose another way to pay – sat behind advice they could not act on. The same applied to Vipps, Swish, MobilePay, Apple Pay and Google Pay. The wording now fits the method actually used, and an unknown method gets wording that is true whichever it was.

4.0.698

  • Fix (one payment was writing two “payment completed” lines in the order history): an ordinary card purchase confirms through more than one route – the SDK callback settles it, and the browser’s completion poll lands a moment later and settles it again – and each route wrote its own note. On two test purchases, one on the classic checkout and one on Blocks, every order came out carrying “Payment completed via VISA (last four)” and “Payment completed via CARD (Wallet)” for the same payment id. A merchant looking at a disputed order could not tell that from two payment attempts. There is now one note per payment, whichever route confirms it, naming the route so support can still see what settled it.
  • Fix (card payments were labelled as wallet payments): the completion note said “(Wallet)” on every inline card purchase, because the endpoint that writes it is named for wallets for historical reasons but serves every payment method. That label is gone.
  • The card brand and last four digits now appear on the note for both checkouts. Which route confirms a payment first differs between the classic checkout and Blocks, and previously that decided whether the note said “VISA (last four)” or just “CARD”; it is now read from the order instead, so it is the same either way.

4.0.697

  • Removed the developer switch that offered to enable part captures on Vipps. Vipps does not support them – the provider itself refused a real attempt – so a switch claiming otherwise is a promise the payment system will not keep, and anyone turning it on would get a failed capture on an order they then have to unpick. Part captures on Vipps are now simply refused, with the amounts named and what to do instead. There is no merchant setting for this and never was.

4.0.696

  • Fix (partial refunds on Vipps were blocked, and they should not have been): since 4.0.666 the plugin refused to refund part of a Vipps order and displayed a warning above the refund box stating that Vipps can only be refunded in full. That rule had never been put to the provider. It has now: on a live shop, kr 2.00 was collected with Vipps and kr 1.00 refunded against it, and the refund was accepted and settled. The restriction took a capability away from every merchant and told anyone wanting to give half an order back to refund all of it and take a new payment instead. Part refunds now work on Vipps like any other method, and the incorrect warning is gone. A merchant whose own Vipps agreement forbids part refunds can restore the restriction with the filter saga_vipps_allow_partial_refund.
  • Part CAPTURES on Vipps remain blocked – that one the provider genuinely does refuse, and it answered so on a real attempt.

4.0.694

  • The two Vipps restrictions – no part refunds, no part captures – can now be lifted by a filter, so they can be tested against the provider rather than only assumed. The behaviour is unchanged: both still refuse by default. This exists because “Vipps only supports full refunds” had never actually been put to the provider, and if that changes, a rule nobody can switch off quietly takes a capability away from every merchant. Developers: saga_vipps_allow_partial_refund and saga_vipps_allow_partial_capture.

4.0.693

  • Fix (a Vipps refund was recorded as a Klarna one): where the provider settles a refund immediately, the plugin wrote “KLARNA_AUTO_REFUND” as the reference and “Klarna refund processed automatically” as the note – for Vipps, Swish and MobilePay too, because that branch serves all of them and only Klarna was named. Seen on a real Vipps refund. No money was ever wrong, but a merchant reconciling a Vipps refund had to know that Klarna meant Vipps, and the wrong name was carried into the accounting export. The refund now names the method that actually paid.

4.0.692

  • Fix (collecting the whole amount was sent as if it were part of it): the plugin always told Saga how much to collect, and Saga reads any capture that names an amount as a partial one. Vipps refuses those outright, so capturing an entire Vipps payment failed with “SVIPPS does not support partial capture” – on a payment where every krone was being collected. Collecting everything now says so, by not naming an amount at all, which is how the payment system expresses it. Collecting part of a payment is unchanged for the methods that allow it.
  • And a genuine part-capture on Vipps is refused before anything is sent, naming both amounts and what to do instead – restore the order and capture in full, or release the reservation and take a new payment for the lower amount – rather than returning an unknown error on an order the merchant then cannot fix.

4.0.691

  • Fix (money paid with Vipps could neither be collected nor released): a Vipps payment was reserved on the customer’s account and then stranded there. The order said the funds would be taken when it was marked Processing; marking it Processing took nothing. There was no Capture button and no Void button, because the plugin held that Vipps is always collected by the provider straight away. And a refund was refused – correctly, since nothing had been collected – with advice to use a button that was not there. The merchant was paid nothing, the customer’s money stayed on hold until it expired, and neither could do anything about it. Measured on a real payment on a live shop. Vipps now offers Capture and Void like card and Klarna. Swish and MobilePay are unchanged: nobody has put a real payment through either, so they keep the previous behaviour until someone has.

4.0.690

  • Fix (Vipps failed outright on the block checkout): pressing the pay button with Vipps selected produced nothing at all – the payment stopped with “Invalid NONCE” and the checkout reset itself. Before starting a Vipps payment the plugin asks for a brand new payment order, because the provider fixes the phone number permanently to an order once it has been set. When the shop already had a payment order for that basket, the plugin’s own duplicate protection handed the existing one back instead – correctly, so that no customer ends up with two orders – but the Vipps code took it for a new one and reloaded the payment system with credentials that did not belong to it. Vipps now checks what it was given, and simply continues on the order that already exists. The duplicate protection is unchanged; nothing was weakened to make this work.

4.0.689

  • Fix (customers could not pay a payment link): if the order carried the customer’s email address, WooCommerce hid the whole payment form behind “log in or confirm the email address on this order”. Measured on a live shop, on exactly the flow this plugin is sold on – the merchant raises an order by hand and sends the customer its link: no payment form at all, and the email had to be deleted from the order before it could be paid. A customer asked to prove who they are before they may settle an invoice sent to them personally will often just give up, and the merchant never learns why it went unpaid. The link’s own order key is what identifies the customer, exactly as payment links work everywhere else, so it is now accepted as sufficient – but only to pay, and only while the order still needs paying. The order confirmation page keeps the check, and a link to an order that has already been paid keeps it too, so a forwarded link cannot be used to read someone’s order afterwards.

4.0.688

  • Diagnostics: when logging is on, the plugin now records why it needs the second call it makes to Saga before charging a card, and what the first call had already sent. Measured from pressing “Bekreft ordre”, eleven of fifteen seconds pass before the card is charged at all, across three round trips – two of which are the plugin’s own. An attempt to drop the middle one was written and then removed the same evening: it could not fire safely, because the checkout is already marked as changed by that point, and overriding that to save four seconds is how a wrong amount gets charged. The measurement and the reason are now recorded in the code so the next attempt starts from data rather than belief. No behaviour changed for customers.

4.0.685

  • Fix (the checkout was unusable after a Klarna attempt): the empty full-screen element Klarna leaves behind sits on top of the page and swallowed every click, so a customer who came back from Klarna – approved or not – could not press anything on the checkout at all. It is Klarna’s element and their code may still be using it, so it is not removed; clicks are let through while there is nothing in it, and that is reversed the moment Klarna puts something back on screen.

4.0.684

  • Fix (the checkout was left under a dead Klarna layer): Klarna leaves an empty full-screen element behind on the page for good after its window opens. 4.0.676 read that element as “Klarna is on screen” and stood the plugin’s own progress panel aside whenever it was there – which, since it is never removed, meant the panel stayed hidden for the rest of the visit. A customer coming back from Klarna was therefore left looking at a blank layer with no message and no way to cancel. The plugin now checks whether Klarna actually has something on screen rather than whether the element exists, so the progress panel and its cancel button come back as soon as Klarna is done.
  • Fix (a customer Klarna would not approve was told nothing at all): Klarna ends its own window with “we could not approve you for this purchase”, the customer closes it – and the shop said nothing and kept waiting, on a page that still looked busy. Klarna’s payment system never reports that outcome to the shop, so nothing was there to react to. The plugin now notices that the customer has come back from Klarna without a payment, checks with Saga whether one arrived after all, and only then tells them the payment was not completed and that they can try again or choose another method. A Klarna payment that did go through still wins, so nobody who has paid is told otherwise. The wording deliberately does not say “rejected”: from the shop’s side, Klarna refusing someone and the customer simply closing the window look exactly the same.

4.0.682

  • Fix (the checkout could be changed while a payment was being processed): after the order was created the “processing” screen disappeared, and for roughly half a minute – until the payment window actually opened – the customer could still switch payment method or apply a discount code. That changes the amount after the payment for the old amount is already under way, which leaves the charge and the order describing different purchases. The processing screen now stays up for as long as the payment is running, and the controls that can change the amount are inert while it does. If the payment needs the customer’s attention – a card number to correct, for example – the screen still steps aside as before so they can fix it and try again.

4.0.681

  • Fix (express checkout could take payment for something the shop did not have): buying a whole cart with an express button never checked stock. The single-product buttons have always checked, but the cart route validated nothing – it took whatever was in the cart, created the order and charged it. WooCommerce’s normal checkout blocks this; express goes around the checkout, so nothing stood in the way. With a wallet payment the customer is charged before anyone can look, and the merchant is left to find it, refund it and explain it. Every line is now checked before an order exists, and quantities are added up per product so two lines of the same item cannot slip past together.
  • Fix (express buttons were shown on sold-out products): “pay now” was offered on products that could not be bought, so tapping it opened a payment sheet and then failed. The buttons are now hidden when the product cannot be bought – including via the shortcode. A product with some variations left, or one on backorder, still shows them, because those can genuinely be bought.

4.0.680

  • Fix (a refused card emptied the shopper’s basket): a customer whose card was declined was told the payment failed and then found their basket gone, with no way to try again. WooCommerce checks on every page load whether the order it is holding for the shopper still looks unpaid, and empties the basket when it does not. The plugin was handing it an order parked in a preparation state that WooCommerce does not recognise as unpaid, so WooCommerce concluded the payment had gone through and cleared the basket. The order is now published only under the state that matches how it is being used. Verified on the test store: after a declined card the basket is still there and the customer can pay with another card.

4.0.679

  • Diagnostics: when logging is switched on, the plugin now records what emptied the shopping cart and from where. A declined card on the classic checkout was leaving shoppers with an empty basket and no way to try again, and nothing in this plugin does that – so the log now names the caller instead of leaving it to guesswork.

4.0.678

  • Fix (the same “payment completed” line was written to an order several times): the browser checks whether a payment has landed on a repeating poll, and every check that arrived after the payment completed added the note again. Measured on one ordinary card purchase: three identical lines for one payment, and the number varied with the shopper’s connection. A merchant reading the history of a disputed order could not tell that from three payment attempts. The note is now written once per payment.

4.0.677

  • Fix (the block checkout lost the payment system’s messages the same way the classic one did): the fix in 4.0.674 was applied to the classic checkout only. The block checkout refreshes the payment system before a payment in exactly the same way, and that refresh clears the two handlers it had registered – so from that moment nothing was listening. Measured: a Klarna order placed through the block checkout came back without the confirmation the classic checkout records for the same purchase. Both handlers are now restored after every refresh there as well.

4.0.676

  • Fix (two sets of text on top of each other while Klarna opened): Klarna draws its own full-screen message over the shop’s checkout while its payment window opens, and the plugin’s “Betalingen lastes” panel stayed underneath and read through it. The customer got both messages at once and neither was legible. The plugin’s panel now steps aside while Klarna is on screen, and comes back the moment Klarna’s window is gone, so the option to cancel or confirm manually is never lost.
  • Fix (the payment system’s own error and status messages were going nowhere): the plugin registers two handlers with the payment system – one for errors, one for payment status. The payment system accepts each of these once and rejects any later attempt, and since 4.0.552 the plugin had been claiming the error slot early with a small logging-only handler in the belief that the real one would replace it later. It did not; the attempt was rejected, and the rejection stopped the real registration before it reached the status handler. The result: no status handler at all, and an error handler that only wrote to the log – so a cancelled or declined payment produced no message to the customer and no offer to try again. The shop was carried by the plugin’s own polling of the payment status, …