Coder Social home page Coder Social logo

omnipay-mpay24's Introduction

Latest Stable Version Total Downloads Latest Unstable Version License

Table of Contents

mPAY24 Driver for Omnipay v3

There are two main front ends to initiate a payment: paymentPage and seamless.

The paymentPage (also known as the redirect method) handles payments completely offsite, while seamless keeps the user on site for most of the time, only going off site for 3D Secure or remote service authentication and authorisation.

Both intiation types use the same direct server (known as backend2backend) API methods.

Seamless Payment Initiation

This intiation method handles a number of payment types, some requiring additional PCI checks to use. The most comming credit card method will be token based, with a token being created at the back end first, and a URL related to that token being used to provide an iframe-based credit card form. An example of how this can work is shown below, but there are other ways it can be done, with additional front-end functionality to choose payment types.

Create Token

First a token is created on the back end. This token will need to be saved for the next stage, either in the session or passed through the order form.

use Omnipay\Omnipay;

$gateway = Omnipay::create('Mpay24_Seamless');

$gateway->setMerchantId('12345');
$gateway->setPassword('AB1234cd56');
$gateway->setTestMode(true);

$request = $gateway->token([
    'language' => 'en',
    //'customerId' => 'foo',
    //'profileId' => 'bar',
    //'style' => 'fizz',
]);

$response = $request->send();

if (! $response->isSuccessful()) {
    // Token could not be generated.
    echo '<p>Error: '.$response->getReturnCode().'</p>';
    exit;
}

This gives us a token and an iframe URL:

$response->getRedirectUrl();
$response->getToken();

The payment form can be created as follows, assuming /pay as the next enpoint in your flow. The iframe will contain the rendered credit card form. Add whatever additional customer or order details you want to the form. The iframe will be submitted with the form, but won't itself make any changes to your form; the credit card details go straight to the mPAY24 gateway. With this example, the submit bitton will remain disabled until the credit card details in the iframe have been completed.

The token does not need to go through the form, but could be carried forward through the session instead.

<?php

<iframe src="<?php echo $response->getRedirectUrl(); ?>" frameBorder="0" width="500"></iframe>

<form action="/pay" method="POST">
  <input name="token" type="hidden" value="<?php echo $response->getToken(); ?>" />
  <button id="paybutton" name="type" value="TOKEN" type="submit" disabled="true">Pay with creditcard</button>
  <button name="type" value="PAYPAL" type="submit">Pay with paypal</button>
</form>

<script>
  window.addEventListener("message", checkValid, false);
  function checkValid(form) {
    var data = JSON.parse(form.data);
    if (data.valid === "true") {
      document.getElementById("paybutton").disabled=false;
    }
  }
</script>

The /pay endpoint handles the actual payment.

The above form does not redirect the user to a payment page. Instead, it sends the card details to the gateway, with the token as a key. So in the next step, the gateway will already have the card details and the merchant site will just use the pre-generated token to reference them when completing the payment.

Payment Using Token

use Omnipay\Omnipay;
use Omnipay\Common\CreditCard;

$gateway = Omnipay::create('Mpay24_Seamless');

$gateway->setMerchantId('12345');
$gateway->setPassword('AB1234cd56');
$gateway->setTestMode(true);

$card = new CreditCard([
    'name' => 'Fred Bloggs',
    //
    'billingName' => 'Fred Billing',
    'billingAddress1' => 'Street 1',
    'billingAddress2' => 'Street 2',
    'billingCity' => 'City',
    'billingPostcode' => 'Postcode',
    'billingCountry' => 'GB',
    //
    'shippingName' => 'Fred Shipping',
    'shippingAddress1' => 'Street 1',
    'shippingAddress2' => 'Street 2',
    'shippingCity' => 'City',
    'shippingPostcode' => 'Postcode',
    'shippingCountry' => 'GB',
]);

$request = $gateway->purchase([
    'paymentType' => 'TOKEN', // or PAYPAL etc. e.g $_POST['type'] in this example.
    'amount' => '9.98',
    'currency' => 'EUR',
    'token' => $token, // e.g. $_POST['token']
    'transactionId' => $transactionId,
    'description' => 'Test Order',
    'returnUrl' => 'https://example.com/complete/success',
    'errorUrl' => 'https://example.com/complete/error',
    'notifyUrl' => 'https://example.com/notify',
    'language' => 'en',
    'card' => $card,
]);

$response = $request->send();

If the payment request is succcessful, then a redirect is likely to be needed to complete 3D Secure actions, Paypal or bank authentication and so on:

if (! $response->isSuccessful() && $response->isRedirect()) {
    $response->redirect();
    exit;
}

Seamless Complete Payment

After 3D Secure is completed, you will be returned to your /complete endpoint where you need to fetch the results of the transation:

use Omnipay\Omnipay;

$gateway = Omnipay::create('Mpay24_Seamless');

