Skip to main content

Customer acquisition for digital goods and services companies

Long gone are the days in which the seller had the upper hand in the relationship with the buyer. The shift in power in the digital world calls for new digital acquisition strategies and solutions and strains the importance of "long-term" relationships.

Customer acquisition is at the foundation of any digital strategy. Treating it with disregard can result in revenue leaks at every interaction point along the prospect-to-customer journey.

If you add to this a poorly integrated combination of home-grown and legacy solutions, the lack of visibility and control will leave you far behind your competitors in today's digital commerce.

Download this eBook on customer acquisition. You will get in-depth insights on:

  • The importance of new distribution channels such as resellers and affiliates for revenue growth and survival
  • Payment solutions that will pave the roads to new global markets
  • Advanced APIs for developing new touchpoints of customer engagement

Find out why customer acquisition is one critical part of the digital commerce lifecycle.

acquisition-ebook.png

InLine Checkout with signature generation

Overview

Documentation for generating a JSON Web Token (JWT) used to authenticate yourself when using the 2Checkout Signature Generation API endpoint can be found here.

Generate a JWT for authenticating

It is strongly recommended you generate this using your own backend server and using a library to work with JSON Web Tokens (JWT).

In our examples below, you can see how we use the lcobucci/jwt PHP library in order to generate the JWT. You can use any library you want, but we strongly recommend using a library and not generating the JWT on your own.

JWT generation example

You can see a simple example of generating the token here:

<?php

namespace App\Helpers;

use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Signer\Hmac\Sha512;
use Lcobucci\JWT\Signer\Key;

class JwtToken
{
    static function getToken(){
        $time = time();
        $token = (new Builder())
            ->issuedAt($time) // Configures the time that the token was issue (iat claim)
            ->expiresAt($time + 3600) // Configures the expiration time of the token (exp claim)
            ->withClaim('sub', config('demo.vendor_code')) // Configures a new claim, called "sub" ( default subject of the JWT )
            ->getToken(new Sha512(), new Key(config('demo.vendor_secret'))); // Retrieves the generated token

        return (string)$token;
    }

}

This method returns a token, which in this case expires 60 minutes (3600 seconds) after it is generated. In most cases, this should allow you enough time, but you can modify this threshold as per your needs.

Using the library, call the new Lcobucci\JWT\Builder() and set the parameters specified in the How-to-generate-a-JWT documentation.

In order to use the JWT to authenticate in the 2Checkout Signature Generation API Endpoint, the algorithm used must be HS512, so you need to call the method getToken with the parameters new Sha512() for the algorithm and new Key('VENDOR_SECRET') for the JWT secret signature. 

For  new Key('VENDOR_SECRET') you need to use your Buy-link  Secret Word, which can be found in your Merchant Control Panel under Integrations → Webhooks & API  → Secret Word section.

Use cases

All the scenarios below use the /encrypt/generate/signature Convert Plus endpoint to generate a valid signature. This endpoint is further documented here and the format of the accepted parameters here

Static cart with a signature generated before the page is rendered

This example showcases a simple HTML page that loads a pre-defined cart and its products. The signature is retrieved before the page is rendered and returned as a response to the client.

example 1_Static cart with signature generated before the page is rendered.png

public function example1(){
    $jwtToken = JwtToken::getToken();
    $signature = CartSignature::getSignatureStaticCart($jwtToken);

    return view('example-1')->with('signature', $signature);
}

The signature is generated as described here.

The body of the function getSignatureStaticCart looks like this:

public static function getSignatureStaticCart(string $jwtToken)
{
    $curl = curl_init();

    $products          = config('examples.1.products');
    $payload           = new \stdClass;
    $payload->merchant = config('demo.vendor_code');
    $payload->lock     = 1;
    $payload->products = $products;
    $payload->currency = 'USD';

    curl_setopt_array($curl, [
        CURLOPT_URL            => https://secure.2checkout.com/checkout/api/encrypt/generate/signature,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CUSTOMREQUEST  => 'POST',
        CURLOPT_POSTFIELDS     => json_encode($payload),
        CURLOPT_HTTPHEADER     => [
            'content-type: application/json',
            'merchant-token: ' . $jwtToken,
        ],
    ]);
    $response = curl_exec($curl);
    $err      = curl_error($curl);
    curl_close($curl);
    if ($err) {
        throw new \Error('Curl error: ' . $err);
    }

    $signature = self::parse($response);

    return $signature;
}

 The relevant contents of the HTML page generated will be:

function buy() {
    TwoCoInlineCart.cart.setCurrency('USD');
    TwoCoInlineCart.products.removeAll();
    TwoCoInlineCart.products.addMany(
        {!! json_encode(config('examples.1.products')) !!}
    );
    TwoCoInlineCart.cart.setCartLockedFlag(true);
    TwoCoInlineCart.cart.setSignature('{{$signature}}');
    TwoCoInlineCart.cart.checkout();
}

The call to the TwoCoInlineCart checkout happens after setting the cart with exactly the same parameters used for the signature AND setting the signature (TwoCoInlineCart.cart.setSignature) to the one previously generated. 

Dynamic cart with products selected by Customer - signature generated only once

This example showcases how you can have a dynamic cart, so the customer can select products and quantities. Because the products are added/changed by the customer inside the page, the signature must be generated when the shopping cart loads, using the products the customer selected; otherwise, the signature would be invalid and the cart would not load.

Clicking the buy-link would not immediately open the cart. Firstly, you must send the current cart payload to your backend and get the signature for the cart.

async function getSignature(){
    const response = await fetch('/api/generate-signature', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        redirect: 'follow',
        referrerPolicy: 'no-referrer',
        body: JSON.stringify({
            cart: {
                products: cart.products
            }
        })
    });
    return response.json();
}

