Online SDK (Self-Hosted Page)

Online SDK (Self-Hosted Page)

Introduction

SagaPay’s online payment solution comes in three different modes tailored to meet your diverse business needs:

  • Payment Page
  • Online SDK (Self-Hosted Page)
  • Merchant Initiated

Online SDK (Self-Hosted Page) is one of these modes where SagaPay renders fields on your web-shop page, providing you with the flexibility to customise the layout any way you want.

Note

To know more about our online payment terminals available refer to the online guide.

The following guide applies only to the Online SDK (Self-Hosted Page) flow.


Loading the SDK

To use the Online SDK, you need to include the Surfboard SDK script in your HTML page. Add the following script tag in your <head> section:

1<script src="https://cdn.surfboardpayments.com/sdk/online-sdk.js"></script>
SDK Script URL

The SDK script URL will be provided by your account manager during terminal setup. The URL above is an example - use the URL provided specifically for your integration.

The SDK exposes a global SurfboardOnlineSDK object that you’ll use for all payment operations.


Initialising the SDK

To initialise the SDK, you will need following three params:

ParameterDescription
publicKeyThis is provided as part of the terminal registration process and is used for all calls for that terminal
orderIdThis is received as a response to the createOrderRequest
nonceThis is received as a response to the createOrderRequest and is important as it serves as the access control for the APIs needed to complete the payment

You can also retrieve the terminal’s public key later using the Fetch Terminal by ID API. The public key is returned as terminalPublicKey in the API response.

Once you have the three params, you can initialise the SDK with the following code. This returns a promise which you can await if you have an async method:

1SurfboardOnlineSDK.errorCallback((code, message) => {
2 console.log("Entered the callback here");
3 console.error(code, message);
4});
5
6SurfboardOnlineSDK.paymentStatusCallback = function (data) {
7 // Your payment status handling function here
8 // structure of data
9 //{
10 // paymentStatus: 'PAYMENT_INITIATED'|'PAYMENT_CANCELLED' | 'PAYMENT_COMPLETED' | 'PAYMENT_FAILED' | 'PAYMENT_PROCESSING' | 'PAYMENT_PROCESSED',
11 // failureReason? : string
12 // }
13};
14
15SurfboardOnlineSDK.initialiseOnlineSDK({
16 publicKey: "", //Your public key
17 orderId: "", //Include the orderId
18 nonce: "", //Include the nonce
19});
20
21SurfboardOnlineSDK.remountOnlineSDK({
22 publicKey: "", // Your public key
23 orderId: "", // New orderId
24 nonce: "", // New nonce
25});

The errorCallback function that you provide will notify you of the various error cases as it occurs. Any error that occurs as part of the initialisation is usually a terminal error. Refer to the error table below to identify the error type.

The paymentStatusCallback is used to list the changes in paymentStatus and can be listened to handle UI changes accordingly.

The SurfboardOnlineSDK.remountOnlineSDK method re-initializes the SDK for a different order (for example, if the customer changes their cart or you want to start a new payment). This method takes the same parameters as initialiseOnlineSDK and allows you to dynamically update the SDK context without a full page reload.


Error Codes

When the SDK is initialised, it could either complete successfully or run into one of the following errors:

Error CodeError MessageExplanationError Category
Surfboard SDK cannot function in the given environmentThe Online SDK cannot function in this environment. This can occur if the code is executed in Node and also in certain headless browser environmentsFATAL
Surfboard SDK initialisation failedThe required params are not presentFATAL
Public key validation failedThe provided publicKey is not validFATAL
Invalid Order IDThe provided orderId is not validFATAL
Invalid NonceThe provided nonce is not validFATAL
401Invalid or Expired Link. Cannot proceedThe backend has declined to proceed with the order. This could be because of several reasons.FATAL

Data Objects

If none of the errors are encountered, you would have the following interfaces exposed on the SurfboardOnlineSDK window object:

  • Orders
  • Merchant
  • Branding
  • Store
  • Payment methods
  • Customer info

Order Object

