1. Go to this page and download the library: Download academe/opayo-pi library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
academe / opayo-pi example snippets
// composer l bring in guzzle/psr7 too, which is what we will use.
use GuzzleHttp\Client; // Or your favourite PSR-18 client
use GuzzleHttp\Exception\ClientException;
use Academe\Opayo\Pi\Model\Auth;
use Academe\Opayo\Pi\Model\Endpoint;
use Academe\Opayo\Pi\Request\CreateSessionKey;
use Academe\Opayo\Pi\Factory;
use Academe\Opayo\Pi\Request\CreateCardIdentifier;
use Academe\Opayo\Pi\Factory\ResponseFactory;
// Set up authentication details object.
$auth = new Auth('vendor-name', 'your-key', 'your-password');
// Also the endpoint.
// This one is set as the test API endpoint.
$endpoint = new Endpoint(Endpoint::MODE_TEST); // or MODE_LIVE
// Request object to construct the session key message.
$keyRequest = new CreateSessionKey($endpoint, $auth);
// PSR-18 HTTP client to send this message.
// If using Guzzle 6, then wrap it with an adapter such as HTTPlug,
// see https://docs.php-http.org/en/latest/clients/guzzle6-adapter.html
$client = new Client();
// Send the PSR-7 message to request a session key.
// The message will be generated by guzzle/psr7 or zendframework/zend-diactoros, with discovery
// on which is installed. You can explictly create the PSR-7 factory instead and pass that in
// as a third parameter when creating Request\CreateSessionKey.
$keyResponse = $client->sendRequest($keyRequest);
// Capture the result in our local response model.
// Use the ResponseFactory to automatically choose the correct message class.
$sessionKey = ResponseFactory::fromHttpResponse($keyResponse);
// If an error is indicated, then you will be returned an ErrorCollection instead
// of the session key. Look into that to diagnose the problem.
if ($sessionKey->isError()) {
// $session_key will be Response\ErrorCollection
var_dump($sessionKey->first());
exit; // Better handling needed than this!
}
// The result we want:
echo "Session key is: " . $sessionKey->getMerchantSessionKey();
use Academe\Opayo\Pi\Request\CreateCardIdentifier;
// Create a card indentifier on the API.
// Note the MMYY order is most often used for GB gateways like Sage Pay. Many European
// gateways tend to go most significant number (MSN) first, i.e. YYMM.
// $endpoint, $auth and $session_key from before:
$cardIdentifierRequest = new CreateCardIdentifier(
$endpoint, $auth, $sessionKey,
'Fred', '4929000000006', '1220', '123' // name, card, MMYY, CVV
);
// Send the PSR-7 message.
// The same error handling as shown earlier can be used.
$cardIdentifierResponse = $client->sendRequest($cardIdentifierRequest);
// Grab the result as a local model.
// If all is well, we will have a CardIdentifier that will be valid for use
// for the next 400 seconds.
$cardIdentifier = ResponseFactory::fromHttpResponse($cardIdentifierResponse);
// Again, an ErrorCollection will be returned in the event of an error:
if ($cardIdentifier->isError()) {
// $session_key will be Response\ErrorCollection
var_dump($cardIdentifier->first());
exit; // Don't do this in production.
}
// When the card is stored by the front end browser only, the following three
// items will be posted back to your application.
echo "Card identifier = " . $cardIdentifier->getCardIdentifier();
echo "Card type = " . $cardIdentifier->getCardType(); // e.g. Visa
// This card identifier will expire at the given time. Do note that this
// will be the timestamp at the Sage Pay server, not locally. You may be
// better off just starting your own 400 second timer here.
var_dump($cardIdentifier->getExpiry()); // DateTime object.
use Academe\Opayo\Pi\Money;
use Academe\Opayo\Pi\PaymentMethod;
use Academe\Opayo\Pi\Request\CreatePayment;
use Academe\Opayo\Pi\Request\Model\SingleUseCard;
use Academe\Opayo\Pi\Money\Amount;
use Academe\Opayo\Pi\Request\Model\Person;
use Academe\Opayo\Pi\Request\Model\Address;
use Academe\Opayo\Pi\Money\MoneyAmount;
use Money\Money as MoneyPhp;
// We need a billing address.
// Sage Pay has many mandatory fields that many gateways leave as optional.
// Sage Pay also has strict validation on these fields, so at the front end
// they must be presented to the user so they can modify the details if
// submission fails validation.
$billingAddress = Address::fromData([
'address1' => 'address one',
'postalCode' => 'NE26',
'city' => 'Whitley',
'state' => 'AL',
'country' => 'US',
]);
// We have a customer to bill.
$customer = new Person(
'Bill Firstname',
'Bill Lastname',
'[email protected]',
'+44 191 12345678'
);
// We have an amount to bill.
// This example is £9.99 (999 pennies).
$amount = Amount::GBP()->withMinorUnit(999);
// Or better to use the moneyphp/money package:
$amount = new MoneyAmount(MoneyPhp::GBP(999));
// We have a card to charge (we get the session key and captured the card identifier earlier).
// See below for details of the various card request objects.
$card = new SingleUseCard($session_key, $card_identifier);
// If you want the card to be reusable, then set its "save" flag:
// You will also need to force 3D secure and set a credential type (see below)
$card = $card->withSave();
// Put it all together into a payment transaction.
$paymentRequest = new CreatePayment(
$endpoint,
$auth,
$card,
'MyVendorTxCode-' . rand(10000000, 99999999), // This will be your local unique transaction ID.
$amount,
'My Purchase Description',
$billingAddress,
$customer,
null, // Optional shipping address
null, // Optional shipping recipient
[
// Don't use 3DSecure this time.
'Apply3DSecure' => CreatePayment::APPLY_3D_SECURE_DISABLE,
// Or force 3D Secure.
'Apply3DSecure' => CreatePayment::APPLY_3D_SECURE_FORCE,
// There are other options available.
'ApplyAvsCvcCheck' => CreatePayment::APPLY_AVS_CVC_CHECK_FORCE
]
);
// Send it to Sage Pay.
$paymentResponse = $client->sendRequest($paymentRequest);
// Assuming we got no exceptions, extract the response details.
$payment = ResponseFactory::fromHttpResponse($paymentResponse);
// Again, an ErrorCollection will be returned in the event of an error.
if ($payment->isError()) {
// $payment_response will be Response\ErrorCollection
var_dump($payment->first());
exit;
}
if ($payment->isRedirect()) {
// If the result is "3dAuth" then we will need to send the user off to do their 3D Secure
// authorisation (more about that process in a bit).
// A status of "Ok" means the transaction was successful.
// A number of validation errors can be captured and linked to specific submitted
// fields (more about that in a bit too).
// In future gateway releases there may be other reasons to redirect, such as PayPal
// authorisation.
// ...
}
// Statuses are listed in `AbstractTransaction` and can be obtained as an array using the static
// helper method:
// AbstractTransaction::constantList('STATUS')
echo "Final status is " . $payment->getStatus();
if ($payment->isSuccess()) {
// Payment is successfully authorised.
// Store everything, then tell the user they have paid.
}
use Academe\Opayo\Pi\Request\FetchTransaction;
// Prepare the message.
$transactionResult = new FetchTransaction(
$endpoint,
$auth,
$transaction_response->getTransactionId() // From earlier
);
// Send it to Sage Pay.
$response = $client->sendRequest($transactionResult);
// Assuming no exceptions, this gives you the payment or repeat payment record.
// But do check for errors in the usual way (i.e. you could get an error collection here).
$fetchedTransaction = ResponseFactory::fromHttpResponse($response);
use Academe\Opayo\Pi\Request\CreateRepeatPayment;
$repeat_payment = new CreateRepeatPayment(
$endpoint,
$auth,
$previous_transaction_id, // The previous payment to take card details from.
'MyVendorTxCode-' . rand(10000000, 99999999), // This will be your local unique transaction ID.
$amount, // Not limited by the original amount.
'My Repeat Purchase Description',
null, // Optional shipping address
null // Optional shipping recipient
);
$paymentRequest = new CreatePayment(
...
[
// Also available: APPLY_3D_SECURE_USEMSPSETTING and APPLY_3D_SECURE_FORCEIGNORINGRULES
'Apply3DSecure' => CreatePayment::APPLY_3D_SECURE_FORCE,
// or set APPLY_3D_SECURE_USEMSPSETTING to control it from the MyOpayo panel.
]
);
// $transaction_response is the message we get back after sending the payment request.
if ($transactionResponse->isRedirect()) {
// This is the bank URL that Sage Pay wants us to send the user to.
$url = $transactionResponse->getAcsUrl();
// This is where the bank will return the user when they are finished there.
// It needs to be an SSL URL to avoid browser errors. That is a consequence of
// the way the banks do the redirect back to the merchant siteusing POST and not GET,
// and something we cannot control.
$termUrl = 'https://example.com/your-3dsecure-result-handler-post-path/';
// $md is optional and is usually a key to help find the transaction in storage.
// For demo, we will just send the vendorTxCode here, but you should avoid exposing
// that value in a real site. You could leave it unused and just store the vendorTxCode
// in the session, since it will always only be used when the user session is available
// (i.e. all callbacks are done through the user's browser).
$md = $transactionResponse->getTransactionId();
// Based on the 3D Secure redirect message, our callback URL and our optional MD,
// we can now get all the POST fields to perform the redirect:
$paRequestFields = $transactionResponse->getPaRequestFields($termUrl, $md);
// All these fields will normally be hidden form items and the form would auto-submit
// using JavaScript. In this example we display the fields and don't auto-submit, so
// you can se what is happening:
echo "<p>Do 3DSecure</p>";
echo "<form method='post' action='$url'>";
foreach($paRequestFields as $field_name => $field_value) {
echo "<p>$field_name <input type='text' name='$field_name' value='$field_value' /></p>";
}
echo "<button type='submit'>Click here if not redirected in five seconds</button>";
echo "</form>";
// Exit in the appropriate way for your application or framework.
exit;
}
use Academe\Opayo\Pi\ServerRequest\Secure3DAcs;
$serverRequest = \GuzzleHttp\Psr7\ServerRequest::fromGlobals();
// or if using a framework that supplies a PSR-7 server request, just use that.
// isRequest() is just a sanity check before diving in with assumptions about the
// incoming request.
if (Secure3DAcs::isRequest($serverRequest->getBody()))
// Yeah, we got a 3d Secure server request coming at us. Process it here.
$secure3dServerRequest = new Secure3DAcs($serverRequest);
...
}
use Academe\Opayo\Pi\ServerRequest\Secure3DAcs;
if (Secure3DAcs::isRequest($_POST)) {
$secure3dServerRequest = Secure3DAcs::fromData($_POST);
...
}
use Academe\Opayo\Pi\Request\CreateSecure3D;
$request = new CreateSecure3D(
$endpoint,
$auth,
$secure3dServerRequest,
// Include the transaction ID.
// For this demo we sent that as `MD` data rather than storing it in the session.
// The transaction ID will generally be in the session; putting it in MD exposes it
// to the end user, so don't do this unless use a nonce!
$secure3dServerRequest->getMD()
);
// Send to Sage Pay and get the final 3D Secure result.
$response = $client->send($request);
$secure3dResponse = ResponseFactory::fromHttpResponse($response);
// This will be the result. We are looking for `Authenticated` or similar.
// The $secure3dResponse will normally be the full transaction details.
//
// NOTE: the result of the 3D Secure verification here is NOT safe to act on.
// I have found that on live, it is possible for the card to totally fail
// authentication, while the 3D Secure result returns `Authenticated` here.
// This is a decision the bank mnakes. They may skip the 3D Secure and mark
// it as "Authenticated" at their own risk. Just log this information.
// Instead, you MUST fetch the remote transaction from the gateway to find
// the real state of both the 3D Secure check and the card authentication
// checks.
echo $secure3dResponse->getStatus();
// Give the gateway some time to get its syncs in order.
sleep(1);
// Fetch the transaction with full details.
$transactionResult = new FetchTransaction(
$endpoint,
$auth,
// transaction ID would normally be in the session, as described above, but we put it
// into the MD for this demo.
$secure3dServerRequest->getMD()
);
// Send the request for the transaction to Sage Pay.
$response = $client->sendRequest($transactionResult);
// We should now have the payment, repeat payment, or an error collection.
$transactionFetch = ResponseFactory::fromHttpResponse($response);
// We should now have the final results.
// The transaction data is all [described in the docs](https://test.sagepay.com/documentation/#transactions).
echo json_encode($transactionFetch);
// When using 3D Secure v2, put together additional SCA details.
$strongCustomerAuthentication = new StrongCustomerAuthentication(
'https://example.com/your-3dsecure-notification-handler-post-url/',
$_SERVER['REMOTE_ADDR'], // IPv4 of user's browser
$_SERVER['HTTP_ACCEPT'], // Full Accept header provided by user's browser
true, // if javascript enabled on the browser; your payment page would need to detect that
'en-GB', // Language of the user's browser; docs are ambiguous on whether "en-GB" or just "en"
$_SERVER['HTTP_USER_AGENT'], // Full user agent of the user's browser
StrongCustomerAuthentication::CHALLENGE_WINDOW_SIZE_FULLSCREEN,
StrongCustomerAuthentication::TRANS_TYPE_GOODS_AND_SERVICE_PURCHASE,
[
// These are mandatory if javascript is enabled.
'browserJavaEnabled' => false,
'browserColorDepth' => StrongCustomerAuthentication::BROWSER_COLOR_DEPTH_32,
'browserScreenHeight' => 512,
'browserScreenWidth' => 1024,
'browserTz' => 60,
]
);
$paymentRequest = new CreatePayment(
...
[
// 3D Secure v2 needs Strong Customer Authentication (SCA) which
// Example 3DS POST redirect with a button.
// The $threeDSSessionData is an optional string that can be passed to the ACS,
// and will be returned with the result to help match up the user with their
// payment request.
// Note: do NOT pass the transactionId in as the threeDSSessionData. If you do
// this, Opayo will reject the 3DS redirect challenge. It's not known why, but
// has been observed. The vendorTxCode can be used with no issues at this time.
if ($transactionResponse->isRedirect()) {
$encThreeDSSessionData = base64_encode($threeDSSessionData);
echo '<form method="post" action="'.$payment->getAcsUrl().'">';
foreach($transactionResponse->getPaRequestFields($encThreeDSSessionData) as $name => $value) {
echo '<input type="hidden" name="'.$name.'" value="'.$value.'" />';
}
echo '<button type="submit">Click here if not redirected in five seconds</button>';
echo '</form>';
}
use Academe\Opayo\Pi\ServerRequest\Secure3Dv2Notification;
if (Secure3Dv2Notification::isRequest($_POST)) {
$secure3Dv2Notification = new Secure3Dv2Notification::fromData($_POST);
...
// If you need the sent session data, it can be found here:
$encThreeDSSessionData = $secure3Dv2Notification->getThreeDSSessionData();
$threeDSSessionData = base64_decode($encThreeDSSessionData);
}
use Academe\Opayo\Pi\Request\CreateSecure3Dv2Challenge;
$request = new CreateSecure3Dv2Challenge(
$endpoint, $auth, $notification, $transactionId
);
$response = $client->sendRequest($request);
$transaction = ResponseFactory::fromHttpResponse($response);
...
if ($saveCard) {
$card = $card->withSave();
}
$paymentRequest = new CreatePayment(
$endpoint,
$auth,
$card,
'MyVendorTxCode-' . rand(10000000, 99999999),
$amount,
'My Purchase Description',
$billingAddress,
$customer
);
if ($saveCard) {
// A credential type must be applied. There are convienence static creators to simplify this
// @see https://developer-eu.elavon.com/docs/opayo/credential-file-0
$paymentRequest->setCredentialType(CredentialType::createForNewReusableCard());
// Saving a token for reuse is only possible when 3D secure is applied (the cardholder must be present)
$paymentRequest->withApply3DSecure(CreatePayment::APPLY_3D_SECURE_FORCE);
}
$paymentResponse = $client->sendRequest($paymentRequest);
$request = new CreateSecure3Dv2Challenge(...);
$response = $client->sendRequest($request);
$response = ResponseFactory::fromHttpResponse($response);
$card = $response->getPaymentMethod();
if ($card instanceof Card && $card->isReusable()) {
// store this info against your customer for future use
$serialisedCard = json_encode($card);
// or individual attributes of the card if you want it normalised
$savedToken = $card->getCardIdentifier();
$lastFourDigits = $card->getLastFourDigits();
$expiryDate = $card->getExpiryDate();
}
if ($merchantPlacingOrderOnCustomersBehalf) {
$card = new ReusableCard($savedToken);
} else {
// customer placing order themselves, 'MyVendorTxCode-' . rand(10000000, 99999999),
$amount,
'My Purchase Description',
$billingAddress,
$customer
);
if ($merchantPlacingOrderOnCustomersBehalf) {
$paymentRequest->setEntryMethod(CreatePayment::ENTRY_METHOD_TELEPHONEORDER);
$paymentRequest->withApplyAvsCvcCheck(CreatePayment::APPLY_AVS_CVC_CHECK_DISABLE);
$paymentRequest->setCredentialType(CredentialType::createForMerchantReusingCard());
} else {
$paymentRequest->setCredentialType(CredentialType::createForCustomerReusingCard());
}
use Academe\Opayo\Pi\Request\LinkSecurityCode;
$securityCode = new LinkSecurityCode(
$endpoint,
$auth,
$sessionKey,
$cardIdentifier,
'123' // The CVV obtained from the user.
);
// Send the message to create the link.
// The result will be a `Response\NoContent` if all is well.
$securityCodeResponse = ResponseFactory::fromHttpResponse(
$client->sendRequest($securityCode)
);
// Should check for errors here:
if ($securityCodeResponse->isError()) {...}
...
// Get the transaction response.
$transactionResponse = ResponseFactory::fromHttpResponse($response);
// Get the card. Only cards are supported as Payment Method at this time,
// though that is likely to change when PayPal support is rolled out.
$card = $transactionResponse->getPaymentMethod();
// If it is reusable, then it can be serialised for storage:
if ($card->isReusable()) {
// Also can use getData() if you want the data without being serialised.
$serialisedCard = json_encode($card);
}
// In a later payment, the card can be reused:
$card = ReusableCard::fromData(json_decode($serialisedCard));
// Or more explicitly:
$card = new ReusableCard($cardIdentifier);
// Or if being linked to a freshly-entered CVV:
$card = new ReusableCard($merchantSessionKey, $cardIdentifier);
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.