The backend receives the products and makes the call to get the signature for this payload, then returns the signature.

public function generateSignature(Request $request){
    $requestProducts = $request->input('cart.products');

    $jwtToken = JwtToken::getToken();
    $signature = CartSignature::getSignatureForProducts($jwtToken, $requestProducts);

    return json_encode($signature);
} 

Then, you can set the signature returned by your backend and initialize the cart. This is the JavaScript snippet that does this.

 async function buy() {
    console.log('getting signature...');
    let signature = await getSignature();
    console.log('signature retrieved, ', signature);

    TwoCoInlineCart.cart.setCurrency('USD');
    TwoCoInlineCart.products.removeAll();
    TwoCoInlineCart.products.addMany(
        cart.products
    );
    TwoCoInlineCart.cart.setCartLockedFlag(true);
    TwoCoInlineCart.cart.setSignature(signature);
    TwoCoInlineCart.cart.checkout();
}

Dynamic cart with products selected by customer - signature generated when the cart is updated 

example 3_Dynamic cart with products selected by client - signature generated when cart is updated.png

For the second use case above, the customer will have to wait for the generateSignature call when the buy button is pressed.

An alternative would be to generate and set a new signature every time the cart is updated. This way, more calls will be made to both your backend and the 2Checkout Signature Generation Endpoint, but the cart will load faster.

async function updateCart() {
    computeCart();
    await setSignature();
    updateView();
}

While computeCart will update the payload, the setSignature call will make an Ajax to your backend, the same as in the second use case, and call TwoCoInlineCart.cart.setSignature(signature);.

async function setSignature() {
    console.log('getting signature...');
    let signature = await getSignature();
    console.log('signature retrieved, ', signature);
    TwoCoInlineCart.cart.setSignature(signature);
}

When clicking the Buy button, the cart will boot faster, as the signature call will already be called when the last cart modification is done.

You can choose between use cases 2 & 3, depending on your application's needs and objectives.

Cart with custom prices Products

If you have products with custom prices, you should always calculate the price of the product in your own backend. If you have frontend logic that generates and computes calculations in order to display the total, those prices should be used for only that, frontend display.

You must recompute the price on your backend and generate a signature with a payload that only you can validate: do not allow an AJAX call to your backend to calculate the total price.

public function signatureCustomPrices(Request $request){
    $requestProducts =  $request->input('cart.products');

    // set custom prices
    // !! do not trust prices coming on the request
    // use the custom prices set in our system ( in this case they can be found in the @var $productsData )
    $productsWithCustomPrices = [];
    foreach($requestProducts as $key => $requestProduct){
        $requestProductCustomPrice                        = $this->getCustomPriceByProductCode($requestProduct['code']);
        $productsWithCustomPrices[$key]['code']           = $requestProduct['code'];
        $productsWithCustomPrices[$key]['quantity']       = $requestProduct['quantity'];
        $productsWithCustomPrices[$key]['custom-price']   = $this->formatCustomPrice($requestProductCustomPrice, $requestProduct['quantity']);
    }

    // create the payload with the product codes and quantities set by shopper, BUT the prices from the backend
    $cartPayload           = new \stdClass;
    $cartPayload->merchant = config('demo.vendor_code');
    $cartPayload->products = $productsWithCustomPrices;
    $cartPayload->currency = 'USD';

    // get JWT using token
    $jwtToken = JwtToken::getToken();

    // generate signature
    $signature = CartSignature::getSignatureForTheEntirePayload($jwtToken, $cartPayload);

    return json_encode($signature);
}

 Depending on your own case, you can use the second or third scenarios to set the signature.

Cart with dynamic products

In order to generate a signature for dynamic products, the payload you send to the 2Checkout API Generation Endpoint should have the same parameters as the ones you use to set up the cart in your JavaScript code.

$cartPayload           = new \stdClass;
$cartPayload->merchant = config('demo.vendor_code');
$cartPayload->currency = 'USD';
$cartPayload->dynamic  = '1';
$cartPayload->products = config('examples.5.products');

$jwtToken = JwtToken::getToken();
$signature = CartSignature::getSignatureForTheEntirePayload($jwtToken, $cartPayload);

function buy() {
    TwoCoInlineCart.setup.setMode('DYNAMIC');
    TwoCoInlineCart.cart.setCurrency('USD');
    TwoCoInlineCart.products.removeAll();
    TwoCoInlineCart.products.addMany(
        {!! json_encode(config('examples.5.products')) !!}
    );

    TwoCoInlineCart.cart.setSignature('{{$signature}}');
    TwoCoInlineCart.cart.checkout();
}

Notice that you must set the payload currency and dynamic, the same way as you do in the JavaScript code.

After you compute this payload, you can just call the signature generation API endpoint.

public static function getSignatureForTheEntirePayload(string $jwtToken, \stdClass $cartPayload)
{
    $curl = curl_init();
    curl_setopt_array($curl, [
        CURLOPT_URL            => config('demo.signature_api_url'),
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CUSTOMREQUEST  => 'POST',
        CURLOPT_POSTFIELDS     => json_encode($cartPayload),
        CURLOPT_HTTPHEADER     => [
            'content-type: application/json',
            'merchant-token: ' . $jwtToken,
        ],
    ]);
    $response = curl_exec($curl);
    $err      = curl_error($curl);
    curl_close($curl);
    if ($err) {
        throw new \Error('Curl error: ' . $err);
    }

    $signature = self::parse($response);

    return $signature;
}

 

Prefill customer data in order forms

Overview

