Skip to main content

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

     

    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

     

    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