Skip to main content

Set up product bundles

Overview  

The 2Checkout system enables you to bundle your products together and offer superior deals to customers when compared to purchasing standalone items. Essentially, a bundle acts as a parent entity to multiple child products.

Availability  

Bundles are available for all 2Checkout vendors.

Requirements

In order to create a bundle, you need to have added/imported at least two (2) active products in the 2Checkout system.

How do I set up a bundle?  

To set up a bundle, make sure that you create or import at least two standalone products into the 2Checkout system, and that they're both active (available for purchase). Then follow these steps:

When opting to use this setting, note that the 2Checkout system will ignore all per-product configurations and will fulfill/deliver orders taking into account only to the fulfillment options valid for the bundle. Make sure that the bundle fulfillment configuration covers all child products.

  1. Navigate to Products, under Setup, and click Add product.
  2. Enter the bundle's name, and under Product type, select the Bundle option.
  3. Next, select at least two (2) of your available, standalone, active products (children) and add them to the bundle (parent entity) you just created.
  4. It's mandatory to select the default currency for the bundle as well as set up pricing. You can also configure additional settings such as product code, version model, marketing details, images, additional order fields, etc. When you're done personalizing your bundle, scroll down to the bottom of the page and hit the Add product button.
  5. Make your way to the Pricing tab and either edit the exiting pricing configuration or create a new one to fine tune the pricing strategy for the bundle.
  6. Under the Fulfillment tab, you can configure how orders containing the bundle will be fulfilled/delivered. Two options are available:
    • Use the fulfillment content & methods of each product in the bundle - Fulfillment for the products included in the bundle will be handled exactly the same as if they were purchased as standalone offerings. Essentially, fulfillment/delivery is done according to per-product configuration.
    • 2Checkout fulfillment for bundle product (binary keys, activation codes, product file, DIS) - You can configure bundle fulfillment options that will override per-product configuration. When choosing the 2Checkout fulfillment for bundle products, the 2Checkout system will deal with the delivery of:
      • Electronic code/key / binary file
      • Product file download link
      • DIS (Download insurance service)
      • Instant delivery (in the Thank you page)
  7. Click on the Renewal tab to define the renewal process for the bundle. Two (2) options are available:
    • Create subscription settings for bundle product - this enables the 2Checkout system to generate a single subscription that will govern all products/services part of the bundle.
    • Renewal: The 2Checkout system will subsequently ignore per-product configurations when renewing the bundle subscription for customers.
    • Refunds: When refunding the full or partial costs of the bundle acquisition, you can opt to have the bundle subscription canceled or leave it active.
    • Expiration/Cancelation: If you cancel the bundle subscription or if it expired, customers will no longer be able to use the products that they acquired as part of the bundle.
  8. Use the subscription settings of each product in the bundle - subscriptions are generated for every product/service in the bundle as if they were purchased as standalone offerings. When this option is selected, the bundle settings impact exclusively the initial purchased made by customers, after which, the bundle becomes irrelevant for future renewals of the subscriptions sold. Following the initial acquisition, subscriptions will be renewed and fulfilled according to their respective per-product configurations, with the 2Checkout system treating them as if they were purchased as individual products and not part of a bundle. 
    • Renewals and expirations/cancelations impact the standalone subscriptions generated for each product included in the bundle.
    • Refunds: When refunding the full or partial costs of the bundle acquisition, you can opt to have the standalone subscriptions canceled or leave them active, individually.

Mapping pricing options between the bundle and the child products  

At this point in time, the 2Checkout system does not support mapping pricing options between the parent bundle and the child products. Current behavior:

1. Use the subscription settings of each product in the bundle - For the initial purchase, customers can select specific pricing options for the bundle, but not for the individual products. 2Checkout generates the subscriptions for the bundled child products using the default, per-product pricing options, namely the preselected option of a Required pricing options group, if any. 2Checkout then uses these options for future renewals, as long as they're still configured for the product for which it generated the subscription.