Use 2Checkout's order form prefill feature to pre-populate forms in the ordering interface with customer data already stored in your shopping cart platform. 

Scenarios

  1. You're using the secure.2checkout.com domain - Send the details to be auto-filled via a form using either GET or POST to https://secure.2checkout.com/order/pf.php.
  2. You're using a custom domain such as store.YourDomain.com - Use the custom domain when sending the details with either GET or POST to https://store.YourDomain.com/order/pf.php.

Workflow

2Checkout captures the sent parameters and redirects the customer to the link set by the "URL" parameter.

Parameters

Required

URL

The GET request created either in the Generate Sales Links area or dynamically. 

Use URL-encoding (RFC 1738) for the value of the URL parameter when working with custom-built links.

MERCHANT

Your 2Checkout Merchant Code (view)

AUTOMODE

(optional) Send this parameter with value = 1 to skip to the credit card details page, provided all billing information is sent as described below. If any of the fields below are incomplete, the regular form will be shown in order for the customer to fill in the missing fields.

Optional: billing information

BILL_FNAME

Client first name

BILL_LNAME

Client last name

BILL_COMPANY

Company name for billing

BILL_FISCALCODE

Company Unique registration code(VAT ID)

BILL_EMAIL

E-mail address

BILL_PHONE

Phone number

BILL_FAX

Fax number

BILL_ADDRESS

Customer/Company physical address

BILL_ADDRESS2

Customer/Company address (second line)

BILL_ZIPCODE

Customer/Company zip code

BILL_CITY

City

BILL_STATE

State/County

BILL_COUNTRYCODE

Country code (two letter code)

Optional: delivery information

DELIVERY_FNAME

Client first name

DELIVERY_LNAME

Client last name

DELIVERY_COMPANY

Company name for delivery

DELIVERY_PHONE

Phone number

DELIVERY_ADDRESS

Client/company address (for delivery)

DELIVERY_ADDRESS2

Client/company address (second line)

DELIVERY_ZIPCODE

Client/company zip code

DELIVERY_CITY

City

DELIVERY_STATE

State/County

DELIVERY_COUNTRYCODE

Country code (NL for Netherlands)

URL encoded string

Not encoded Encoded
https://secure.2checkout.com/order/cart.php?PRODS=123456&QTY=1
https%3A%2F%2Fsecure.2checkout.com%2Forder%2Fcart.php%3FPRODS%3D123456%26QTY%3D1

Example: Submitting customer email in the buy-link

https://secure.2checkout.com/order/pf.php?MERCHANT=YourCode&BILL_EMAIL=john.doe@example.com&URL=https%3A%2F%2Fsecure.2checkout.com%2Forder%2Fcheckout.php%3FPRODS%3D123456%26QTY%3D1

Encrypt prefilled data

Send the data using either GET or POST.

Cryptographic Standard

Advanced Encryption Standard (AES), used with 256-bit keys, in GCM (Galois/Counter Mode), with a random and unique 'Initialization vector (IV)' 96-bit. 

Encryption Key

Derive the encryption key from the secret key of your account from Integrations > Webhooks & API page, using a SHA256 HMAC and a cryptographically strong random nonce.

A cryptographic nonce is a random number that is used in communication protocols to assist maintain the privacy of communications. It is a single-use, generated using a cryptographically-strong, high-entropy, random-number generator.

Hash

Generate a keyed hash value using the HMAC method and the SHA2/ SHA3 hashing algorithm of the unencrypted URL (Buy Link) and your account's secret key.

GET request

Build your URL using this format:

https://secure.2checkout.com/order/pf.php?V=2&MERCHANT=<Your_Merchant_CODE>&DATA=<Nonce>.<Tag>.<IV>.<EncryptedData>&URL=<CHECKOUT_URL>

All encrypted parameters are first Base64-encoded and then URL-encoded.

Encrypted Prefill Example PHP

<?php
function generateRandomString($length = 10) {
    $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $charactersLength = strlen($characters);
    $randomString = '';
    for ($i = 0; $i < $length; $i++) {
        $randomString .= $characters[rand(0, $charactersLength - 1)];
    }
    return $randomString;
}

echo '============================== BUY LINKS ==============================';

$baseUrl = "https://secure.2checkout.com";
$url = $baseUrl . '/order/checkout.php?CART=1&CARD=1';

$prods = '1234567';
$secretKey = '123&^^%$hjBJ';
$merchantCode = 'YourCode';


$urlString = 'PRODS=' . $prods . '&QTY=1&PRICES' . $prods . '[USD]=5&PLNKID='.strtoupper(generateRandomString());

$buyLink = "$baseUrl/order/checkout.php?PRODS=$prods&QTY=1&CART=1&CARD=2&SHORT_FORM=1";

$dataArray = [
    'BILL_FNAME' => 'John',
    'BILL_LNAME' => 'Doe',
    'BILL_EMAIL' => 'test@test.com',
    'BILL_COUNTRYCODE' => 'RO'
];
$data = http_build_query($dataArray) . '&URL=' . urlencode($buyLink);

dp('################################################################################################################');
dp('###################################################  V2  #######################################################');
dp('################################################################################################################');

$cypherMethod = 'aes-256-gcm';
$ivLength = openssl_cipher_iv_length($cypherMethod);
$keyLength = 256/8;
$tagLength = 16;

$initialKeyingMaterial = $secretKey;
$keyDerivationNonceOrSalt = openssl_random_pseudo_bytes($keyLength);
$iv = openssl_random_pseudo_bytes($ivLength);