$gateway->setMerchantId('12345');
$gateway->setPassword('AB1234cd56');
$gateway->setTestMode(true);

$request = $gateway->completePurchase([
    // Will be in $_GET['TID'], but don't trust that; store it in the session.
    'transactionId' => $transactionId,
]);

$response = $request->send();

The $response will contain the normal Omnipay statuses and messages to define the result.

Note: your complete endpoint will be given the transaction result when redirected from the gateway. This result is not signed, and so can be easily manipulated by an end user. For this reason, this driver fetches the result from the gateway (a "pull" notification) to ensure no untrusted user data becomes a part of the process.

Payment Page

The payment page sends the user to the payment gateway to make a payment. The user will have a single payment type chosen for them, or can choose from a range of payment types offered, from a list filtered by the merchant site.

Purchase (redirect)

use Omnipay\Omnipay;
use Omnipay\Common\CreditCard;

$gateway = Omnipay::create('Mpay24_PaymentPage');

$gateway->setMerchantId('12345');
$gateway->setPassword('AB1234cd56');
$gateway->setTestMode(true);

$request = $gateway->purchase([
    'amount' => '9.98',
    'currency' => 'EUR',
    'token' => $token, // e.g. $_POST['token']
    'transactionId' => $transactionId,
    'description' => 'Test Order',
    'returnUrl' => 'https://example.com/complete/success',
    'errorUrl' => 'https://example.com/complete/error',
    'notifyUrl' => 'https://example.com/notify',
    'language' => 'en',
    'card' => $card, // Names, addresses
    'items' => $items,
]);

$response = $request->send();

If all is accepted, the $response object will be a redirect to the payment page.

To restrict the user to a single payment method, add the paymentType and brand. Example:

    'paymentType' => 'CC',
    'brand' => 'VISA',

Alternatively a range of payment methods can be supplied as a JSON string:

    'paymentMethods' => [
      ["paymentType" => "CC", "brand" => "VISA"],
      ["paymentType" => "CC", "brand" => "MASTERCARD"],
      ["paymentType" => "PAYPAL", "brand" => "PAYPAL"],
    ],

    // Or you can supply 'paymentMethods' as a JSON string.

For some payment types the brand is mandatory and for some it is optional. Examples:

  • paymentType "CC" and brand "VISA" will offer a visa card payment type only.
  • paymentType "CC" and no brand will offer a choice of all credit card types available.
  • No paymentType and no brand will offer a choice from all payment types available.

Payment Page Complete Payment

The transaction is completed in exactly the same way as for the seamless payments.

Payment Page Recurring Profiles

The gateway supports two types of profile: a single recurring payment profile for a customer, and up to 20 interactive profiles for each customer. The Payment Page API will support only ONE of these profile types at a time. This driver presently support ONLY recurrent payment profiles for Payment Page.

To create or update a customer's recurring payment profile, when making a purchase, set the createCard flag and provide a cardReference:

'createCard' => true,
'cardReference' => 'card-12345',

On completing the payment, you can check if the customer recurring profile was created or updated by checking the profile status:

$profileWasCreatedOrUpdates = $completeResult->isProfileChanged();

If this returns true, then it means the payment details for the current transaction have been saved against the customer ID. Use the customer ID as though it were a card reference when making a backend payment.

A customer ID can be used to make a recurring payment (an offline payment) liek this:

$gateway = Omnipay::create('Mpay24_Backend');

// Set the usual merchant ID and test mode flags.

$request = $gateway->purchase([
    'amount' => '9.99',
    'currency' => 'EUR',
    'transactionId' => 'new-transaction-id',
    'description' => 'Recurring Payment Description',
    'card' => [
        'name' => 'Customer Name',
    ],
    'notifyUrl' => 'https://omnipay.acadweb.co.uk/mpay24/notify.php?foo=bar&fee=fah', // mandatory
    'language' => 'de',
    'cardReference' => 'card-12345',
]);

This will return the details of the successful payment, or error details if not successful.

Notification Handler

The notification handler will accept notification server requests, and provide the status, amounts, payment methods actually used, transactionReference.

$request = $gateway->acceptNotification();

// $request->getTransactionId();
// $request->getTransactionReference();
// $request->getTransactionStatus();
// $request->getMoney();
// $request->isSuccessful();
// $request->getData();

omnipay-mpay24's People

Contributors

aimeos avatar aimeoscom avatar judgej avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

omnipay-mpay24's Issues

cancelled transaction is marked as paid

When I cancel a payment, mpay sends something like this to my notify endpoint:

$_GET = {array} [18]
 OPERATION = "CONFIRMATION"
 TID = "12345aa33"
 STATUS = "ERROR"
 PRICE = "15000"
 CURRENCY = "EUR"
 P_TYPE = "EPS"
 BRAND = "EPS"
 MPAYTID = "1234"
 USER_FIELD = ""
 ORDERDESC = "some nice description"
 CUSTOMER = "Werner Krauss"
 CUSTOMER_EMAIL = "[email protected]"
 LANGUAGE = "DE"
 CUSTOMER_ID = ""
 PROFILE_ID = ""
 PROFILE_STATUS = "IGNORED"
 FILTER_STATUS = ""
 APPR_CODE = ""