2. Create subscription settings for bundle product - shoppers can select specific pricing options for the bundle during the initial purchase. Shoppers cannot choose the options of the individual child products. 2Checkout renews the standalone subscription all bundled child products according to the pricing options configuration of the parent bundle, while taking into account the initial purchase details. Essentially, the 2Checkout system preserves the options selected by shoppers during the initial purchase process for all renewals, as long as the product's pricing configuration still features them.

Otherwise, for the renewal process, 2Checkout uses the default pricing options, namely the preselected option of a Required pricing options group, if any.

Edit bundle information, pricing, fulfillment, and renewal settings  

Bundled products are governed by a single parent-level subscription: Any changes to the bundle information as well as pricing, fulfillment and renewal settings impacts future purchases, including renewal orders, provided that the renewal configuration used is Create subscription settings for bundle product. 

Disabling such a bundle results in the expiration of all subscriptions.

Individual subscriptions are created for standalone bundled products: Any changes to the bundle information as well as pricing, fulfillment and renewal settings impacts future purchases. Renewals of subscriptions for bundles already purchased are impacted by settings at the child product-level and not those per-bundle. 

Disabling such a bundle will not impact existing subscriptions, 
unless the products they're associated with are also disabled.

Edit bundle products  

Edit bundle products by navigating to the Bundle Options tab. You can add more products to the bundle, remove some of those added in the past, or both. Changes to child products will impact only:

  • New purchases (all acquisitions will feature the updated products in the bundle)
  • Subscription renewals (detailed below)
Bundle settings Subscription renewal
Individual subscriptions created for standalone bundled products Changes to bundle contents won't impact the renewal process of existing subscriptions. All subscriptions already sold to customers continue to renew according to per-product settings, regardless of changes to the contents of the parent bundle.
Create subscription settings for bundle product Subscription renewals done after the bundle contents are updated feature the new products.

Promotions, Upsell and Cross-sell  

Bundles behave similar to standalone products when it comes to promotions, upsell and cross-sell campaigns.

LCN (License Change Notifications) for Bundles  

2Checkout sends out LCNs out depending on the renewal settings of the bundle:

Create subscription settings for bundle product: 2Checkout generates a single LCN.

Use the subscription settings of each product in the bundle: 2Checkout generates multiple LCNs for each respective subscription associated with the child products included in the bundle.

IPN (Instant Payment Notifications)  

IPN notifications for bundle products behave the same as for any other regular product. 
For setting the IPN script, go to the System settings section: https://secure.2Checkout.com/cpanel/ipn_settings.php

IPN settings specific to a product bundle are:

  • IPN_BUNDLE_DETAILS[] : array with information on products included in the bundle, including IDs, names, codes, quantity, SKUs, product groups, and product group names.
  • IPN_BUNDLE_DELIVEREDCODES[]: represents delivery information for each product included in the bundle.

ISE (Instant Order Search Export) for bundles  

The export files (CVS or XML) generated using ISE contain the same information available in the Order Search Export, with the data included depending on the renewal settings of the bundle:

Create subscription settings for bundle product: In this case details are at bundle level:

  • Concatenated fulfillment/delivery keys/codes
  • 2Checkout includes subscription information for the bundle 

Use the subscription settings of each product in the bundle: In this case details are also at bundle level:

  • Fulfillment/delivery keys/codes are concatenated
  • The information for the subscriptions created for each bundled child product is not provided

IRN (Instant Refund Notification)  

Use IRN to issue:

  • Total refunds for bundles: Repay the entire costs of the order used by a customer to purchase the bundle.
  • Partial refunds for bundles: Issue partial refunds for bundles only in scenarios in which shoppers purchased multiple units of the bundle. For example, if Customer A acquired 2 units of Bundle A for $1,000, you can repay $500 for 1 unit of the bundle. However, you won't be able to issue partial refunds unless they match exactly the sum paid by the customer per unit of bundle.

When issuing total or partial refunds you also have the option of cancelling the bundle parent level subscription or the child-level subscriptions generated. If this option is selected subscriptions will be disabled immediately after the refund is processed by the 2Checkout system.

Refunds  

