Payment Request

Integration Guide: Payment Request Signatures

Introduction

In the PortOne API, payment requests are signed to ensure their authenticity and integrity. This document provides guidelines on how to generate and verify signatures for payment requests.

Generating a Signature

To generate a signature for a payment request, follow these steps:

  1. Concatenate the required parameters of the payment request into a single string.
  2. Append the merchant's secret key to the concatenated string.
  3. Calculate the SHA-256 hash of the resulting string.
  4. Base64-encode the hash to obtain the signature.

Here's a sample code snippet in Python demonstrating how to generate a signature:


import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/base64"
)

type RequestObj struct {
    ClientKey string
    Currency string
    Amount string
    MerchantOrderId string
    SuccessUrl string
    FailureUrl string
}

func GenerateSignature(requestObj RequestObj, secretKey string) string {
    params := make(url.Values)
    params.Add("client_key", requestObj.ClientKey)
    params.Add("currency", requestObj.Currency)
    params.Add("amount", requestObj.Amount)
    params.Add("merchant_order_id", requestObj.MerchantOrderId)
    params.Add("success_url", requestObj.SuccessUrl)
    params.Add("failure_url", requestObj.FailureUrl)

    data := params.Encode()

    secret := []byte(secretKey)
    message := []byte(data)

    hash := hmac.New(sha256.New, secret)
    hash.Write(message)

    // to base64
    hash_value := base64.StdEncoding.EncodeToString(hash.Sum(nil))
    return hash_value
}

<?php
function GenerateSignature($requestObj, $secretKey) {
  $data = array(
      'amount' => $requestObj.Amount,
      'currency' => $requestObj.Currency,
      'failure_url' => $requestObj.FailureUrl,
      'merchant_order_id' => $requestObj.MerchantOrderId,
      'client_key' => $requestObj.ClientKey,
      'success_url' => $requestObj.SuccessUrl
  );
  ksort($data);
  $data = http_build_query($data);
  $message = $data;

  return base64_encode(hash_hmac('sha256', $message, $secretKey, true));
}
?>

var url = require('url');
var crypto = require('crypto');

function GenerateSignature(requestObj, secretKey) {
    const params = new URLSearchParams();

    params.append('amount', requestObj.Amount)
    params.append('currency', requestObj.Currency)
    params.append('failure_url', requestObj.FailureUrl)
    params.append('merchant_order_id', requestObj.MerchantOrderId)
    params.append('client_key', requestObj.ClientKey)
    params.append('success_url', requestObj.SuccessUrl)

    params.sort();
    var message = params.toString()
    var hash_value =  crypto.createHmac('sha256', secretKey).update(message).hash.digest('base64');

    return hash_value
}

using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;

namespace ConsoleApp
{
    class PaymentRequest
    {
        public string Amount;
        public string ClientKey;
        public string Currency;
        public string FailureUrl;
        public string MerchantOrderId;
        public string SuccessUrl;
    }

    class ApiSecurityExample
    {
        public static string GenerateSignature(PaymentRequest paymentRequest, string secret)
        {
            var map = new Dictionary<string, string>()
            {
                { "amount", RemoveTrailingZeros(paymentRequest.Amount) },
                { "client_key", paymentRequest.ClientKey },
                { "currency", paymentRequest.Currency },
                { "failure_url", paymentRequest.FailureUrl },
                { "merchant_order_id", paymentRequest.MerchantOrderId },
                { "success_url", paymentRequest.SuccessUrl },
            };

            var stringBuilder = new StringBuilder();
            foreach (var key in map.Keys)
            {
                if (stringBuilder.Length > 0)
                {
                    stringBuilder.Append("&");
                }
                var value = map[key];
                try
                {
                    stringBuilder.Append((key != null ? Uri.EscapeDataString(key) : ""));
                    stringBuilder.Append("=");
                    stringBuilder.Append(value != null ? Uri.EscapeDataString(value) : "");
                }
                catch (ArgumentNullException e)
                {
                    throw new Exception("The key or value is null.", e);
                }
                catch (UriFormatException e)
                {
                    throw new Exception("Invalid format for key or value.", e);
                }
            }

            var message = stringBuilder.ToString();
            Console.WriteLine("message: " + message);
            var encoding = new ASCIIEncoding();
            byte[] keyByte = encoding.GetBytes(secret);
            byte[] messageBytes = encoding.GetBytes(message);
            var hmacsha256 = new HMACSHA256(keyByte);
            byte[] hashmessage = hmacsha256.ComputeHash(messageBytes);
            string hash_value = Convert.ToBase64String(hashmessage);

            return hash_value;
        }