Silverstripe-Omnipay then creates a CompletePurchaseRequest, which tries to grab the transaction with the given TID from mpay24.

This results in an object like:

$params = {array} [20]
 OPERATION = "CONFIRMATION"
 TID = "12345aa33"
 STATUS = "ERROR"
 PRICE = "15000"
 CURRENCY = "EUR"
 P_TYPE = "EPS"
 BRAND = "EPS"
 MPAYTID = "1234"
 USER_FIELD = ""
 ORDERDESC = "some nice description"
 CUSTOMER = "Werner Krauss"
 CUSTOMER_EMAIL = "[email protected]"
 LANGUAGE = "DE"
 CUSTOMER_ID = ""
 PROFILE_ID = ""
 PROFILE_STATUS = "IGNORED"
 FILTER_STATUS = ""
 APPR_CODE = ""
 operationStatus = "OK"
 returnCode = "OK"

In ComplaetePurchaseRequest::sendData() I get this result:

$result = {Mpay24\Responses\TransactionStatusResponse} [7]
 paramCount = {int} 18
 transaction = {array} [18]
  OPERATION = "CONFIRMATION"
  TID = "12345aa33"
  STATUS = "ERROR"
  PRICE = "15000"
  CURRENCY = "EUR"
  P_TYPE = "EPS"
  BRAND = "EPS"
  MPAYTID = "1234"
  USER_FIELD = ""
  ORDERDESC = "some nice description"
  CUSTOMER = "Werner Krauss"
  CUSTOMER_EMAIL = "[email protected]"
  LANGUAGE = "DE"
  CUSTOMER_ID = ""
  PROFILE_ID = ""
  PROFILE_STATUS = "IGNORED"
  FILTER_STATUS = ""
  APPR_CODE = ""
 responseAsDom = {DOMDocument} [35]
 status = "OK"
 returnCode = "OK"
 createdAt = {int} 1592214783
 exception = null

which means, the TransactionStatusResponse was fine, but the transaction itself is bogus.

IMHO this code

        $params['operationStatus'] = $result->getStatus();
        $params['returnCode'] = $result->getReturnCode();

should not only check if the TransactionStatusResponse was OK, but also check the actual status via $result->getParams()

Add customer and profile support

This is needed for repeated payments. A profile is an approved repeated payment for a customer. Some notes:

  • A customer record must be created. ID is defined by the merchant site.
  • The customer chooses their own profile ID. The profile IDs are unique within a customer.
  • API endpoints allow the current profiles to be listed, added, removed.

Implement isPending()

Some mPAY24 responses must be interpreted as pending. At the moment they are not caught.

According to the mpay24 documentation, there are those values possible:
https://docs.mpay24.com/docs/transaction-overview#transaction-state

  • Capture : isSuccessful() -> true (for captures)
  • Failed : isSuccessful(), isCancelled(), isPending() -> false
  • Authorization : isSuccessful() -> true (for authorizations)
  • Credit : Can there be an isRefund() method returning true for that case?
  • Cancelled : isCancelled() -> true
  • Redirect : isPending() -> true
  • Pending : isPending() -> true
  • Waiting for confirmation : isPending() -> true
  • Chargeback : Maybe also isRefund()?
  • Withdraw : isSuccessful() -> true

The \Omnipay\Common\Message\NotificationInterface defines STATUS_PENDING
as additional status for $omniRequest->getTransactionStatus() when
isSuccessful() returns true.

addPaymentType: Brand can be optional

For giropay (german online banking), brand is set to "n/a" according to mpay docs, same for maestro and paypal.

I suggest to make PurchaseRequest::addPaymentType()'s $brand parameter optional and only add to mdxi if set. Adding an empty brand results in an INVALID_MDXI error.

INVALID_MDXI when ShippingCountry is empty

Somehow when Name is set, Omnipay\Common\CreditCard also sets ShippingName.

In my case I didn't provide any further shipping address data and got an INVALID_MDXI, cause shipping country was empty and didn't match "two characters". I worked around this by resetting Card's ShippingName.

Maybe you can find a better check for returning shipping address than only checking for the name.

PS: Thanks for this great omnipay gateway!

Entity encoding for paymentPage

The paymentPage API uses XML in the underlying, and the mPAY24 SDK uses DOMDocument to construct the XML. Although it contains an xmlEncode() method, it does not use it to encode supplied data. That data includes URLs. A URL with an ampersand (&) throws an exception unless it is XML encoded. Other fields act in a similar way.

All appropriate fields need encoding, preferably in one place so it can be turned off at some point if needed.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.