dp("NONCE (" . (strlen($keyDerivationNonceOrSalt)*8) . " bits): " . bin2hex($keyDerivationNonceOrSalt));
dp("IKM, IPN KEY (" . (strlen($initialKeyingMaterial)*8) . " bits, text): " . "'$initialKeyingMaterial'");
dp("IKM, IPN KEY (" . (strlen($initialKeyingMaterial)*8) . " bits, HEX): " . bin2hex($initialKeyingMaterial));
dp(" ");
dp("REQUIRED IV LENGTH: " . ($ivLength * 8) . ' bits');
dp("IV (" . (strlen($iv)*8) . " bits): " . bin2hex($iv));
dp(" ");

$encryptionKey = hash_hmac('sha256', $secretKey, $keyDerivationNonceOrSalt, true);
dp("DERIVED ENCRYPTION KEY (" . (strlen($encryptionKey)*8) . " bits): " . bin2hex($encryptionKey));

$encryptedData = openssl_encrypt($data, $cypherMethod, $encryptionKey, OPENSSL_RAW_DATA, $iv, $tag, $keyDerivationNonceOrSalt, $tagLength);
$decryptedString = openssl_decrypt($encryptedData, $cypherMethod, $encryptionKey, OPENSSL_RAW_DATA, $iv, $tag, $keyDerivationNonceOrSalt);

dp(" ");
dp("INPUT DATA (PLAIN): " . $data);

dp("SPECIFIED AUTHENTICATION TAG LENGTH: " . $tagLength . ' bytes');
dp("RESULTING AUTHENTICATION TAG: " . bin2hex($tag));
dp(" ");
dp("openssl_decrypt('" . bin2hex($encryptedData) . "', '" . bin2hex($cypherMethod) . "', '" . bin2hex($encryptionKey) . "', OPENSSL_RAW_DATA, '" . bin2hex($iv) . "', '" . bin2hex($tag) . "', '" . bin2hex($keyDerivationNonceOrSalt) . "')");
dp(" ");
dp("ENCRYPTED DATA:   " . bin2hex($encryptedData));
dp("INPUT DATA (HEX): " . bin2hex($data));
dp("DECRYPTED DATA:   " . bin2hex($decryptedString));

$base64Payload = base64_encode($keyDerivationNonceOrSalt)
    . '.' . base64_encode($tag)
    . '.' . base64_encode($iv)
    . '.' . base64_encode($encryptedData);

dp(" ");
dp("DATA PAYLOAD STRUCTURE: " . 'base64_encode(NONCE).base64_encode(TAG).base64_encode(IV).base64_encode(ENCRYPTED DATA)');
dp("DATA PAYLOAD (BASE64): " . $base64Payload);

$checkoutUrl = 'checkout.php?PRODS=' . $prods . '&QTY=1';
$url = "$baseUrl/order/pf.php?V=2&MERCHANT={$merchantCode}&DATA=" . urlencode($base64Payload) . '&URL=' . urlencode($checkoutUrl);
dp($url);

function dp($s) {
    if (empty($_SERVER['argv'])) {
        echo "<pre>" . htmlentities($s) . "\n\n</pre>\n";
    } else {
        echo "$s\n\n";
    }
}

Encrypted Prefill Example C#

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

namespace Encrypted_Prefill_Example
{
    internal class Example
    {
        public static void Main(string[] arg)
        {
            Console.WriteLine("============================== BUY LINKS ==============================");

            string baseUrl = "https://secure.2checkout.com";
            string prods = "12345678";
            string secretKey = "YOUR_SECRET_KEY";
            string merchantCode = "YOUR_MERCHANT_CODE";

            string url = baseUrl + "/order/checkout.php?CART=1&CARD=1";
            string urlString = "PRODS=" + Uri.EscapeDataString(prods) + "&QTY=1&PRICES" + Uri.EscapeDataString(prods) + "[USD]=5&PLNKID=" + GenerateRandomString().ToUpper();
            string buyLink = baseUrl + "/order/checkout.php?PRODS=" + Uri.EscapeDataString(prods) + "&QTY=1&CART=1&CARD=2&SHORT_FORM=1";

            string data = "BILL_FNAME=John&BILL_LNAME=Doe&BILL_EMAIL=test@test.com&BILL_COUNTRYCODE=RO&URL=" + Uri.EscapeDataString(buyLink);

            Console.WriteLine("################################################################################################################");
            Console.WriteLine("###################################################  V2  #######################################################");
            Console.WriteLine("################################################################################################################");

            string cypherMethod = "AES256GCM"; // Adjusted for .NET's naming
            int ivLength = 12; // GCM standard IV length
            int keyLength = 32; // 256 bits
            int tagLength = 16;

            byte[] initialKeyingMaterial = Encoding.UTF8.GetBytes(secretKey);
            byte[] keyDerivationNonceOrSalt = GenerateRandomBytes(keyLength);
            byte[] iv = GenerateRandomBytes(ivLength);

            PrintDebugInfo("NONCE", keyDerivationNonceOrSalt);
            PrintDebugInfo("IKM, IPN KEY", initialKeyingMaterial);
            PrintDebugInfo("IV", iv);

            byte[] encryptionKey = new HMACSHA256(keyDerivationNonceOrSalt).ComputeHash(initialKeyingMaterial);
            PrintDebugInfo("DERIVED ENCRYPTION KEY", encryptionKey);

            byte[] encryptedData, decryptedData, tag;
            encryptedData = Encrypt(Encoding.UTF8.GetBytes(data), encryptionKey, iv, out tag, keyDerivationNonceOrSalt);
            decryptedData = Decrypt(encryptedData, encryptionKey, iv, tag, keyDerivationNonceOrSalt);

            PrintDebugInfo("RESULTING AUTHENTICATION TAG", tag);

            PrintDebugInfo("INPUT DATA", Encoding.UTF8.GetBytes(data));
            PrintDebugInfo("ENCRYPTED DATA", encryptedData);
            PrintDebugInfo("DECRYPTED DATA", decryptedData);

            string base64Payload = Convert.ToBase64String(keyDerivationNonceOrSalt)
                + '.' + Convert.ToBase64String(tag)
                + '.' + Convert.ToBase64String(iv)
                + '.' + Convert.ToBase64String(encryptedData);

            Console.WriteLine();
            Console.WriteLine("DATA PAYLOAD STRUCTURE (BASE64-encoded components): NONCE.TAG.IV.ENCRYPTED_DATA");
            Console.WriteLine("DATA PAYLOAD (BASE64): " + base64Payload);

            string checkoutUrl = "checkout.php?PRODS=" + Uri.EscapeDataString(prods) + "&QTY=1";
            url = baseUrl + "/order/pf.php?V=2&MERCHANT=" + Uri.EscapeDataString(merchantCode) + "&DATA=" + Uri.EscapeDataString(base64Payload) + "&URL=" + Uri.EscapeDataString(checkoutUrl);

            Console.WriteLine();
            Console.WriteLine(url);
        }

