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

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 [email protected] 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.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 /payments/{paymentId}/void (replaces the previous POST /transactions/{id}/cancel). The plugin surfaces Saga’s voidStatus (VOID_INITIATED / VOIDED / CANNOT_VOID); CANNOT_VOID is correctly treated as a failure with a clear error note. Voids on captured payments must happen before 23:00 UTC same day; voids on delayCapture authorisations can happen any time before capture.

4.0.374

  • README ENCODING CLEANUP – Fixed double-encoded UTF-8 characters in readme.txt that displayed as garbled symbols on the ww.wp.xz.cn listing (checkmarks, em-dashes, arrows, the Norwegian letter o-slash). Shortened short description to satisfy the ww.wp.xz.cn 150-character limit. No functional code changes – this is a documentation-only release.

4.0.373

  • MERCHANT VERIFICATION (OTP onboarding) – New verification panel under WooCommerce > SagaPay settings. On first install (or after credentials change) the store owner clicks “Send verification code”, receives a 6-digit OTP at the email registered with their SagaPay account, and confirms it to bind the store’s outbound IP range to its merchant credentials. Prevents credential abuse from other servers. Three layers of rate-limiting (5 starts/hour, 5 wrong-guess lockout, 10 min TTL). Backend verification is enforced by the SagaPay edge worker; unverified stores receive a clear “Merchant not verified: complete OTP onboarding” error rather than a generic forbidden response, making the required action obvious to the merchant.

4.0.372

  • WP.ORG LISTING FIXES – Synchronised the legacy saga-payments-gateway.php Version header to 4.0.372 (was 4.0.356, which caused wp.org to flag a version mismatch between the plugin header and the SVN tag), bumped tested-up-to to WP 7.0 / WC 10.7 in both entry files, removed third-party gateway brand references from the public description, added a setup note clarifying that Terminal ID and Public Key are auto-provisioned on save once Merchant ID and Store ID are entered, and trimmed the long changelog to satisfy the 5,000-word limit (full per-wave history retained in the plugin source).

4.0.371

  • WALLET FAILED BACKEND-VERIFY RESCUE (wave10dv) – For wallet payment methods (Apple Pay, Google Pay, Vipps, Swish, MobilePay) the failed-status branch now performs an authoritative backend verification via saga_complete_wallet_payment before surfacing a payment error. SDK-reported FAILED can be overruled by a Saga API PAYMENT_COMPLETED status, eliminating a race where a transient wallet SDK failure would mark a successful payment as failed. Also includes wave10du wallet visibility/polling immediate backend checks at multiple submit gates, fail-open semantics so verified-paid orders are never blocked by client-side state. Hardens against charged-but-declared-failed scenarios across all wallet flows.
  • WP 7.0 “Armstrong” compatibility verified – no breaking changes for the plugin. jQuery retained, PHP 7.4 minimum unchanged, HPOS unchanged, classic checkout unchanged, WC Blocks API unchanged. Plugin tested up to WP 7.0 and WC 10.7.
  • Many internal stability and reconciliation improvements (waves 10df through 10du) covering Klarna fresh-order rebind, webhook HMAC volume mapping, surfboard poll throttling, real-iframe cancel detection, Klarna repeat-cancel latch reset, and post-success suppression. See plugin source for full wave-by-wave technical notes.

Older releases

  • Full per-version changelog (4.0.0 through 4.0.355) is preserved in the plugin’s source repository. ww.wp.xz.cn limits the readme changelog to 5,000 words, so detailed older entries have been archived there to keep this listing concise.