Skip to main content

Dynamic Checkout Links

Generate custom checkout links with dynamic product settings that can be posted on your website or sent directly to customers. Dynamic settings allow you to modify product information, pricing, and custom fields on a per-link basis.

Overview Dynamic

Checkout Links enable you to:

  • Customize product details (name, description, pricing) per checkout link
  • Pre-fill customer information to streamline checkout
  • Apply dynamic discounts and promotional pricing
  • Set link expiration dates for time-limited offers
  • Pass custom fields for tracking and integration purposes

Step 1: Access the Link Generator

Navigate to Developer Tools → Purchase Link Generator in your PayPro Global account.

Step 2: Configure Basic Settings

Select your product(s) and configure the following optional parameters:

ParameterDescription
Page TemplateChoose the checkout page design
LanguageSet the checkout language
CurrencyPre-select the currency
First NamePre-fill customer's first name
Last NamePre-fill customer's last name
EmailPre-fill customer's email
CountryPre-select customer's country
CityPre-fill customer's city
AddressPre-fill customer's address
Payment MethodPre-select payment method

Step 3: Generate the Link

  1. Click Generate Link to create your checkout URL
  2. Use Test Mode checkbox to generate a test link before going live
  3. For products with cross-sell/upsell campaigns, choose whether to include them

Dynamic Settings Configuration

Enabling Dynamic Settings

Dynamic settings must be enabled in your product configuration: Product Setup → Step 2: Pricing Configuration → Dynamic Settings

Available Dynamic Parameters

When dynamic settings are enabled, you can customize:

1. Product Information

  • Product name
  • Product description
  • Discount percentage

2. Dynamic Pricing

  • Custom price per currency
  • SKU for price tracking
  • Multiple currency support

3. Dynamic Recurring Pricing (Subscriptions only)

  • Custom recurring price per currency
  • Recurring SKU (Available only when "First charge amount is different than recurring charges" is enabled)

4. Custom Fields

  • Pass custom tracking data via x-CustomFieldName=Value format
  • Retrieve data in webhooks via ORDER_CUSTOM_FIELDS

5. Link Expiration
Set checkout link expiration date and time (UTC) Format: yyyy-mm-ddThh:mm:ss

Encryption Implementation

When to Use Encryption

Dynamic settings can be configured as encrypted or non-encrypted in your product setup.

Encryption is recommended for:

  • Sensitive pricing information
  • Preventing link tampering
  • Secure promotional campaigns

Encryption Requirements

Key Length: Exactly 32 characters (256 bits)
IV Length: Exactly 16 characters (128 bits)
Algorithm: AES-256 with CBC mode and PKCS7 padding
Encoding: URL-safe Base64 (replace + with - and / with _ )

Dynamic Settings String Format
Parameter Syntax:

SettingFormatExample
Product IDID=XXXXXID=12345
Product NameName=ValueName=Premium Plan
DescriptionDescription=ValueDescription=Full access to all features
DiscountDiscountPercentage=XX.YYDiscountPercentage=15.00
PricePrice[CURRENCY][Amount]=XX.YYPrice[USD][Amount]=99.99
Price SKUPrice[CURRENCY][Sku]=ValuePrice[USD][Sku]=PLAN-USD
Recurring PriceRecurringPrice[CURRENCY][Amount]=XX.YYRecurringPrice[USD][Amount]=49.99
Recurring SKURecurringPrice[CURRENCY][Sku]=ValueRecurringPrice[USD][Sku]=REC-USD
Custom Fieldx-FieldName=Valuex-CampaignId=SUMMER2026

note

The first currency in the query string will be used as the product's base currency.

URL Parameter Reference

Non-Encrypted Dynamic Settings

When encryption is disabled, data is passed via individual URL parameters:

https://store.payproglobal.com/checkout?

&products[i][ID]=ID
&products[i][name]=NAME 
&products[i][description]=DESCRIPTION 
&products[i][discountpercentage]=PERCENTAGE 
&products[i][LinkExpiration]=yyyy-mm-ddThh:mm:ss
&products[i][price][CURRENCY_CODE][amount]=AMOUNT 
&products[i][recurringprice][CURRENCY_CODE][amount]=AMOUNT 
&products[i][price][CURRENCY_CODE][sku]=SKU 
&products[i][recurringprice][CURRENCY_CODE][sku]=SKU

Hash Protection

If hash protection is enabled in your product settings, an additional parameter will be added:

&products[i][hash]=HASH_VALUE 

Encrypted Dynamic Settings

When encryption is enabled, all dynamic data is passed via a single parameter:

products[N][data]=EncryptedData

Where N is the product index in the order (starting from 1).

Example:

https://store.payproglobal.com/checkout? &products[1][ID]=12345&products[1][data]=ENCRYPTED_STRING_HERE

Code Examples

C# Implementation

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

public class AESEncryption
{
private byte[] Key;
private byte[] IV;

public AESEncryption(string key, string iv)
{
Key = Encoding.ASCII.GetBytes(key);
IV = Encoding.ASCII.GetBytes(iv);

if (Key.Length != 32)
throw new Exception("The Key must be exactly 32 characters (256 bits)!");
if (IV.Length != 16)
throw new Exception("The IV must be exactly 16 characters (128 bits)!");
}

public string Encrypt(string plainText)
{
return ToUrlSafeBase64(EncryptBytes(plainText));
}

private byte[] EncryptBytes(string plainText)
{
using (RijndaelManaged aes = new RijndaelManaged())
{
aes.Padding = PaddingMode.PKCS7;
aes.Mode = CipherMode.CBC;
aes.KeySize = 256;
aes.Key = Key;
aes.IV = IV;

ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);

using (MemoryStream msEncrypt = new MemoryStream())
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(plainText);
swEncrypt.Close();
return msEncrypt.ToArray();
}
}
}

public string Decrypt(string cipherText)
{
return DecryptBytes(FromUrlSafeBase64(cipherText));
}

private string DecryptBytes(byte[] cipherText)
{
using (RijndaelManaged aes = new RijndaelManaged())
{
aes.Padding = PaddingMode.PKCS7;
aes.Mode = CipherMode.CBC;
aes.KeySize = 256;
aes.Key = Key;
aes.IV = IV;

ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);

using (MemoryStream msDecrypt = new MemoryStream(cipherText))
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
return srDecrypt.ReadToEnd();
}
}
}

private string ToUrlSafeBase64(byte[] input)
{
return Convert.ToBase64String(input).Replace("+", "-").Replace("/", "_");
}

private byte[] FromUrlSafeBase64(string input)
{
return Convert.FromBase64String(input.Replace("-", "+").Replace("_", "/"));
}
}

// Usage Example
class Program
{
static void Main(string[] args)
{
// Your encryption key (32 characters)
string key = "abcdefghijklmnopabcdefghijklmnop";
// Your initialization vector (16 characters)
string iv = "abcdefghijklmnop";

// Your product ID from PayPro Global
int productId = 11111;

// Format your dynamic settings string
string dynamicSettings = "x-CustomerID=12345&Name=Premium Plan&" +
"Description=Annual Subscription&" +
"DiscountPercentage=10&Price[USD][Amount]=99.99&" +
"Price[USD][Sku]=PLAN-USD";

var aes = new AESEncryption(key, iv);
string encrypted = aes.Encrypt(dynamicSettings);

Console.WriteLine("Encrypted data: " + encrypted);
Console.WriteLine("Decrypted data: " + aes.Decrypt(encrypted));

// Build checkout URL with product ID and encrypted data
// Note: Product index always starts at [1], not [0]
string checkoutUrl = $"https://store.payproglobal.com/checkout?products[1][id]=
{productId}&products[1][data]={encrypted}";

Console.WriteLine("\nCheckout URL: " + checkoutUrl);
}
}

PHP Implementation

<?php
class AESEncryption {
private $key;
private $iv;

public function __construct($key, $iv) {
if (strlen($key) !== 32) {
throw new Exception("Key must be exactly 32 characters!");
}
if (strlen($iv) !== 16) {
throw new Exception("IV must be exactly 16 characters!");
}

$this->key = $key;
$this->iv = $iv;
}

public function encrypt($plainText) {
$encrypted = openssl_encrypt(
$plainText,
'AES-256-CBC',
$this->key,
OPENSSL_RAW_DATA,
$this->iv
);

return $this->toUrlSafeBase64($encrypted);
}

public function decrypt($cipherText) {
$decoded = $this->fromUrlSafeBase64($cipherText);

return openssl_decrypt(
$decoded,
'AES-256-CBC',
$this->key,
OPENSSL_RAW_DATA,
$this->iv
);
}

private function toUrlSafeBase64($data) {
return str_replace(['+', '/'], ['-', '_'], base64_encode($data));
}

private function fromUrlSafeBase64($data) {
return base64_decode(str_replace(['-', '_'], ['+', '/'], $data));
}
}