        private static void PrintDebugInfo(string prefix, byte[] data)
        {
            Console.WriteLine(prefix + " (" + (data.Length * 8) + " bits): " + BitConverter.ToString(data).Replace("-", ""));
            Console.WriteLine();
        }

        private static string GenerateRandomString(int length = 10)
        {
            const string characters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
            var randomString = new StringBuilder(length);
            Random random = new Random();
            for (int i = 0; i < length; i++)
            {
                randomString.Append(characters[random.Next(characters.Length)]);
            }
            return randomString.ToString();
        }

        private static byte[] GenerateRandomBytes(int length)
        {
            // Create a byte array to hold the random bytes
            byte[] randomBytes = new byte[length];

            // Generate random bytes using RNGCryptoServiceProvider
            using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
            {
                rng.GetBytes(randomBytes);
            }

            return randomBytes;
        }

        private static byte[] Encrypt(byte[] plaintextBytes, byte[] key, byte[] iv, out byte[] tag, byte[] associatedData)
        {
            byte[] ciphertextBytes = new byte[plaintextBytes.Length];
            tag = new byte[16]; // GCM tag length of 128 bits

            using (AesGcm aesGcm = new AesGcm(key))
            {
                aesGcm.Encrypt(iv, plaintextBytes, ciphertextBytes, tag, associatedData);
            }

            return ciphertextBytes;
        }

        private static byte[] Decrypt(byte[] ciphertextBytes, byte[] key, byte[] iv, byte[] tag, byte[] associatedData)
        {
            byte[] plaintextBytes = new byte[ciphertextBytes.Length];
            
            using (AesGcm aesGcm = new AesGcm(key))
            {
                aesGcm.Decrypt(iv, ciphertextBytes, tag, plaintextBytes, associatedData);
            }

            return plaintextBytes;
        }
    }
}

 

Manage test subscriptions

Overview

Import test subscription and data from your system or from a third-party and test advanced renewal and upgrade scenarios in the 2Checkout platform.

Availability

Import of test subscriptions is available for all accounts. Contact 2Checkout for additional details.

Requirements

Test subscriptions can be imported only for eStore orders.

For subscriptions that need to have payment information attached, only dummy credit card data can be assigned. Check here the list of credit cards that can be used for import.

Import test subscriptions via CSV

Import your existing subscriptions in our database, by using a UTF-8 encoded CSV file ("Comma Separated Values"). A sample .csv file is provided here.

Without credit card information

Import subscriptions including product and customer information by using the instructions from this article.

For subscriptions that don't have payment information assigned, do not send any values in the CardNumber and CardExpirationDate columns.

WITH CREDIT CARD INFORMATION

Import subscriptions with credit card information assigned by using the instructions from this article.

Only 2Checkout test credit cards can be used in the import. Check here the list of credit cards that can be used for import.

Cardholder name is composed by using the First name and Last name provided in the import file, as follows: FirstName + " " + LastName.

Import test subscriptions via API

Import your existing subscriptions by using our API capabilities.

Without credit card information

JSON-RPC 4.0 REST 4.0 SOAP 4.0
Import test subscriptions without payment information Import test subscriptions without payment information Import test subscriptions without payment information

WITH CREDIT CARD INFORMATION

JSON-RPC 4.0 REST 4.0 SOAP 4.0
Import test subscriptions with credit card information Import test subscriptions with credit card information Import test subscriptions with credit card information

Test subscriptions without credit cards

Explore manual renewal and upgrade scenarios with your imported test subscriptions.

Limitations

Automatic renewals (both via Web, and via API) are possible only after our internal cron has executed a renewal for your subscription, and an order has been generated for the imported subscription. Alternatively, you can place a manual renewal for the subscription generated instead of waiting for the cron to execute.

Upgrades via API are possible after our internal cron has executed a renewal for your subscription, and an order has been generated for the imported subscription. Alternatively, you can place a manual renewal for the subscription generated instead of waiting for the cron to execute.

Renewal using the hosted cart

Perform manual renewals for imported test subscriptions from your Control Panel. 

  1. Go to Orders & Customers > Subscriptions.
  2. Search for the imported test subscription using a license identifier (email, reference, name).
  3. Open the subscription details page, and click on Renew subscription.
  4. Choose the manual renewal type.
  5. Confirm the billing information, and place the renewal order.

Upgrade using the hosted cart