When issuing a total or a partial refund for an order, you have the options of:

  • Cancelling the bundle parent level subscription or the child-level subscriptions generated. If you select this option 2Checkout disables subscriptions immediately after processing the refund.
  • Stopping automatic billing for the bundle parent level subscription or the child-level subscriptions generated. When you select this option subscriptions remain active and in use until their renewal deadline, when they'll expire. Shoppers can still manually renew their subscriptions if they choose to.

Upgrades  

Upgrades involving bundles are not supported.

Renewal notifications  

2Checkout sends out emails depending on bundle settings:

  • According to global or per-bundle renewal settings if the bundled child products are governed by a parent-level bundle subscription.
  • According to global or per-product renewal settings if subscriptions are generated for each child product included in the bundle.

Fulfillment/delivery emails  

2Checkout sends out fulfillment/delivery notifications to shoppers according to the bundle fulfillment settings:

  • Use the fulfillment content & methods of each product in the bundle - Shoppers receive emails for each product in the bundle.
  • 2Checkout fulfillment for bundle product (binary keys, activation codes, product file, DIS) - Shoppers receive a single email.

FAQ  

1. Can I offer trials for bundles?

Trials for bundles are not supported.

2. Can I offer bundles to my partners/resellers?

Your partners can acquire bundles from you. 2Checkout generates subscriptions based on the renewal and delivery settings of the bundled product.

  • If you configure the bundled product to use the renewal and fulfillment settings of each product in the bundle, 2Checkout creates standalone subscriptions for each of the bundle components. Renewal and fulfillment (if applicable) are, therefore, made individually for each of them.
  • If you configure the bundled product to have bundle-level renewal and fulfillment settings, 2Checkout creates one single subscription for the entire bundle. Fulfillment (if applicable) and renewal are made at a bundle level.

3. What can bundles contain?

Bundles can be created using any type of standalone product. You won't be able to create bundles using other bundles, Download Software Insurance, etc.

4. Can I disable bundles?

Disable bundles by navigating to the Information tab, selecting the No option for Product enabled, scrolling down to the bottom of the page and hitting Save. The result of the disabling action differs according to the bundle renewal settings:

  • Create subscription settings for bundle product - Existing subscriptions continue to work until they expire - 2Checkout won't renew them.
  • Use the subscription settings of each product in the bundle - Existing subscriptions continue to work and 2Checkout renews them according to per-product settings.

5. Can I use usage billing with bundles?

This functionality is not supported.

6. Can bundles be imported using the product import feature?

This functionality is not supported.

7. How will shoppers receive activation keys for product bundles?

You can associate a product bundle with a static/ dynamic key list, same as you do with any other product, in which case shoppers receive a set of keys for one product only – the product bundle. If you do not specifically associate the product bundle to a key list, shoppers receive two (or more) sets of keys, one for each product included of the bundle.

8. What kind of notifications will shoppers receive in case of a product bundle?

Shoppers receive the same notification emails as in the case of a regular product purchase. If keys need to be delivered for each bundled product, 2Checkout sends all sets of keys as usual in separate email messages, one for each product in the order.

9. How can I find a bundled product In the Merchant Control Panel?

You can filter bundled products in the Merchant Control Panel by navigating to Dashboard → Products → Search tab and then selecting the Bundle products filter, as shown in this image.

bundle search.PNG

Place an order with iDEAL

Overview

Use the placeOrder method to create orders and collect payments from iDEAL.

Requirements

Only shoppers in the Netherlands can select iDEAL as a payment option and choose their bank. You're required to include the following text when using Avangate API and to make it visible for your customers in the ordering interface.

Order processed by Avangate, authorized reseller and merchant of the products and services offered within this store. 

Supported currencies

  • EUR

