Web Checkout Integration

The Samsung Pay Web Checkout feature can be easily implemented on your website.

Prerequisites

Before you can integrate Samsung Pay Web Checkout, the following requirements must be met:

  • You have a Samsung Pay merchant ID. To obtain it, complete the partner onboarding process.

  • The acquirer and issuer support tokenized transactions for in-app purchases, according to card network specifications.

Integrating Samsung Pay Web Checkout

To integrate the Samsung Pay Web Checkout solution to your website:

  1. Include the Samsung Pay Web SDK JavaScript file in your website front-end:

    <script src="https://img.mpay.samsung.com/gsmpi/sdk/samsungpay_web_sdk.js"></script>
    
  2. Define the supported payment methods and Samsung Pay API version in the paymentMethods object.
    You must also provide your unique merchant ID in the serviceId key.

    const paymentMethods = {
      "version": "2",
      "serviceId": "dcc1cbb25d6a470bb42926",
      "protocol": "PROTOCOL_3DS",
      "allowedBrands": ["visa","mastercard"]
    }
    
  3. Initialize the Samsung Pay client by creating an instance of the PaymentClient object.
    You must also define the operation environment for your Web Checkout:

    • STAGE = staging environment, used for testing
    • STAGE_WITHOUT_APK = staging environment, used to simulate the testing process without using a device authorization. Visit Staging without APK full guide here.
    • PRODUCTION = production environment, for actual payments
    const samsungPayClient = new SamsungPay.PaymentClient({environment: "STAGE"});
    

    If your project has a Content-Security-Policy (CSP) applied, please ensure that you add a nonce to the CSS to maintain compliance. This can be done by updating your SDK configuration as follows:

    const samsungPayClient = new SamsungPay.PaymentClient({environment: "STAGE", nonce: "your-nonce"});
    
  4. Check whether Samsung Pay is supported for the payment request, using the isReadyToPay() method with the paymentMethods object:

    samsungPayClient.isReadyToPay(paymentMethods).then(function(response) {
      if (response.result) {
        // add a payment button 
      }
    }).catch(function(err) {
      console.error(err); 
    });
    
  5. Add the Samsung Pay button to your page using the official button asset:

<div id="samsungpay-container">
  <button id="samsung-pay-btn">
      <img src="/your/path../samsung-pay-button.png" alt="Samsung Pay" style="{follow the Samsung's official branding guideline}" />
  </button>
</div>
  1. Add your event handler to the button:
document.getElementById("samsung-pay-btn").addEventListener("click", onSamsungPayButtonClicked);
  1. Create the transaction information.
    The transactionDetail object contains the order number, merchant information, and total amount for the purchase.

    const transactionDetail = {
      "orderNumber": "DSTRF345789dsgTY",
      "merchant": {
        "name": "Virtual Shop",
        "url": "virtualshop.com",
        "id": "xn7qfnd",
        "countryCode": "US"
      },
      "amount": {
        "option": "FORMAT_TOTAL_ESTIMATED_AMOUNT",
        "currency": "USD",
        "total": 300
      }
    }
    
  2. Launch the payment sheet.
    When the onClick() event is triggered, your event handler must call the loadPaymentSheet() method, which initiates the Web Checkout UI flow. When the user confirms the payment from their mobile device, you receive the paymentCredential object generated by the device.

  3. Extract the payment credential information from the 3DS.Data key within the paymentCredential object and process it through your payment provider.

  4. Inform the Samsung server of the payment result using the notify() method within the paymentResult object.

samsungPayClient.loadPaymentSheet(paymentMethods, transactionDetail).then(function(paymentCredential) {
  // Process payment with provider
  ...
  ...
  const paymentResult = {
    "status": "CHARGED",
    "provider": "PG Name"
  }  
  samsungPayClient.notify(paymentResult);
}).catch(error => {
  // Show error in developer console for debugging
  console.error(err);
});

Payment credential sample