Place upgrade orders for imported test subscriptions from your Control Panel.

  1. Go to Orders & Customers > Subscriptions.
  2. Search for the imported test subscription using a license identifier (email, reference, name).
  3. Open the subscription details page, and click on Upgrade subscription.
  4. Choose the new payment information type.
  5. Confirm the new payment details and place the upgrade order.

Manual renewal VIA API

Renew a subscription via API by using the 2Checkout Subscription Reference. Collect your recurring revenue with one of the following payment methods:

  • Credit cards
  • PayPal
  • WeChat Pay
  • iDEAL
  • Purchase Order
  • Wire
JSON-RPC 4.0 REST 4.0 SOAP 4.0
Place a manual renewal order Place a manual renewal order Place a manual renewal order

 

Update a cross-sell campaign

Overview

Use the updateCrossSellCampaign method to update a cross-sell campaign for your 2Checkout account. 

Request parameters

Parameters Type Required Description

CampaignCode 

 String 

Required

The campaign code that can be used when placing orders. 

MasterProducts 

Array of strings 

Required

Array of product codes to apply to this campaign. 

DisplayType 

String 

Required

The display type of the campaign; Can be cart, review, finish.

DisplayInEmail 

Boolean 

Required

Determines if the campaign will be displayed in payment receipt emails.

Products 

Array of objects 

Required

Array of objects containing the product codes pointing to the promoted products when tied to each master product, the discount value, and the discount type (can only be percent).

ProductCode 

String 

Required

Product code of the product to be recommended to the shopper.

Discount 

Float 

Required

Value of the discount.

DiscountType 

String 

Required

Can only be 'PERCENT'.

Name 

String 

Required

Name of the campaign.

CampaignStatus

String 

Optional 

The status of the cross-sell campaign.

StartDate 

String 

Optional 

The date when the cross-sell campaign starts, formatted as YYYY-MM-DD.

EndDate 

String 

Optional 

The date when the cross-sell campaign ends, formatted as YYYY-MM-DD.

Response parameters

Parameters Type Required Description

CampaignCode 

 String 

Required

The campaign code that can be used when placing orders. 

MasterProducts 

Array of strings 

Required

Array of product codes to apply to this campaign. 

DisplayType 

String 

Required

The display type of the campaign; Can be cart, review, finish.

DisplayInEmail 

Boolean 

Required

Determines if the campaign will be displayed in payment receipt emails. Can be 'true' or 'false'.

Products 

Array of objects 

Required

Array of objects containing the product codes pointing to the promoted products when tied to each master product, the discount value, and the discount type (can only be percent).

ProductCode 

String 

Required

Product code of the product to be recommended to the shopper.

Discount 

Float 

Required

Value of the discount.

DiscountType 

String 

Required

Can only be 'PERCENT'.

Name 

String 

Required

Name of the campaign.

CampaignStatus

String 

Optional 

The status of the cross-sell campaign.

StartDate 

String 

Optional 

The date when the cross-sell campaign starts, formatted as YYYY-MM-DD.

EndDate 

String 

Optional 

The date when the cross-sell campaign ends, formatted as YYYY-MM-DD.

Request sample

<?php 

require ('PATH_TO_AUTH'); 


$csCampaign = new stdClass(); 
$csCampaign->CampaignCode = '2Xrl85eSkemBv3G3ea+9fg=='; 
$csCampaign->MasterProducts = ['C8851A5BC9']; 
$csCampaign->DisplayType = 'review'; 
$csCampaign->DisplayInEmail = true; 
$csCampaign->Name = 'testingRpc'; 
$csCampaign->StartDate = '2019-10-21'; 
$csCampaign->EndDate = '2021-10-21'; 
$csCampaign->CampaignStatus = 'ACTIVE'; 
$csCampaign->Products = []; 
$csCampaign->Products[0] = new stdClass(); 
$csCampaign->Products[0]->ProductCode = '512712FA53'; 
$csCampaign->Products[0]->Discount = 20; 
$csCampaign->Products[0]->DiscountType = 'PERCENT'; 

 
$jsonRpcRequest = new stdClass(); 
$jsonRpcRequest->jsonrpc = '2.0'; 
$jsonRpcRequest->method = 'updateCrossSellCampaign'; 
$jsonRpcRequest->params = array($sessionID, $csCampaign); 
$jsonRpcRequest->id = $i++; 


$csCampaignResponse = callRPC($jsonRpcRequest, $host, true)

Retrieve an order

Overview

Use the getOrder method to retrieve details on a specific order placed with dynamic product information, or catalog products, using the unique, system-generated reference.

Parameters

Parameters Type/Description

sessionID

Required (string)

 

Session identifier, the output of the Login method. Include sessionID into all your requests. Avangate throws an exception if the values are incorrect.  The sessionID expires in 10 minutes.

orderReference

Required (string)

 

Order reference number of an older order, which is already approved/paid.

Response

Parameters Type/Description

Order information

Object (orders with catalog products)

Order information Object (orders with dynamic product information)

Request 

<?php

require ('PATH_TO_AUTH');

$orderReference = 'YOUR_ORDER_REFERENCE';

try {
    $fullOrderDetails = $client->getOrder    ($sessionID, $orderReference);
}
catch (SoapFault $e) {
    echo "fullOrderDetails: " . $e->getMessage();
    exit;
}
var_dump("fullOrderDetails", $fullOrderDetails);
?>

 

Upgrade subscription

Overview

Use the setSubscriptionUpgrade method to upgrade a subscription.

Requirements

You can only upgrade subscriptions with automatic renewal enabled.

   The upgrade will not work if the subscription has a refund request that was not yet processed.

Parameters

Parameters Type/Description

sessionID

Required (string)

 

