Regular Subscription

Introduction

PortOne API payment requests are signed to ensure their authenticity and integrity. This document provides guidelines on how to generate signature for a regular subscription request.

Steps

To generate a signature for a regular subscription request, follow these steps:

  1. Concatenate the required parameters of the request into a single string in alphabetical order.
  2. Calculate the SHA-256 hash of the resulting string.
  3. Base64-encode the hash to obtain the signature.

Code Examples

package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"fmt"
	"net/url"
	"strconv"
)

type RequestObj struct {
	ClientKey        string
	Currency         string
	FailureUrl       string
	MerchantOrderRef string
	Quantity         int64
	SuccessUrl       string
}

func GenerateSignature(requestObj RequestObj, portOneSecret string) string {
	// Create a url.Values map and add the necessary parameters
	params := make(url.Values)
	params.Add("client_key", requestObj.ClientKey)
	params.Add("currency", requestObj.Currency)
	params.Add("failure_url", requestObj.FailureUrl)
	params.Add("merchant_order_ref", requestObj.MerchantOrderRef)
	params.Add("quantity", strconv.Itoa(int(requestObj.Quantity)))
	params.Add("success_url", requestObj.SuccessUrl)

	// Encode the parameters
	data := params.Encode()
	// fmt.Println("data is:", data)

	// Create the HMAC hash using SHA-256
	secret := []byte(portOneSecret)
	message := []byte(data)
	hash := hmac.New(sha256.New, secret)
	hash.Write(message)

	// Convert the hash to a base64 string
	hashValue := base64.StdEncoding.EncodeToString(hash.Sum(nil))
	return hashValue
}

func main() {

	portOneKey := "PORTONE_KEY"
	portOneSecret := "PORTONE_SECRET"
	// The unique merchant order reference generated by the merchant
	merchantOrderRef := ""

	requestObj := RequestObj{
		ClientKey:        portOneKey,
		Currency:         "USD",
		MerchantOrderRef: merchantOrderRef,
		Quantity:         1,
		SuccessUrl:       "https://subscription.portone.cloud/success.html",
		FailureUrl:       "https://subscription.portone.cloud/failure.html",
	}

	// Generate the signature
	signature := GenerateSignature(requestObj, portOneSecret)

	// Output the signature
	fmt.Println("Signature is:", signature)
}
<?php
function GenerateSignature($requestObj, $secretKey) {
  $data = array(
      'client_key' => $requestObj->ClientKey,
      'currency' => $requestObj->Currency,
      'failure_url' => $requestObj->FailureUrl,
      'merchant_order_ref' => $requestObj->MerchantOrderRef,
      'quantity' => $requestObj->Quantity,
      'success_url' => $requestObj->SuccessUrl
  );
  ksort($data);
  $data = http_build_query($data);
  $message = $data;

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

function main() {
    
    $key = "PORTONE_KEY";
    $secret_key = "PORTONE_SECRET";
    // The unique merchant order reference generated by the merchant
    $merchant_order_ref = "";
    // Example request object
    $requestObj = (object) [
        'ClientKey' => $key,
        'Currency' => 'USD',
        'MerchantOrderRef' => $merchant_order_ref,
        'Quantity' => 1,
        'SuccessUrl' => 'https://subscription.portone.cloud/success.html',
        'FailureUrl' => 'https://subscription.portone.cloud/failure.html'
    ];

    $signature = GenerateSignature($requestObj, $secret_key);
    echo "Signature: " . $signature;
}

main();
?>
const crypto = require('crypto');
const { URLSearchParams } = require('url');

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

    params.append('client_key', requestObj.ClientKey);
    params.append('currency', requestObj.Currency);
    params.append('failure_url', requestObj.FailureUrl);
    params.append('merchant_order_ref', requestObj.MerchantOrderRef);
    params.append('quantity', requestObj.Quantity);
    params.append('success_url', requestObj.SuccessUrl);
    params.sort();

    const message = params.toString();
    const hashValue = crypto.createHmac('sha256', secretKey).update(message).digest('base64');

    return hashValue;
}

const clientKey = 'PORTONE_KEY';
const secretKey = 'PORTONE_SECRET';
// The unique merchant order reference generated by the merchant
const merchantOrderRef = '';

const requestObj = {
    ClientKey: clientKey,
    Currency: 'USD',
    FailureUrl: 'https://subscription.portone.cloud/failure.html',
    MerchantOrderRef: merchantOrderRef,
    Quantity: 1,
    SuccessUrl: 'https://subscription.portone.cloud/success.html'
};

const signature = GenerateSignature(requestObj, secretKey);
console.log(`Signature: ${signature}\n`);
using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;

namespace ConsoleApp
{
    class PaymentRequest
    {
        public string ClientKey;
        public string Currency;
        public string FailureUrl;
        public string MerchantOrderRef;
        public int Quantity;
        public string SuccessUrl;
    }

    class ApiSecurityExample
    {
        public static string GenerateSignature(PaymentRequest paymentRequest, string secret)
        {
            var map = new SortedDictionary<string, string>()
            {
                { "client_key", paymentRequest.ClientKey },
                { "currency", paymentRequest.Currency },
                { "failure_url", paymentRequest.FailureUrl },
                { "merchant_order_ref", paymentRequest.MerchantOrderRef },
                { "success_url", paymentRequest.SuccessUrl },
                { "quantity", paymentRequest.Quantity.ToString() }
            };

            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;
        }
    }