Workflow

  1. Use the getIdealIssuerBanks method for retrieving information on the Avangate list of banks that support iDEAL payments. More details about this method here.
  2. Shoppers select iDEAL as payment option in the interface you provide them, and select their bank from the list.
  3. Create the order object. Use IDEAL as the type of the PaymentDetails object, and include ReturnURL and CancelURL. The BankCode parameter should be added based on the bank selected by the customer, from the array obtained after calling method getIdealIssuerBanks.
  4. Use the placeOrder method to send the data to Avangate.
  5. Once you place the order, Avangate logs it into the system. At this point in time, the status of the order is PENDING.
  6. Avangate returns an Order object as the output of the placeOrder method. 
  7. Use the PaymentMethod object to create a redirect URL for the shoppers, concatenating the values of the Href and avng8apitoken parameters. Here's an example of the redirect URL:
    https://api.avangate.com/4.0/scripts/ideal/authorize/?avng8apitoken=f56373d92ed6b153
    
  8. Customers are directed to the iDEAL payment page, where they have to confirm their payment details before finishing the ordering process.
  9. Shoppers are redirected to the RedirectURL from the Order information object. In case the payment fails, shoppers are redirected to the CancelURL. 

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.

Order

Required (Object)

 

Object designed to collect all data necessary for an order, including billing, product/subscription plan and payment details.

To place an order with iDEAL, use IDEAL as the type of the PaymentDetails object and provide the following parameters as part of the PaymentMethod object:

  • ReturnURL - URL to which customers are redirected after a successful payment.
  • CancelURL - URL to which customers are directed after a failed payment attempt.
  • BankCode - information retrieved based on the bank selected by the customer, from the array obtained after calling getIdealIssuerBanks method.

See code sample for more details. 

Response

Order information

Object

Request


<?php

// authentication script: https://knowledgecenter.avangate.com/Integration/SOAP_API/API_4.0/02Authentication

require ('PATH_TO_AUTH');

$Order = new stdClass();
$Order->RefNo = NULL;
$Order->Currency = 'eur';
$Order->Country = 'nl';
$Order->Language = 'en';
$Order->CustomerIP = '91.220.121.21';
$Order->ExternalReference = NULL;
$Order->Source = NULL;
$Order->AffiliateId = NULL;
$Order->CustomerReference = NULL;
$Order->Items = array();
$Order->Items[0] = new stdClass();
$Order->Items[0]->Code = 'my_subscription_1';
$Order->Items[0]->Quantity = 1;
$Order->Items[0]->PriceOptions = NULL;
$Order->Items[0]->SKU = NULL;
$Order->Items[0]->Price = NULL;
$Order->Items[0]->CrossSell = NULL;
$Order->Items[0]->Trial = false;
$Order->Items[0]->AdditionalFields = NULL;
$Order->BillingDetails = new stdClass();
$Order->BillingDetails->FirstName = 'FirstName';
$Order->BillingDetails->LastName = 'LastName';
$Order->BillingDetails->CountryCode = 'NL';
$Order->BillingDetails->State = 'State Example';
$Order->BillingDetails->City = 'City Example';
$Order->BillingDetails->Address1 = 'Address example';
$Order->BillingDetails->Address2 = NULL;
$Order->BillingDetails->Zip = '12345';
$Order->BillingDetails->Email = 'email@address.com';
$Order->BillingDetails->Phone = NULL;
$Order->BillingDetails->Company = NULL;
$Order->PaymentDetails = new stdClass ();
$Order->PaymentDetails->Type = 'IDEAL';
$Order->PaymentDetails->Currency = 'eur';
$Order->PaymentDetails->PaymentMethod = new stdClass ();
$Order->PaymentDetails->CustomerIP = '91.220.121.21';
$Order->PaymentDetails->PaymentMethod->ReturnURL = 'YOUR_RETURN_URL';
$Order->PaymentDetails->PaymentMethod->CancelURL= 'YOUR_CANCEL_URL';
$Order->PaymentDetails->PaymentMethod->BankCode='BANK_CODE'; // value retrieved based on the bank selected by the customer, from the array obtained after calling method getIdealIssuerBanks.

try {
   $newOrder = $client->placeOrder($sessionID, $Order);
}
catch (SoapFault $e) {
    echo "newOrder: " . $e->getMessage();
    exit;
}

$idealredirect= $newOrder->PaymentDetails->PaymentMethod->Authorize->Href."/?avng8apitoken=".$newOrder->PaymentDetails->PaymentMethod->Authorize->Params->avng8apitoken;

header('Location:' . $idealredirect);
?>

Retrieve a price option group