// Usage Example
$key = "abcdefghijklmnopabcdefghijklmnop"; // 32 characters
$iv = "abcdefghijklmnop"; // 16 characters
$dynamicSettings = "x-CustomerID=12345&Name=Premium Plan&Description=Annual Subscription&" .
"DiscountPercentage=10&Price[USD][Amount]=99.99&" .
"Price[USD][Sku]=PLAN-USD";
$aes = new AESEncryption($key, $iv);
$encrypted = $aes->encrypt($dynamicSettings);
$productId = 11111;
echo "Encrypted: " . $encrypted . "\n";
echo "Decrypted: " . $aes->decrypt($encrypted) . "\n";
// Build checkout URL
$checkoutUrl = "https://store.payproglobal.com/checkout?products[1][id]=".$productId."&products[1][data]=" . urlencode($encrypted);
echo "Checkout URL: " . $checkoutUrl;
?>

Python Implementation

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import base64
class AESEncryption:
def __init__(self, key, iv):
if len(key) != 32:
raise ValueError("Key must be exactly 32 characters!")
if len(iv) != 16:
raise ValueError("IV must be exactly 16 characters!")

self.key = key.encode('utf-8')
self.iv = iv.encode('utf-8')

def encrypt(self, plain_text):
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
padded_data = pad(plain_text.encode('utf-8'), AES.block_size)
encrypted = cipher.encrypt(padded_data)
return self._to_url_safe_base64(encrypted)

def decrypt(self, cipher_text):
decoded = self._from_url_safe_base64(cipher_text)
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
decrypted = unpad(cipher.decrypt(decoded), AES.block_size)
return decrypted.decode('utf-8')

def _to_url_safe_base64(self, data):
return base64.b64encode(data).decode('utf-8').replace('+', '-').replace('/', '_')

def _from_url_safe_base64(self, data):
return base64.b64decode(data.replace('-', '+').replace('_', '/'))

# Usage Example
key = "abcdefghijklmnopabcdefghijklmnop" # 32 characters
iv = "abcdefghijklmnop" # 16 characters
productId = 11111
dynamic_settings = "x-CustomerID=12345&Name=Premium Plan&Description=Annual Subscription&DiscountPercentage=10&Price[USD][Amount]=99.99&Price[USD][Sku]=PLAN-USD";
aes = AESEncryption(key, iv)
encrypted = aes.encrypt(dynamic_settings)
checkoutUrl = f"https://store.payproglobal.com/checkout?products[1][id]={productId}&products[1][data]={encrypted}"
print(f"Encrypted: {encrypted}")
print(f"Decrypted: {aes.decrypt(encrypted)}")
print(f"Checkout URL: {checkoutUrl}")

Security Best Practices

Key Management

DO:

  • Generate unique, cryptographically secure keys for production
  • Store keys securely (environment variables, secrets management)
  • Use different keys for test and production environments
  • Rotate keys periodically

DON'T:

  • Hard-code keys in your source code
  • Share keys via email or unsecured channels
  • Use the example keys provided in documentation
  • Reuse keys across different environments

URL Safety

  • Always use URL-safe Base64 encoding (replace + with - and / with _)
  • Validate expiration timestamps before generating links
  • Use hash protection when available for additional security
  • Consider implementing rate limiting on checkout URL generation

Testing Your Implementation

Verification Steps

  1. Generate a test checkout URL with encrypted dynamic settings
  2. Open the URL in a browser
  3. Verify the checkout page displays your custom values:
    • Product name matches your encrypted data
    • Price reflects your dynamic pricing
    • Custom fields are present (check via webhooks after test purchase)

Debug Mode

Enable Test Mode in the Purchase Link Generator to:

  • Test without processing real payments
  • Verify encryption/decryption works correctly
  • Check custom field transmission via test webhooks

Common Issues

IssueSolution
"Invalid data parameter"Verify key/IV lengths (32/16 chars) and URL-safe Base64 encoding
Original price shows instead of dynamicCheck encryption format and parameter names
Link expired immediatelyEnsure LinkExpiration uses UTC timezone format: yyyy-mm-ddThh:mm:ss
Custom fields not receivedVerify x- prefix and check webhook payload
Encrypted data appears corruptedUse URL-safe Base64 (replace + with - and / with _)

Additional Resources

Support

Need help implementing dynamic checkout links? Email: [email protected]