  class Program
    {
       static void Main(string[] args)
        {
            string key = "PORTONE_KEY";
            string secret = "PORTONE_SECRET";
            // The unique merchant order reference generated by the merchant
            string merchantOrderRef = "";
            PaymentRequest paymentRequest = new PaymentRequest()
            {
                ClientKey = key,
                Currency = "USD",
                FailureUrl = "https://subscription.portone.cloud/failure.html",
                MerchantOrderRef = merchantOrderRef,
                SuccessUrl = "https://subscription.portone.cloud/success.html",
                Quantity = 1
            };

            

            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;
import java.text.DecimalFormat;
import java.util.Map;
import java.util.TreeMap;

public class Main {
    public static void main(String[] args) {
        String key = "PORTONE_KEY";
        String secret = "PORTONE_SECRET";
        // The unique merchant order reference generated by the merchant
        String merchantOrderRef = "";
        String currency = "USD";
        Integer quantity = 1;

        // Create an instance of RequestObj using the default constructor
        RequestObj requestObj = new RequestObj();

        // Use setter methods to set the values
        requestObj.setClientKey(key);
        requestObj.setCurrency(currency);
        requestObj.setMerchantOrderRef(merchantOrderRef);
        requestObj.setSuccessUrl("https://subscription.portone.cloud/success.html");
        requestObj.setFailureUrl("https://subscription.portone.cloud/failure.html");
        requestObj.setQuantity(quantity);

        String signature = generateSignature(requestObj, secret);
        System.out.println("Signature: " + signature);
    }

    public static String generateSignature(RequestObj requestObj, String secretKey) {
        try {
            Map<String, String> params = new TreeMap<>();
            params.put("client_key", requestObj.getClientKey());
            params.put("currency", requestObj.getCurrency());
            params.put("failure_url", requestObj.getFailureUrl());
            params.put("merchant_order_ref", requestObj.getMerchantOrderRef());
            params.put("success_url", requestObj.getSuccessUrl());
            params.put("quantity", requestObj.getQuantity().toString());

            // Encode the parameters
            String data = encodeParams(params);
            // System.out.println("data: " + data);
            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);
        }
    }
    
    public static String encodeParams(Map<String, String> params) {
        StringBuilder encodedParams = new StringBuilder();

        for (Map.Entry<String, String> entry : params.entrySet()) {
            if (encodedParams.length() > 0) {
                encodedParams.append("&");
            }
            encodedParams.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8))
                         .append("=")
                         .append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8));
        }

        return encodedParams.toString();
    }
}

class RequestObj {
    private String clientKey;
    private String currency;
    private String merchantOrderRef;
    private String successUrl;
    private String failureUrl;
    private Integer quantity;

    // Default constructor
    public RequestObj() {
    }

    // Setter methods
    public void setClientKey(String clientKey) {
        this.clientKey = clientKey;
    }

    public void setCurrency(String currency) {
        this.currency = currency;
    }

    public void setMerchantOrderRef(String merchantOrderRef) {
        this.merchantOrderRef = merchantOrderRef;
    }

    public void setSuccessUrl(String successUrl) {
        this.successUrl = successUrl;
    }

    public void setFailureUrl(String failureUrl) {
        this.failureUrl = failureUrl;
    }

    public void setQuantity(Integer quantity) {
        this.quantity = quantity;
    }

    // Getter methods
    public String getClientKey() {
        return clientKey;
    }

    public String getCurrency() {
        return currency;
    }

    public String getMerchantOrderRef() {
        return merchantOrderRef;
    }

    public String getSuccessUrl() {
        return successUrl;
    }

    public String getFailureUrl() {
        return failureUrl;
    }

    public Integer getQuantity() {
        return quantity;
    }
}
#!/usr/bin/python
# -*- coding: utf-8 -*-
import urllib.parse
import hashlib
import hmac
import base64

class RequestObj:   
    def __init__(self, ClientKey, Currency, FailureUrl, MerchantOrderRef, Quantity, SuccessUrl):
        # Instance Variables    
        self.ClientKey = ClientKey
        self.Currency = Currency    
        self.FailureUrl = FailureUrl 
        self.MerchantOrderRef = MerchantOrderRef
        self.Quantity = Quantity
        self.SuccessUrl = SuccessUrl

def GenerateSignature(requestObj, secretKey):
    f = {
        'client_key': requestObj.ClientKey,
        'currency': requestObj.Currency,
        'failure_url': requestObj.FailureUrl,
        'merchant_order_ref': requestObj.MerchantOrderRef,
        'success_url': requestObj.SuccessUrl,
        'quantity': requestObj.Quantity
    }
    
    f = dict(sorted(f.items()))
    message1 = urllib.parse.urlencode(f)
    message = message1.encode('utf-8')
    # print(f'message: {message}\n')
    secret = secretKey.encode('utf-8')
    signature = base64.b64encode(hmac.new(secret, message, digestmod=hashlib.sha256).digest()).decode('utf-8')
    return signature

# Define constants
key = 'PORTONE_KEY'
secret = 'PORTONE_SECRET'
# The unique merchant order reference generated by the merchant
merchantOrderRef = ''
# Create an instance of RequestObj
requestObj = RequestObj(
    ClientKey=key,
    Currency='USD',
    FailureUrl='https://subscription.portone.cloud/failure.html',
    MerchantOrderRef=merchantOrderRef,
    SuccessUrl='https://subscription.portone.cloud/success.html',
    Quantity=1
)

# Call GenerateSignature
signature = GenerateSignature(requestObj, secret)
print(f'Signature: {signature}\n')

Parameter list to generate signature

client_key
string

The PortOne key of the merchant


currency
string

The currency of the subscription


failure_url
string

The URL to redirect to after a failed subscription


merchant_order_ref
string

Order Reference sent by merchant to create the subscription


quantity
string

The quantity of the item being subscribed


success_url
string

The URL to redirect to after a successful subscription


📘

Note

Refer to Subscription Request for the complete list of subscription request parameters.