Overview

Use the getPriceOptionGroup method to extract information about a specific price option group that you configured.

Parameters

Parameters Type/Description

ProductGroup

Object

Details below.

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.

groupCode

Required (string)

 

The code that the 2Checkout system generated or that you set for the product pricing options group.

Response

Parameters Type/Description

PriceOptionGroup

Array of objects

Request

<?php

require ('PATH_TO_AUTH');

$groupCode = 'USERS';

try {
    $existentPriceOptionGroup = $client->getPriceOptionGroup($sessionID, $groupCode);
}

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

var_dump("existentPriceOptionGroup", $existentPriceOptionGroup);

?>

 

On-site optimizations that drive conversions

Watch this exclusive webinar with our partner VeInteractive for an in-depth, first look at the onsite optimizations that helped their client Kingsoft Office achieve a 32% average campaign conversion rate. 

Cole Armstrong, Kingsoft's Director of Marketing, together with Sam Siegel, Director of Account Management at Ve Interactive and Shannon MacLeod, Conversion Optimization Consultant at 2Checkout, will deep dive into the tactics used along with tips for using analytics data to improve conversions

Join Our Webinar

Mark orders as shipped

Overview

If you offer tangible products through 2Checkout, or if your cart utilizes your 2Checkout account’s shipping methods (such as WooCommerce), you are required to mark these orders as shipped to qualify associated funds for payout(s). Any tangible sale that is not marked as shipped will not deposit and therefore will not be paid out.

Note: Sales for intangible products and orders passed in without a shipping method using one of our third party cart parameter sets will deposit automatically and cannot be marked as shipped.

Mark orders as shipped

You can confirm the delivery of your orders from Orders and customers > Fulfillment confirmations, or directly from the order page.

In the Fulfillment confirmations page, select the order you want to confirm the delivery for, and click Confirm fulfillment. In case you select a single order, for which a shipping method was used during the ordering process, you will be asked to fill in a Tracking number and Additional shipping information (both optional fields).

You can select multiple orders and confirm the delivery simultaneously for them.

Note: Once an order has been marked as shipped, 2Checkout sends a notification to the customer, letting them know that the delivery process has started.

Print package slip

Use the Print Package Slip functionality to print a document that confirms the shipment date, together with the order information. Add this document to the package that will be shipped to your customers, so that they have an easy time matching the package with their order.

Edit shipping information

Update the shipping information for orders marked as shipped. In case you want to provide an additional note to your customers regarding the delivery, or want to fill in a different Tracking Number, use the Edit Shipping Information functionality from the order page. 2Checkout sends a notification to your customers once you update the shipping information from an order.

Unassign a PricingOption Group

Overview

Use the unassignPricingConfigurationOptionGroup method to remove a PricingOption Group from a PricingConfiguration.

Parameters

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.

PricingConfigurationCode

Required (string)

 

Unique, system-generated pricing configuration identifier.  

PriceOptionsGroupAssigned

Required (Object)

 

Details below.

 

PriceOptionsGroupAssigned

Object

Code

Required (string)

 

PricingOption Group identifier.

Required

Required (Object)

 

True or false depending on whether the pricing options group is required during the purchase process or not.

Response

bool(true)

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;
}
 
$PricingConfigurationCode = '54DCBC3DC8';
$PriceOptionsGroupAssigned = new stdClass();
$PriceOptionsGroupAssigned->Code = 'STORAGE';
$PriceOptionsGroupAssigned->Required = false;
 
 
try {
    $UnassignedOption = $client-> unassignPricingConfigurationOptionGroup ($sessionID, $PricingConfigurationCode, $PriceOptionsGroupAssigned);
}

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

var_dump("Options", $UnassignedOption);
 
 
?>

Convert a trial

Overview

Use the convertTrial method to convert a trial to a paid subscription. In the eventuality of a conversion failure, you can use convertTrial again for the same trial subscription only after you let 24 hours pass since the initial attempt. The Avangate system attempts to automatically convert trials before they expire to full subscriptions, unless you made an attempt that failed less than 24 hours before the scheduled expiration deadline.

