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.

warning WARNING: This code is not 100% working code and is still being worked out how to implement fully - handle with care

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)

$curl = curl_init();
curl_setopt_array($curl, array(
    CURLOPT_URL            => "",
    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);


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

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

Step 1 Result:

  • MSK:61D46443-C451-4CCD-931A-2D05FC9B5974
  • 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.

    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>
            <div class='form-group'>
                <div class='label-column'>First Name</div>
                <div class='input-column'><input type='text' name='FirstNm'></div>
            <div class='form-group'>
                <div class='label-column'>Last Name</div>
                <div class='input-column'><input type='text' name='LastNm'></div>
            <div class='form-group'>
                <div class='label-column'>Line 1</div>
                <div class='input-column'><input type='text' name='AddressLine1'></div>
            <div class='form-group'>
                <div class='label-column'>Town/City</div>
                <div class='input-column'><input type='text' name='City'></div>
            <div class='form-group'>
                <div class='label-column'>Post code</div>
                <div class='input-column'><input type='text' name='Postcode'></div>
            <div class='form-group'>
                <div class='label-column'>Country</div>
                <div class='input-column'><input type='text' name='CountryCode'></div>
            <div id=\"submit-container\">
                <button type=\"submit\" onClick='doShowTarget()'>Make Payment</button>
            MSK:<input type='text' readonly name='merchantSessionKey' value='$msk'>
    echo /**@lang Javascript */ "
        <script src=''></script>
        <script src=''></script>
            sagepayCheckout({ merchantSessionKey: '$msk' }).form(); 

    Step 2 Result:

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

    warning Never use real cards on this test page.

    First Name
    Last Name


    Line 1
    Post code


    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
  • 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

    echo "<iframe name='formtarget' id='formtarget' class='hidden'></iframe>";
    No code file found

    Step 3 Result:

    sagepay_processor.php output in the sagepay_checkout.php iframe:

    Waiting for form submission from above


    New Comment

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

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

    Tue May 30, 23 05:26:14