Session identifier, the output of the Login method. Include sessionID into all your requests. 2Checkout throws an exception if the values are incorrect.  The sessionID expires in 10 minutes.

subscriptionReference

Required (string)

 

Unique, system-generated subscription identifier.

productCode

Required (string)

 

Unique identifier of the target product for the subscription upgrade process. You control the product code and can set it up in the Control Panel.

options

Optional (string)

 

Pricing options of the target product for the subscription upgrade process.

 

String - ';' separated list of 'OptionValue' returned by getProductUpgradeOptions function.

If the pricing options groups is "scale" (interval), the Options parameter should be sent like this: [option group unique code] + "=" + value

e.g. Users=7

customPrice

Optional (string)

 

The price you want to charge customers for the upgrade. The currency used by default is the same as in the previous payment customers made.

Response

Parameters Type/Description

Boolean

true or false depending on whether or not the operation succeeded.

Request

<?php

require ('PATH_TO_AUTH');

$subscriptionReference = 'BE14FDCF37';
$ProductCode = 'my_subscription_123';
$Options = 'emailsupport;oneuser1';//case sensitive; include only price options codes (exclude spaces)
$customPrice = 1;

try {
    $upgradedSubscriptions = $client->setSubscriptionUpgrade($sessionID, $subscriptionReference, $ProductCode, $Options, $customPrice);
}
catch (SoapFault $e) {
    echo "upgradedSubscriptions: " . $e->getMessage();
    exit;
}
var_dump("upgradedSubscriptions", $upgradedSubscriptions);

Place test orders from the Merchant Control Panel

Overview

You can place five types of test orders:

  1. New acquisition
  2. Trial
  3. Automatic renewal/trial conversion
  4. Manual renewal
  5. Upgrade

To start the testing process, you first need to generate a test buy-link. Using test Buy Links you can replicate the new acquisitions and trial purchase scenarios. Automatic renewalstrial conversionsmanual renewals, and upgrades require that you placed an initial test order and that 2Checkout generated a subscription for the test purchase.

 

Recommended resources

How to generate a test Buy-Link

Create links in the Merchant Control Panel

  1. Go to Setup -> Generate links -> Checkout Links.
  2. Choose a purchase flow from the available list.
  3. Select one or more products.
  4. Optional: you can control a collection of advanced options, but none are required to place test orders.
  5. Click on Generate link.
  6. Click Place a test order. Alternatively, you can copy the generated link and paste it in your browser's address bar.

Manually add the DOTEST parameter to Buy-Links

You can add the DOTEST parameter manually to Buy Links and use the result to place test orders. For example,

Buy link: https://secure.2checkout.com/order/checkout.php?PRODS=4551041&QTY=1
Test buy link: https://secure.2checkout.com/order/checkout.php?PRODS=4551041&QTY=1&DOTEST=1

DOTEST=1

Optional

Use DOTEST=1 to place orders in a test environment. You need to enter the dummy credit card details for the specific scenario that you're testing.

DOTEST in buy-links only works if the you enabled the testing system for your account. Otherwise, it has no impact whatsoever.

New acquisition test orders

  1. Use the guidance above to generate the test buy-link.
  2. Open the test buy-link in your browser.
  3. Optional: You can change products, enter discount coupons, update pricing options, quantity, cross-sell, upsell, etc.
  4. Enter Billing Information. The details can be fictitious, but use a valid email address that you control:
    • To also get notifications from the 2Checkout system.
    • Be able to access myAccount.
  5. Use one of the dummy credit cards in the Test payment methods page to provide payment information details. Note: The result varies depending on the used card number and cardholder name.

Trial test orders

  1. Use the guidance above to generate the test buy-link.
  2. Choose the Trial link purchase flow.
  3. Open the test buy-link in your browser.
  4. Optional: You can change products, enter discount coupons, update pricing options, quantity, cross-sell, upsell, etc.
  5. Enter Billing Information. The details can be fictitious, but use a valid email address that you control:
    • To also get notifications from the 2Checkout system.
    • Be able to access myAccount.
  6. Use one of the dummy credit cards in the Test payment methods page to provide payment information details. Note: The result varies depending on the used card number and cardholder name.

Automatic renewal test orders

Renewal test orders are only available for subscriptions generated for existing test orders involving products with the subscription service enabled and set to an interval other than a one-time fee (non-recurring).

The 2Checkout system automatically renews test subscriptions before they expire. Renewal test orders require you to place an initial new purchase test order so that the 2Checkout system generates a test subscription.

  • The 2Checkout platform supports recurring billing for test orders paid with credit/debit cards. Depending on the renewal interval set for subscriptions, the 2Checkout system will charge customers in accordance with the following two scenarios:
    • The subscription renewal interval is smaller or equal to six (6) months. The 2Checkout system will attempt to charge customers (per their time zone) at a maximum of three (3) hours prior to the expiration of the current subscription.
    • Subscription renewal interval is larger than six (6) months. The first attempt to charge shoppers is made 27 hours prior to the expiration date. A subsequent attempt is made, seven (7) hours before the subscription is set to expire.

Trigger an automatic renewal test order

  1. Go to Subscription management or to Order search and search either for a test subscription or a test order. It's mandatory to use a non-lifetime test subscription.
  2. Click on View subscription to access the details page of the item you wish to trigger the auto-renewal process for.
  3. Click on Renew subscription. Select the Auto option and click on Renew subscriptionNote: The 2Checkout system will only renew subscriptions with a valid card attached. This means that the card number and expiration date (10 / 16) combination must support auto-renewal.
  4. After the 2Checkout system automatically renews the subscription, it will update the history area at the bottom of the page with the details on the renewal test order.