In case the trial conversion fails due to a transaction issue, the Avangate system sends unfinished payment follow-up emails to customers, provided that you set up lead management for your account.

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.

SubscriptionReference

Required (string)

 

Unique, system-generated subscription identifier of the trial that you convert to a paid subscription. The unique identifier from the Avangate system:

  • Must belong to an active trial subscription with the recurring billing system (auto-renewal) enabled.
  • The initial order placed to access the trial subscription must be finalized (status Finished).

Note: This method does not work for cancelled and/or expired trial subscriptions.

 

Avangate charges customers using the payment data attached to the trial subscription. In the case of credit/debit cards, if customers update their payment information in myAccount or if you update these details on behalf of your subscribers, the Avangate system uses the latest card info provided to charge subscription renewals.

ExtendSubscriptionFromPaymentDate

Optional (boolean)

 

true = Set the moment of the conversion as the start date of the full subscription. 

Example: A 7 day trial purchased on October 29 for a monthly subscription converted on October 30 with $ExtendSubscriptionFromPaymentDate = true; features the following Billing cycle expiration: Nov 30, 2013 and Avangate scraps the initial trial expiration date November 5.

 

false = Set initial trial expiration deadline as the fhe full subscription start date. 

Example: A 10 day trial purchased on October 29 for a monthly subscription converted on October 30 with $ExtendSubscriptionFromPaymentDate = false; features the following Billing cycle expiration: December 9, with the first month period of the subscription added to the trial lifetime stretching until November 8.

 

Can be NULL. If not sent, the default value is false.

Response

Boolean

true or false depending on whether the changes were successful or not.

Request


<?php
 
 
function callRPC($Request, $hostUrl, $Debug = true) {
    $curl = curl_init($hostUrl);
    curl_setopt($curl, CURLOPT_POST, 1);
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
    curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
    curl_setopt($curl, CURLOPT_SSLVERSION, 0);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: application/json', 'Accept: application/json'));
    $RequestString = json_encode($Request);
    curl_setopt($curl, CURLOPT_POSTFIELDS, $RequestString);
 
 
    if ($Debug) {
        $RequestString;
    }
    $ResponseString = curl_exec($curl);
    if ($Debug) {
        $ResponseString;
    }
 
    if (!empty($ResponseString)) {
        $Response = json_decode($ResponseString);
        if (isset($Response->result)) {
            return $Response->result;
        }
        if (!is_null($Response->error)) {
            var_dump($Request->method, $Response->error);
        }
    } else {
        return null;
    }
}
 
$host = 'https://api.avangate.com/rpc/3.0/';
 
$merchantCode = "YOUR_MERCHANT_CODE";// your account's merchant code available in the 'System settings' area of the cPanel: https://secure.avangate.com/cpanel/account_settings.php
$key = "YOUR_SECRET_KEY";// your account's secret key available in the 'System settings' area of the cPanel: https://secure.avangate.com/cpanel/account_settings.php
 
$string = strlen($merchantCode) . $merchantCode . strlen(gmdate('Y-m-d H:i:s')) . gmdate('Y-m-d H:i:s');
$hash = hash_hmac('md5', $string, $key);
 
$i = 1; // counter for api calls
// call login
$jsonRpcRequest = new stdClass();
$jsonRpcRequest->jsonrpc = '2.0';
$jsonRpcRequest->method = 'login';
$jsonRpcRequest->params = array($merchantCode, gmdate('Y-m-d H:i:s'), $hash);
$jsonRpcRequest->id = $i++;
 
$sessionID = callRPC($jsonRpcRequest, $host);
 
var_dump($sessionID);
$SubscriptionReference = 'BF44555C6C';
$ExtendSubscriptionFromPaymentDate = true; //false can also be used if you want the subscription start date to be the moment when the trial was set to initially expire.

$jsonRpcRequest = array (
'method' => 'convertTrial',
'params' => array($sessionID, $SubscriptionReference, $ExtendSubscriptionFromPaymentDate),
'id' => $i++,
'jsonrpc' => '2.0');

var_dump (callRPC((Object)$jsonRpcRequest, $host, true));

 

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

 

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