1order: {
2 order: {
3 orderId: string;
4 terminalId: string;
5 billingAddress: IAddress;
6 orderStatus: string;
7 orderDate: string;
8 memberId: string;
9 totalOrderPriceValue: string;
10 totalOrderPriceCurrency: string;
11 memberEmail: string;
12 memberPhone: {
13 countryCode: string;
14 number: string;
15 };
16 referenceId: string;
17 orderLines: [{
18 externalItemId: string;
19 gtin: string;
20 brand: string;
21 itemPrice: {
22 value: string;
23 currency: string;
24 campaign: string;
25 regular: string;
26 tax: [{
27 type: 'VAT';
28 percentage: number;
29 amount: number;
30 }];
31 };
32 itemId: string;
33 quantity: number;
34 unit: string;
35 name: string;
36 description?: string;
37 imageUrl?: string;
38 }];
39 tax: ITax[];
40 },
41 addCustomerInformation: (customerDetails) => Promise<boolean>,
42 initiatePayments: paymentMethod => Promise<PaymentAttempt | Error>
43}
44
45interface IAddress {
46 addressId?: string;
47 careOf?: string;
48 addressLine1: string;
49 addressLine2?: string;
50 addressLine3?: string;
51 city: string;
52 countryCode: string;
53 postalCode: string;
54}
55
56interface ITax {
57 type: 'VAT';
58 percentage: number;
59 amount: number;
60}

Merchant Object

1merchant: {
2 name: string;
3 organizationNumber: string;
4}

Branding Object

1branding: {
2 backgroundColor: string; // '#ffffff'
3 brandColor: string;
4 accentColor: string;
5 rectShape: "EDGY" | "ROUNDED" | "PILL";
6 fontType: "serif" | "sans-serif" | "monospace";
7 logoUrl: string;
8 iconUrl: string;
9}

Store Object

1store: {
2 name: string;
3 phoneNumber: {
4 countryCode: string;
5 number: string;
6 };
7 email: string;
8 address: {
9 addressId?: string;
10 careOf?: string;
11 addressLine1?: string;
12 addressLine2?: string;
13 addressLine3?: string;
14 city?: string;
15 countryCode?: string;
16 postalCode?: string;
17 };
18 privacyPolicyUrl: string;
19 termAndConditionUrl: string;
20}

Payment Methods Object

1paymentMethods: {
2 supportedPaymentMethods: Array<IPaymentMethods>;
3 lockToPaymentMethods: Array<IPaymentMethods> | null;
4}

Customer Object

1customer: {
2 name?: string;
3 email?: string;
4 phone?: ICustomerPhone;
5 orderBillingAddress?: {
6 addressId?: string;
7 careOf?: string;
8 addressLine1?: string;
9 addressLine2?: string;
10 addressLine3?: string;
11 city?: string;
12 countryCode?: string;
13 postalCode?: string;
14 };
15 customerId?: string;
16 savedCards?: ISavedCard[];
17 savedAddresses?: {
18 addressId?: string;
19 careOf?: string;
20 addressLine1?: string;
21 addressLine2?: string;
22 addressLine3?: string;
23 city?: string;
24 countryCode?: string;
25 postalCode?: string;
26 }[];
27}
28
29interface ICustomerPhone {
30 countryCode: string;
31 number: string;
32}

You can use the data provided here to display the required information on the web checkout.

Important

It is mandatory for you to display the contact information, privacy policy and the terms and conditions on the payment page.


Payment Flow

Almost all payment methods require you to provide customer information if it is not already present as part of creating the order.

Updating Customer Information

When handling online payments via the SDK, it is vital to include specific customer information for the payment. This customer information can be provided via the SDK or through the address field while creating the order.

Mandatory Fields by Payment Method

Payment MethodMandatory Fields
Card PaymentsEmail, Phone, Address
KlarnaEmail, Phone, Address, Shipping Address (for physical goods)
Apple PayEmail, Name, Phone, PostalAddress
Google PayEmail, Address (handled automatically via Google Pay sheet)
VippsEmail, Name, Address (NO phone!)
SwishPhone

To update the customer information you can use the following code:

1SurfboardOnlineSDK.order.addCustomerInformation(customerDetails)
2
3// Structure of customer details
4{
5 name?: string;
6 email?: string;
7 phone?: {
8 countryCode: string;
9 number: string;
10 };
11 billingAddress?: {
12 addressId?: string;
13 careOf?: string;
14 addressLine1?: string;
15 addressLine2?: string;
16 addressLine3?: string;
17 city?: string;
18 countryCode?: string;
19 postalCode?: string;
20 };
21}
Important

Please note that the customer’s address is mandatory for card payments due to requirements from the payment schemes and acquirers we follow. We recommend that, whenever you have the address, you include it in the API request when creating the order. This way, the address will be pre-filled and displayed to the customer, eliminating the need for them to enter it manually.

