Using PHP to set up and collect a payment using a SagePay/Opayo Drop-in Checkout input form

This is a walk through of the PHP & Javascript code required for collecting a card payment method using .

This code is based on an example found on opayo/integrate-our-drop-checkout .

The results shown are actual running code using the SagePay/Opayo Sandbox account (opayo/test-sandbox).

The code here is using the Opayo developers test environment and will not collect real funds.

Opayo fees in the UK are from: [Flex plan:] £32p/m for up to 350 transactions p/m, then 12p per transaction above that.

Written: Aug-2022 PHP: 7+ Sagepay.js: v1

Step 1 Code:

Step 1 : Create a merchant session key (MSK)


sagepay_checkout.php
$curl = curl_init();
curl_setopt_array($curl, array(
    CURLOPT_URL            => "https://pi-test.sagepay.com/api/v1/merchant-session-keys",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_SSL_VERIFYPEER => false,
    CURLOPT_SSL_VERIFYHOST => false,
    CURLOPT_CUSTOMREQUEST  => "POST",
    CURLOPT_POSTFIELDS     => '{ "vendorName": "sandbox" }',
    CURLOPT_HTTPHEADER     => array(
        "Authorization: Basic " . $key,
        "Cache-Control: no-cache",
        "Content-Type: application/json"
    )
));

$responseJson = curl_exec($curl);
$response = json_decode($responseJson, true); // false will allow you to do $response->merchantSessionKey, true will allow you to do $response['merchantSessionKey']
$err = curl_error($curl);

curl_close($curl);

echo "Response<PRE>$responseJson</PRE>";
if ($err) echo "Err<PRE>$err</PRE>";

$msk = $response['merchantSessionKey'];
echo "<LI>MSK:$msk";

Step 1 Result:

Response
{"expiry":"2022-08-09T23:10:05.483+01:00","merchantSessionKey":"DB9F9B98-A3EB-4196-863B-1906568ADBE0"}
  • MSK:DB9F9B98-A3EB-4196-863B-1906568ADBE0
  • Step 2 Code:

    Use the sagepay.js and sagepay-dropin.js scripts to generated a credit-card form fields that is inserted as an iFrame within sp-container. This iframe has been highlighted with the glowing red border for the demo.

    This is placed into a <form> that in this case is actioned to post through a new script (mysagepay_processor.php), plus passing through the previously generated merchantSessionKey.

    For readability, I have also used a <form target='...'> to allow the output of the php call to be directed to the current page; whether this is a good idea or not is yet to be known.

    The obvious flaw in this code is that sage*.js asks for the 'Name:' field, but (1) does not pass this through to the processor via the form, and (2) later on requires a first-name and a last-name. For this reason, we have had to ask for First Name and Last Name as well - not a very good UX. [If this isn't a reason to give up on this drop-in code and move onto a Custom Form then I don't know what is! 🤨]

    Note, this seems a very basic set of form fields, missing paypal, google/apple pay, and card logos.


    sagepay_checkout.php
    echo /**@lang HTML */ "
        <p style='color:red'><i class='material-icons' style='vertical-align: middle; color: red'>warning</i> Never use real cards on this test page.</p>
    
        <form action='mysagepay_processor.php' target='formtarget' method='POST'>
        
            <div id=\"sp-container\"'></div>
            
            <br>
            <div class='form-group'>
                <div class='label-column'>First Name</div>
                <div class='input-column'><input type='text' name='FirstNm'></div>
            </div>
            
            <div class='form-group'>
                <div class='label-column'>Last Name</div>
                <div class='input-column'><input type='text' name='LastNm'></div>
            </div>
            
            <p><u>Address</u></p>
            <div class='form-group'>
                <div class='label-column'>Line 1</div>
                <div class='input-column'><input type='text' name='AddressLine1'></div>
            </div>
            
            <div class='form-group'>
                <div class='label-column'>Town/City</div>
                <div class='input-column'><input type='text' name='City'></div>
            </div>
            
            <div class='form-group'>
                <div class='label-column'>Post code</div>
                <div class='input-column'><input type='text' name='Postcode'></div>
            </div>
            
            <div class='form-group'>
                <div class='label-column'>Country</div>
                <div class='input-column'><input type='text' name='CountryCode'></div>
            </div>
            
            <div id=\"submit-container\">
                <button type=\"submit\" onClick='doShowTarget()'>Make Payment</button>
            </div>
            
            <br><hr>
            MSK:<input type='text' readonly name='merchantSessionKey' value='$msk'>
            
        </form>";
    
    
    echo /**@lang Javascript */ "
    
        <script src='https://pi-test.sagepay.com/api/v1/js/sagepay.js'></script>
        <script src='https://pi-test.opayo.co.uk/api/v1/js/sagepay-dropin.js'></script>
        <script>
            sagepayCheckout({ merchantSessionKey: '$msk' }).form(); 
        </script>
    
    ";

    Step 2 Result:

    HINT: Use the Test card options listed below (e.g. 4929000000006 - expires 08/22, CVC 123, etc)

    warning Never use real cards on this test page.


    First Name
    Last Name

    Address

    Line 1
    Town/City
    Post code
    Country


    MSK:

    Clicking Make Payment will make the sagepay.js call the form action page and into the next step target below




    expand_more Test card options:

  • See https://developer-eu.elavon.com/docs/opayo/test-sandbox#test-cards
  • Step 3 Code:

    The credit card details have been posted through card-identifier to our next step, mysagepay_processor, that now requests a payment, adding in amounts and currency information before posting it to sagepay.com


    sagepay_checkout.php
    echo "<iframe name='formtarget' id='formtarget' class='hidden'></iframe>";

    mysagepay_processor.php
    $merchantSessionKey = getSafePostTx($_POST["merchantSessionKey"]); // getSafePostTx strip bogus text out of the posting
    $cardIdentifier = getSafePostTx($_POST["card-identifier"]);
    $firstName = getSafePostTx($_POST['FirstNm']);
    $lastName = getSafePostTx($_POST['LastNm']);
    $billing_address = getSafePostTx($_POST['AddressLine1']);
    $billing_city = getSafePostTx($_POST['City']);
    $billing_zip = getSafePostTx($_POST['Postcode']);
    $billing_countrycd = getSafePostTx($_POST['CountryCode']);
    
    $amount = 100;
    $currency = "GBP";
    
    // Keys taken from https://developer-eu.elavon.com/docs/opayo/test-sandbox
    $ikey = "hJYxsw7HLbj40cB8udES8CDRFLhuJ8G54O6rDpUXvE6hYDrria";
    $ipwd = "o2iHSrFybYMZpmWOQMuhsXP52V4fBtpuSDshrKDSWsBY1OiN6hwd9Kb12z4j5Us5u";
    $key = base64_encode("$ikey:$ipwd");
    
    $curl = curl_init();
    if (1) {
    
        curl_setopt_array($curl, array(
            CURLOPT_URL            => "https://pi-test.sagepay.com/api/v1/transactions",
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false,
            CURLOPT_CUSTOMREQUEST  => "POST",
            CURLOPT_POSTFIELDS     => '{' .
                '"transactionType": "Payment",' .
                '"paymentMethod": {' .
                '    "card": {' .
                '        "merchantSessionKey": "' . $merchantSessionKey . '",' .
                '        "cardIdentifier": "' . $cardIdentifier . '"' .
                '    }' .
                '},' .
                '"vendorTxCode": "SagePayExample' . time() . '",' .
                '"amount": ' . $amount . ',' .
                '"currency": "' . $currency . '",' .
                '"description": "Sage Payment Integration Example",' .
                '"apply3DSecure": "UseMSPSetting",' .
                '"customerFirstName": "' . $firstName . '",' .
                '"customerLastName": "' . $lastName . '",' .
                '"billingAddress": {' .
                '    "address1": "' . $billing_address . '",' .
                '    "city": "' . $billing_city . '",' .
                '    "postalCode": "' . $billing_zip . '",' .
                '    "country": "' . $billing_countrycd . '"' .
                '},' .
                '"entryMethod": "Ecommerce"' .
                '}',
            CURLOPT_HTTPHEADER     => array(
                "Authorization: Basic $key",
                "Cache-Control: no-cache",
                "Content-Type: application/json"
            ),
        ));
    
        $responseJson = curl_exec($curl);
        $response = json_decode($responseJson, true); // false will allow you to do $response->merchantSessionKey, true will allow you to do $response['merchantSessionKey']
        $err = curl_error($curl);
    
        //echo "<PRE>$key</PRE>";
        echo "result:<PRE>$responseJson</PRE>";
        echo "err:<PRE>$err</PRE>";
    }
    curl_close($curl);

    Step 3 Result:

    sagepay_processor.php output in the sagepay_checkout.php iframe:

    Waiting for form submission from above

    Comments


    New Comment

    NOTE: (Put code blocks in [[[ and ]]] markup to be formatted.)

    (Posted comments will be checked by the administrator before being published)