Subscription renewal test orders

Renewal test orders require you to place an initial new purchase test order so that the 2Checkout system generates a test subscription.

  1. Edit the product(s) for which you wish to place test orders.
  2. Make sure the renewal interval for the product under the Renewal tab, in the Subscription Settings area is set to a value other than a one-time fee (non-recurring).
  3. Place a test order for the product and enable auto-renewal in the shopping cart during the purchasing process.

Manual renewal test order

You need to place manual renewal test orders yourself. You or the 2Checkout system can place only test orders for test subscriptions generated. The DOTEST=1 URL parameter is used automatically for the ordering process.

  1. Navigate to Subscription management or to Order search and search either for a test subscription or a test order. It's mandatory to use a non-lifetime test subscription.
  2. Click on View subscription to access the details page of the item you wish to trigger the auto-renewal process for.
  3. Click on Renew subscription. Select the Manual option and click on Renew subscription. Alternatively, click on or copy & paste the Manual Payment test link into your browser's address bar
  4. You will land in the shopping cart where you can place a manual renewal test order. The system will include the DOTEST=1 URL parameter in the Buy Link.
  5. The system will update the Subscription history area with the details on the renewal test order.

Test renewal notifications

  1. Go to Subscription management or to Order search and search either for a test subscription or a test order. It's mandatory to use a non-lifetime test subscription.
  2. Click on View subscription to access the details page of the item you wish to trigger the auto-renewal process for.
  3. Click Send expiration email. Select one of the options available:
    • Auto (with or without card expiration instructions)
    • Manual
  4. 2Checkout sends emails to the email address under End-user details.
  5. If you click on the manual payment link included in the manual renewal email will you also land in the shopping cart and be able to place a manual renewal test order.

How CAPTCHA verification works

To increase the security of the 2Checkout shopping cart and protect you from hackers and fake accounts, a CAPTCHA verification is implemented in the shopping cart in order to correctly differentiate real users from fake accounts.

CAPTCHA identification is based on your login history:

  1. If you have logged in to your Control Panel account in the past 30 days, you will not be shown a CAPTCHA verification when placing test orders.
  2. If you have not logged in to your Control Panel account in the past 30 days, you will be shown a CAPTCHA verification when placing test orders.

Retrieve a product group

Response

ProductGroup

Array of objects

Request

<?php

$host   = "https://api.avangate.com";
$client = new SoapClient($host . "/soap/4.0/?wsdl", array(
    'location' => $host . "/soap/4.0/",
    "stream_context" => stream_context_create(array(
        'ssl' => array(
            'verify_peer' => false,
            'verify_peer_name' => false
        )
    ))
));


function hmac($key, $data)
{
    $b = 64; // byte length for md5
    if (strlen($key) > $b) {
        $key = pack("H*", md5($key));
    }
    
    $key    = str_pad($key, $b, chr(0x00));
    $ipad   = str_pad('', $b, chr(0x36));
    $opad   = str_pad('', $b, chr(0x5c));
    $k_ipad = $key ^ $ipad;
    $k_opad = $key ^ $opad;
    return md5($k_opad . pack("H*", md5($k_ipad . $data)));
}

$merchantCode = "YOURCODE123"; //your account's merchant code available in the 'System settings' area of the cPanel: https://secure.avangate.com/cpanel/account_settings.php
$key          = "SECRET_KEY"; //your account's secret key available in the 'System settings' area of the cPanel: https://secure.avangate.com/cpanel/account_settings.php
$now          = gmdate('Y-m-d H:i:s'); //date_default_timezone_set('UTC')

$string = strlen($merchantCode) . $merchantCode . strlen($now) . $now;
$hash   = hmac($key, $string);

try {
    $sessionID = $client->login($merchantCode, $now, $hash);
}

catch (SoapFault $e) {
    echo "Authentication: " . $e->getMessage();
    exit;
}

$Code = '5C08A761E1';

try {
    $MyProductGroup = $client->getProductGroup($sessionID, $Code);
}

catch (SoapFault $e) {
    echo "My Product Group: " . $e->getMessage();
    exit;
}

var_dump("My Product Group", $MyProductGroup);


?>

Test subscription options

Overview

The subscription details page enables you to test the renewal and notifications system for test subscriptions. The following options are available:

Send expiration email to the subscription's end-user:

  • Test recurring billing notification. You can also simulate dunning management messages. 
  • Test manual payment notification.

Renew subscription:

  • Test an automatic renewal and recurring charge.
  • Test a manual renewal.

Imported subscriptions

Import test subscription and data from your or a third-party system and test advanced renewal and upgrade scenarios in the 2Checkout platform.

Click here to learn how to import test subscriptions with or without dummy credit card information. Note that you can only attach the credit cards on this list to any imported test subscription. 

Use cases for imported test subscriptions without dummy card data:

  • Renewal using the hosted cart
  • Manual renewal using the API
  • Upgrade using the hosted cart

Use cases for imported test subscriptions with dummy card data:

  • Renewal using the hosted cart
  • Manual renewal using the API
  • Upgrade using the hosted cart
  • Simulate a recurring charge and renewal via your control panel
  • Trigger a recurring charge using the API
  • Upgrade the subscription using the API
  • Create tokens for the card-on-file and generate tokenized payments both using the hosted cart and the API

 

Need help?

Do you have a question? If you didn’t find the answer you are looking for in our documentation, you can contact our Support teams for more information. If you have a technical issue or question, please contact us. We are happy to help.

Not yet a Verifone customer?

We’ll help you choose the right payment solution for your business, wherever you want to sell, in-person or online. Our team of experts will happily discuss your needs.

Verifone logo