        private static string RemoveTrailingZeros(string amount)
        {
            decimal decimalAmount;
            if (decimal.TryParse(amount, out decimalAmount))
            {
                // Format the decimal without trailing zeros after the decimal point
                return decimalAmount.ToString("0.###################");
            }
            return amount; // Return the original amount if parsing fails
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            PaymentRequest paymentRequest = new PaymentRequest()
            {
                Amount = "50",
                Currency = "USD",
                FailureUrl = "https://checkout.protone.cloud/failure.html",
                MerchantOrderId = "testing123456654321",
                ClientKey = "Portone-Key",
                SuccessUrl = "https://checkout.protone.cloud/success.html"
            };

            string secret = "Portone-Secret-Key";

            string signature = ApiSecurityExample.GenerateSignature(paymentRequest, secret);

            Console.WriteLine("Signature: " + signature);
        }
    }
}

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class Main {
    public static void main(String[] args) {
        RequestObj requestObj = new RequestObj(
            "your_portone_key",
            "THB", // currency
            "100", // amount
            "53056340451223466", // merchant order id
            "https://your_domain.com/success",  // success URL
            "https://your_domain.com/failure"); // failure URL
        String signature = generateSignature(requestObj, "your_portone_secret_key");
        System.out.println("Generated Signature: " + signature);
    }

    public static String generateSignature(RequestObj requestObj, String secretKey) {
        try {
            String data = "amount=" + URLEncoder.encode(requestObj.getAmount(), StandardCharsets.UTF_8.toString()) +
                "&client_key=" + URLEncoder.encode(requestObj.getClientKey(), StandardCharsets.UTF_8.toString()) +
                "&currency=" + URLEncoder.encode(requestObj.getCurrency(), StandardCharsets.UTF_8.toString()) +
                "&failure_url=" + URLEncoder.encode(requestObj.getFailureUrl(), StandardCharsets.UTF_8.toString()) +
                "&merchant_order_id=" + URLEncoder.encode(requestObj.getMerchantOrderId(), StandardCharsets.UTF_8.toString()) +
                "&success_url=" + URLEncoder.encode(requestObj.getSuccessUrl(), StandardCharsets.UTF_8.toString());

            Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256");
            sha256_HMAC.init(secretKeySpec);

            byte[] hash = sha256_HMAC.doFinal(data.getBytes());
            return Base64.getEncoder().encodeToString(hash);
        } catch (Exception e) {
            throw new RuntimeException("Failed to calculate hmac-sha256", e);
        }
    }
}

class RequestObj {
    private String clientKey;
    private String currency;
    private String amount;
    private String merchantOrderId;
    private String successUrl;
    private String failureUrl;

    public RequestObj(String clientKey, String currency, String amount, String merchantOrderId, String successUrl, String failureUrl) {
        this.clientKey = clientKey;
        this.currency = currency;
        this.amount = amount;
        this.merchantOrderId = merchantOrderId;
        this.successUrl = successUrl;
        this.failureUrl = failureUrl;
    }

    public String getClientKey() {
        return clientKey;
    }

    public String getCurrency() {
        return currency;
    }

    public String getAmount() {
        return amount;
    }

    public String getMerchantOrderId() {
        return merchantOrderId;
    }

    public String getSuccessUrl() {
        return successUrl;
    }

    public String getFailureUrl() {
        return failureUrl;
    }
}

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class Main {
    public static void main(String[] args) {
        RequestObj requestObj = new RequestObj(
            "your_portone_key",
            "THB", // currency
            "100", // amount
            "53056340451223466", // merchant order id
            "https://your_domain.com/success",  // success URL
            "https://your_domain.com/failure"); // failure URL
        String signature = generateSignature(requestObj, "your_portone_secret_key");
        System.out.println("Generated Signature: " + signature);
    }

    public static String generateSignature(RequestObj requestObj, String secretKey) {
        try {
            String data = "amount=" + URLEncoder.encode(requestObj.getAmount(), StandardCharsets.UTF_8.toString()) +
                "&client_key=" + URLEncoder.encode(requestObj.getClientKey(), StandardCharsets.UTF_8.toString()) +
                "&currency=" + URLEncoder.encode(requestObj.getCurrency(), StandardCharsets.UTF_8.toString()) +
                "&failure_url=" + URLEncoder.encode(requestObj.getFailureUrl(), StandardCharsets.UTF_8.toString()) +
                "&merchant_order_id=" + URLEncoder.encode(requestObj.getMerchantOrderId(), StandardCharsets.UTF_8.toString()) +
                "&success_url=" + URLEncoder.encode(requestObj.getSuccessUrl(), StandardCharsets.UTF_8.toString());

            Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256");
            sha256_HMAC.init(secretKeySpec);

            byte[] hash = sha256_HMAC.doFinal(data.getBytes());
            return Base64.getEncoder().encodeToString(hash);
        } catch (Exception e) {
            throw new RuntimeException("Failed to calculate hmac-sha256", e);
        }
    }
}

class RequestObj {
    private String clientKey;
    private String currency;
    private String amount;
    private String merchantOrderId;
    private String successUrl;
    private String failureUrl;

    public RequestObj(String clientKey, String currency, String amount, String merchantOrderId, String successUrl, String failureUrl) {
        this.clientKey = clientKey;
        this.currency = currency;
        this.amount = amount;
        this.merchantOrderId = merchantOrderId;
        this.successUrl = successUrl;
        this.failureUrl = failureUrl;
    }

    public String getClientKey() {
        return clientKey;
    }

    public String getCurrency() {
        return currency;
    }

    public String getAmount() {
        return amount;
    }

    public String getMerchantOrderId() {
        return merchantOrderId;
    }

    public String getSuccessUrl() {
        return successUrl;
    }

    public String getFailureUrl() {
        return failureUrl;
    }
}