In case the customer details update fails for any reason, the following error code is returned:

Error CodeMessageDescriptionError Category
ON_008Customer Information update failedThe backend returned an errorNon Fatal

Initiating Payments

Payments can be initiated for the following methods:

  • Swish payments
  • Vipps payments
  • Card payments
  • Apple Pay
  • Google Pay
  • Klarna

Swish Payments

Swish payment involves customers making the payment in their Swish app. For mobile devices, the customer will be automatically redirected to the Swish app, complete the transaction, and then redirected to your application. For web payments, the customer will need to scan the displayed QR code using the Swish app and complete the payment.

To initiate a Swish payment, you can use the following code:

1// Initiate the payment
2const paymentAttempt = await SurfboardOnlineSDK.order.initiatePayments("NSWISH");

This will generate a payment attempt object that provides the following utility methods:

1getPaymentStatus(); // Returns the last known payment status {paymentStatus: PAYMENT_STATUS}
2getSwishAppRedirectUrl; // Provides the URL to redirect the customer to the Swish app
3getSwishQRData; // Returns the QR code data to display for the customer to scan
  • getSwishAppRedirectUrl(callback?: string) returns the URL to redirect the customer to the Swish app on mobile devices. When calling this method, make sure to provide a callback URL where the Swish app should redirect the customer after the payment is complete.
  • getSwishQRData returns the QR code data to display on the web for the customer to scan and complete the payment.

The paymentStatusCallback will return the change in payment status’s as it happens.

Vipps Payments

Vipps is a Norwegian mobile payment solution. Vipps payments are only available for customers paying in Norwegian Kroner (NOK).

Currency Restriction

Vipps payments are only available for NOK currency. Ensure your order is created with NOK before offering Vipps as a payment option.

To initiate a Vipps payment, you can use the following code:

1// Initiate the payment
2const paymentAttempt = await SurfboardOnlineSDK.order.initiatePayments("SVIPPS");
Critical: No Phone Number for Vipps

Vipps must NOT receive a phone number in the customer data. Including a phone number will break the payment flow. Only provide name, email, and billing address.

Customer Data for Vipps

1// Correct customer data for Vipps - NO phone field!
2const customerInfo = {
3 name: "Ola Nordmann",
4 email: "ola@example.com",
5 billingAddress: {
6 addressLine1: "Storgata 1",
7 city: "Oslo",
8 postalCode: "0123",
9 countryCode: "NO"
10 }
11 // DO NOT include phone for Vipps!
12};
13
14await SurfboardOnlineSDK.order.addCustomerInformation(customerInfo);

Vipps Payment Flow

  1. Desktop/Mobile browser: SDK opens a popup/overlay where the customer sees Vipps QR code or instructions
  2. Customer opens the Vipps app on their mobile and approves the payment
  3. SDK receives PAYMENT_COMPLETED via paymentStatusCallback

Payment Status Handling

1SurfboardOnlineSDK.paymentStatusCallback((data) => {
2 const status = data.paymentStatus;
3
4 switch(status) {
5 case 'PAYMENT_INITIATED':
6 // Payment started, popup is displayed
7 break;
8 case 'PAYMENT_COMPLETED':
9 // Payment successful - redirect to confirmation
10 break;
11 case 'PAYMENT_CANCELLED':
12 // Customer cancelled the payment
13 break;
14 case 'PAYMENT_FAILED':
15 // Payment failed
16 break;
17 }
18});
Recurring Payments

Vipps does not support recurring/subscription payments via SDK. For products with automatic renewal, the customer must use card (CARD) to save a payment token for future MIT transactions.

Card Payments

To process card payments with the SDK, you need to provide a mount point for the SDK to load the fields. The SDK provides various options for how these fields are displayed, and you control this through configuration passed during mounting.

  1. In your web page, add a div element that will hold the card input fields:
1<div id="card-details"></div>
  1. Call the mount function by passing in the relevant containers:
1SurfboardOnlineSDK.mount({
2 mountCardWidget: "card-details",
3 mountApplePayWidget: "apple-pay-button",
4});

The SDK will use this container to load and display the card fields when you initialise it.

Apple Pay

To process Apple Pay with the SDK you need to provide a mount point in your HTML and call the SurfboardOnlineSDK.mount() function as follows:

1<div id="apple-pay"></div>
1SurfboardOnlineSDK.mount({
2 mountApplePayWidget: "apple-pay",
3});

This renders the Apple Pay button which handles the payment.

Apple Pay Additional Setup

For Apple Pay to work, you must set up the domain association file correctly.

Apple Pay Domain Association File Setup:

  1. Download/Copy the domain association file
  2. Host it on each domain/subdomain you want to use Apple Pay on
  3. Use the exact filename: apple-developer-merchantid-domain-association
  4. Place it in the /.well-known/ directory

File requirements:

  • Content-Type must be text/plain
  • Must be publicly accessible
  • No password protection
  • No proxy or redirects

The file needs to be accessible at this exact location for Apple Pay verification to work properly.

You can wait for the response in paymentStatusCallback function to check the payment status.

Google Pay

Google Pay™ allows customers to complete purchases using their debit or credit cards linked to their Google Accounts.

Acceptable Use Policy

All merchants must adhere to the Google Pay™ APIs Acceptable Use Policy and accept the terms defined in the Google Pay™ API Terms of Service.

Mounting Google Pay

To render the Google Pay button, add a mount point in your HTML and include it in the mount() call:

1<div id="google-pay"></div>
1SurfboardOnlineSDK.mount({
2 mountCardWidget: "card-details",
3 mountApplePayWidget: "apple-pay",
4 mountGooglePayWidget: "google-pay",
5});

The SDK will render the Google Pay button in the specified container. When clicked, it opens the Google Pay payment sheet where customers can select their saved cards or add a new one.

Google Pay Setup Requirements

To enable Google Pay for your integration, you need to complete the following steps in the Google Pay Business Console:

  1. Access Google Pay Business Console

  2. Domain Configuration

    • Add your store’s domain(s) for integration
    • Our API will verify and register all domains under your ownership
  3. Website Integration Setup

    • In the left navigation, click “Google Pay API”
    • Select “Website Integrate” → “Add website”
    • Choose Gateway as your Integration type
    • Upload required business images
    • Fill in all required business information
    • Submit for review
  4. Activation

    • Google will review your integration request
    • You’ll receive confirmation once Google Pay is activated for your website

Supported Card Networks

The following card networks and authentication methods are supported:

1const allowedCardNetworks = ["AMEX", "DISCOVER", "MASTERCARD", "VISA"];
2const allowedCardAuthMethods = ["PAN_ONLY", "CRYPTOGRAM_3DS"];
3D-Secure Compliance

Google Pay transactions using PAN_ONLY mode use the actual card number and must comply with 3D-Secure (SCA) requirements. If 3D-Secure applies to your market region, it will be automatically enabled on your merchant account. CRYPTOGRAM_3DS uses a secure token and does not require additional 3D-Secure verification.

Payment Status

Google Pay payment status is tracked through the same paymentStatusCallback function used for other payment methods.

Regional Availability

Some Google Pay and Google Wallet features may be unavailable in certain countries or regions. Learn more about availability.

Klarna

To initiate a Klarna payment, make sure you update the required customer information as following example:

1SurfboardOnlineSDK.order.addCustomerInformation({
2 phone: { countryCode: "+46", number: "701740615" },
3 name: "customer name",
4 email: "customer@email.se",
5 billingAddress: {
6 city: "Stockholm",
7 postalCode: "60320",
8 countryCode: "SE",
9 addressLine1: "16th Street",
10 },
11});

You can then initiate the Klarna payment simply using:

1SurfboardOnlineSDK.order.initiatePayments("KLARNA");

The payment status can be tracked using paymentStatusCallback function.


Payment Error Codes

During this process you might face the following error codes:

Error CodeMessageDescriptionError Category
ON_009Phone number is a required parameter to perform a swish paymentThe customer phone number is not set for this PaymentNon Fatal
ON_010This payment method is not supported for this paymentThe provided payment method is not available for the particular orderNon Fatal
ON_011This payment is already completedThe payment is completed and cannot be proceeded further. The payment might be successful, cancelled or failedNon Fatal
ON_012An error occurred while initiating payments. Please try againThis usually means there is an error from the backend due to various reasons. You can build in a limited number of retry attempts on your sideNon Fatal
ON_013Unknown Error. Please try againThis usually means the SDK is unable to initiate the payment. Retries would generally not solve it. Reloading the page might help.Non Fatal
ON_014Maximum Checks Exceeded. Please refresh PageThe payment page is set to check the status every 1.5secs via polling. The SDK polls for a total of 300 counts. If this limit is exceeded before the payment being completed, the following error occurs. In most cases, this means the payment is either cancelled or completed. It is suggested to refresh the pageNon Fatal
ON_015Payment Status cannot be fetched currently. Please Try AgainAs part of continuous polling, if more than 10 status checks fail continually, then this error is returned. This is usually a problem with the backend system being down or non-responsive and cannot be solved on the customer end.Non Fatal
ON_016Invalid Card DetailsCard details are invalid. Customer needs to update the informationNon Fatal
ON_017Email is a required parameter to perform this paymentCustomer needs to update the emailNon Fatal
ON_018Billing Address is a required Parameter to perform this paymentCustomer needs to update the billing addressNon Fatal

Complete Example

Here’s a complete example of an HTML page with the Online SDK integrated:

1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8" />
5 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6 <meta
7 http-equiv="Content-Security-Policy"
8 content="default-src * 'unsafe-inline' 'unsafe-eval'; script-src * 'unsafe-inline' 'unsafe-eval'; connect-src * 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src *; style-src * 'unsafe-inline';"
9 />
10 <!-- Include the Surfboard SDK - URL provided by your account manager -->
11 <script src="{{YOUR_SURFBOARD_SDK_URL}}"></script>
12 <title>Payment Page</title>
13</head>
14<body>
15 <!-- Container for card input fields -->
16 <div id="card-details"></div>
17
18 <!-- Container for Apple Pay button -->
19 <div id="apple-pay"></div>
20
21 <!-- Container for Google Pay button -->
22 <div id="google-pay"></div>
23
24 <!-- Payment buttons -->
25 <button id="pay-with-card">Pay with Card</button>
26 <button id="pay-with-vipps">Pay with Vipps</button>
27 <button id="pay-with-klarna">Pay with Klarna</button>
28
29 <script>
30 // Extract order ID and nonce from URL parameters
31 const urlParams = new URLSearchParams(window.location.search);
32 const orderId = urlParams.get('o');
33 const nonce = urlParams.get('n');
34
35 // Set up error callback
36 SurfboardOnlineSDK.errorCallback((code, message) => {
37 console.error('SDK Error:', code, message);
38 });
39
40 // Set up payment status callback
41 SurfboardOnlineSDK.paymentStatusCallback((data) => {
42 console.log('Payment Status:', data.paymentStatus);
43
44 if (data.paymentStatus === 'PAYMENT_COMPLETED') {
45 // Redirect to success page
46 window.location.href = '/payment-success';
47 } else if (data.paymentStatus === 'PAYMENT_FAILED') {
48 // Show error message
49 alert('Payment failed: ' + (data.failureReason || 'Unknown error'));
50 }
51 });
52
53 // Initialize the SDK
54 SurfboardOnlineSDK.initialiseOnlineSDK({
55 publicKey: "{{YOUR_PUBLIC_KEY}}", // From terminal registration
56 orderId: orderId,
57 nonce: nonce,
58 }).then(() => {
59 // Mount payment widgets
60 SurfboardOnlineSDK.mount({
61 mountCardWidget: 'card-details',
62 mountApplePayWidget: 'apple-pay',
63 mountGooglePayWidget: 'google-pay',
64 });
65 });
66
67 // Card payment button
68 document.getElementById('pay-with-card').addEventListener('click', () => {
69 SurfboardOnlineSDK.order.initiatePayments('CARD');
70 });
71
72 // Vipps payment button (NOK only, no phone number!)
73 document.getElementById('pay-with-vipps').addEventListener('click', async () => {
74 // Add customer info WITHOUT phone for Vipps
75 await SurfboardOnlineSDK.order.addCustomerInformation({
76 name: "Customer Name",
77 email: "customer@example.com",
78 billingAddress: {
79 addressLine1: "Street 1",
80 city: "Oslo",
81 postalCode: "0123",
82 countryCode: "NO"
83 }
84 });
85 SurfboardOnlineSDK.order.initiatePayments('SVIPPS');
86 });
87
88 // Klarna payment button
89 document.getElementById('pay-with-klarna').addEventListener('click', () => {
90 SurfboardOnlineSDK.order.initiatePayments('KLARNA');
91 });
92 </script>
93</body>
94</html>
Sample Application

Contact your account manager for access to complete sample applications including React integration examples.