The paymentCredential is the resulting output of the loadPaymentSheet() method.

Sample paymentCredential JSON output (using JWE-only)

{
  "method": "3DS",
  "recurring_payment": false,
  "card_brand": "visa",
  "card_last4digits": "8226",
  "3DS": {
    "type": "S",
    "version": "100",
    "data": "eyJhbGciOiJSU0ExXzUiLCJraWQiOiIxZHlsbkFVRVJtTk53Z0J0MmVzcEVWU1pOSWRZZGhqbVI3bzhQcDVKaGVBPSIsInR5cCI6IkpPU0UiLCJjaGFubmVsU2VjdXJpdHlDb250ZXh0IjoiUlNBX1BLSSIsImVuYyI6IkExMjhHQ00ifQ.JyKXN2h9pk1Uj-4kNPuij1r49yKw7-3aElZnHAdzsZTcLVJLhOYjOmujfl1H21yQ_5rMdwZ9Lj6o67j8M6KN_1dNKvNQAUgi203oL5teGF-J15n_pcINJ1nYcfYIVOhazIdbg9FQ2nztS_mUu9cvYKIZ-iFsuz6RfL9aiuoaKjpcTZPN8LWlddZxZme3j86sD45i-AhXWBuJFVy9D2ZRT1sddgOxGOrJRzy3o5S29PYbKAYtJMcpc_Jicu-sDSX3S1Snm_CVHAqiCcOxYidIh6hFwo35FssWysvxu8yFPgTWbcdaI9uJkptVR7npNp1cH85JA3DvW3MI87v-pwiqmw.HdZesNBxU0d0T68e.pCv1CSIBW7JGtlgFOOvmeBM-wGGpW9rHOnBkDb_qWWFL_CuF7_0nj_kNuozQ4pUDK0_vzkTbHi3kV0Gt2YBmQs6ZfpnXD3CDPgK_lYiO8Z8xCiASoz5vLTamJG7n5mAAdXxPVqWtCPk_TbKsVE2ke8W7R3U4kApFJl2EnE06J3E4rkae367X8_aOXy2l3LHoeqZl4lFsNtFs71xfc-s9h5-Bgi2clKBa-9hLRtpbxTUmWa830rwywm7M.Fs5-tfBxQ73L7ICRRWKblA"
  }
}

The decrypted output will be similar to this:

{
    "amount": "100",
    "currency_code": "USD",
    "utc": "1719388643614",
    "eci_indicator": "5",
    "tokenPAN": "5185731679991253",
    "tokenPanExpiration": "0127",
    "cryptogram": "AKkeaVcvwHfmAMmud6r3AoACFA=="
}

Decrypting Payment Credentials

For security reasons, the payment credential data that you receive is protected by JSON Web Encryption (JWE). To decrypt the payment credentials:

  1. Generate a DER file from your private key:

    $ openssl pkcs8 -topk8 -in Merchant.key -outform DER -nocrypt -out rsapriv.der
    
  2. Decrypt the JWE encrypted data.

    • Sample implementation in Java:

      import java.nio.file.Files;
      import java.nio.file.Paths;
      import java.security.KeyFactory;
      import java.security.interfaces.RSAPrivateKey;
      import java.security.spec.PKCS8EncodedKeySpec;
      import java.util.Base64;
      
      import javax.crypto.Cipher;
      import javax.crypto.spec.GCMParameterSpec;
      import javax.crypto.spec.SecretKeySpec;
      
      import com.fasterxml.jackson.databind.JsonNode;
      import com.fasterxml.jackson.databind.ObjectMapper;
      
      public class DeveloperPortalSample {
      
          public static void main(String[] args) throws Exception {
             // Example JWE string (replace with your actual JWE and private key path)
              String encryptedText = {{encryptedPayload}};
              String privateKeyPath = "./rsapriv.der";
      
              String PRIVATE_KEY = Base64.getEncoder().encodeToString(Files.readAllBytes(Paths.get(privateKeyPath)));
              String result = decryptJWE(encryptedText, PRIVATE_KEY);
              System.out.println(result); 
          }
      
          public static String decryptJWE(String encryptedText, String privateKeyText) throws Exception {
              // Split JWE parts by '.'
              String delims = "[.]";
      
              String[] tokens = encryptedText.split(delims);
      
              if (tokens.length < 5) {
                  throw new IllegalArgumentException("Invalid JWE format");
              }
      
              // Decode and parse JWE header
              byte[] headerBytes = Base64.getUrlDecoder().decode(tokens[0]);
              String headerJson = new String(headerBytes);
              ObjectMapper mapper = new ObjectMapper();
              JsonNode header = mapper.readTree(headerJson);
      
              // Extract algorithm information from header
              String alg = header.has("alg") ? header.get("alg").asText() : "RSA1_5";
              String enc = header.has("enc") ? header.get("enc").asText() : "A128GCM";
      
              // Convert private key
              byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyText);
              PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
              KeyFactory keyFactory = KeyFactory.getInstance("RSA");
              RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(privateKeySpec);
      
              // Decode encrypted key, IV, ciphertext, and authentication tag
              byte[] encKey = Base64.getUrlDecoder().decode(tokens[1]);
              byte[] iv = Base64.getUrlDecoder().decode(tokens[2]);
              byte[] cipherText = Base64.getUrlDecoder().decode(tokens[3]);
              byte[] tag = Base64.getUrlDecoder().decode(tokens[4]);
      
              // Create Cipher instance based on key management algorithm
              String keyManagementAlgorithm;
              boolean useAAD = false;
      
              if ("RSA-OAEP".equals(alg)) {
                  keyManagementAlgorithm = "RSA/ECB/OAEPPadding";
                  // At Samsung, OAEP uses AAD (Additional Authenticated Data)
                  useAAD = true;
              } else if ("RSA1_5".equals(alg)) {
                  keyManagementAlgorithm = "RSA/ECB/PKCS1Padding";
                  // while RSA1_5 does not use AAD.
                  useAAD = false;
              } else {
                  throw new IllegalArgumentException("Unsupported key management algorithm: " + alg);
              }
      
              // Decrypt the CEK (Content Encryption Key)
              Cipher decryptCipher = Cipher.getInstance(keyManagementAlgorithm);
              decryptCipher.init(Cipher.DECRYPT_MODE, privateKey);
              byte[] plainEncKey = decryptCipher.doFinal(encKey);
      
              // Create Cipher instance based on content encryption algorithm
              String contentEncryptionAlgorithm;
              int gcmTagLength;
      
              if ("A128GCM".equals(enc) || "A256GCM".equals(enc)) {
                  contentEncryptionAlgorithm = "AES/GCM/NoPadding";
                  gcmTagLength = 128;
              } else {
                  throw new IllegalArgumentException("Unsupported content encryption algorithm: " + enc);
              }
      
              // Decrypt the content
              Cipher contentCipher = Cipher.getInstance(contentEncryptionAlgorithm);
              GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(gcmTagLength, iv);
              SecretKeySpec keySpec = new SecretKeySpec(plainEncKey, "AES");
              contentCipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);
      
              // AAD handling: Use Base64Url-encoded header bytes as AAD 
              if (useAAD) {
                  byte[] encodedHeader = Base64.getUrlEncoder().withoutPadding().encode(headerBytes);
                  contentCipher.updateAAD(encodedHeader);
              }
      
              // Concatenate cipherText and tag, then pass to doFinal
              byte[] cipherData = new byte[cipherText.length + tag.length];
              System.arraycopy(cipherText, 0, cipherData, 0, cipherText.length);
              System.arraycopy(tag, 0, cipherData, cipherText.length, tag.length);
              byte[] plainText = contentCipher.doFinal(cipherData);
      
              return new String(plainText, java.nio.charset.StandardCharsets.UTF_8);
          }
      
    • Sample implementation in C#

      using System;
      using System.IO;
      using System.Text;
      using System.Text.Json.Nodes;
      using System.Security.Cryptography;
      
      	public static void Main(string[] args)
      	{
      		// Example JWE string (replace with your actual JWE and private key path)
      		string encryptedText = {{encryptedPayload}};
      		string privateKeyPath = ./rsapriv.der";
      
      		// Read the private key file (DER format)
      		byte[] privateKeyBytes = File.ReadAllBytes(privateKeyPath);
      
      		// Decrypt the JWE
      		string result = decryptJWE(encryptedText, privateKeyBytes);
      
      		// Print the result
      		Console.WriteLine(result);
      	}
      
      	public static string decryptJWE(string encryptedText, byte[] privateKeyBytes)
      	{
      		// Split JWE parts by '.'
      		var parts = encryptedText.Split('.');
      		if (parts.Length < 5)
      			throw new ArgumentException("Invalid JWE format");
      
      		// Decode and parse JWE header
      		var headerBytes = Base64UrlDecode(parts[0]);
      		var headerJson = Encoding.UTF8.GetString(headerBytes);
      		var header = JsonNode.Parse(headerJson);
      
      		// Extract algorithm information from header
      		string alg = header?["alg"]?.ToString() ?? "RSA1_5";
      		string enc = header?["enc"]?.ToString() ?? "A128GCM";
      
      		// Convert private key (assume PKCS8 DER)
      		using var rsa = RSA.Create();
      		rsa.ImportPkcs8PrivateKey(privateKeyBytes, out _);
      
      		// Decode encrypted key, IV, ciphertext, and authentication tag
      		var encKey = Base64UrlDecode(parts[1]);
      		var iv = Base64UrlDecode(parts[2]);
      		var cipherText = Base64UrlDecode(parts[3]);
      		var tag = Base64UrlDecode(parts[4]);
      
      		// Create Cipher instance based on key management algorithm
      		bool useAAD = false;
      		if (alg == "RSA-OAEP")
      		{
      			// At Samsung, OAEP uses AAD (Additional Authenticated Data)
      			useAAD = true;
      		}
      		else if (alg == "RSA1_5")
      		{
      			// while RSA1_5 does not use AAD.
      			useAAD = false;
      		}
      		else
      		{
      			throw new ArgumentException($"Unsupported key management algorithm: {alg}");
      		}
      
      		// Decrypt the CEK (Content Encryption Key)
      		byte[] plainEncKey = alg == "RSA-OAEP"
      			? rsa.Decrypt(encKey, RSAEncryptionPadding.OaepSHA1)
      			: rsa.Decrypt(encKey, RSAEncryptionPadding.Pkcs1);
      
      		// Decrypt the content
      		using var aes = new AesGcm(plainEncKey, 16);
      		var plainText = new byte[cipherText.Length];
      		if (useAAD)
      		{
      			// AAD handling: Use Base64Url-encoded header bytes as AAD 
      			var encodedHeader = Encoding.ASCII.GetBytes(Base64UrlEncode(headerBytes));
      			aes.Decrypt(iv, cipherText, tag, plainText, encodedHeader);
      		}
      		else
      		{
      			aes.Decrypt(iv, cipherText, tag, plainText);
      		}
      		return Encoding.UTF8.GetString(plainText).TrimEnd('\0');
      	}
      
      	private static byte[] Base64UrlDecode(string input)
      	{
      		string s = input.Replace('-', '+').Replace('_', '/');
      		switch (s.Length % 4)
      		{
      			case 2: s += "=="; break;
      			case 3: s += "="; break;
      		}
      		return Convert.FromBase64String(s);
      	}
      
      	private static string Base64UrlEncode(byte[] input)
      	{
      		return Convert.ToBase64String(input)
      			.TrimEnd('=')
      			.Replace('+', '-')
      			.Replace('/', '_');
      	}