Global Iris RealMPI

Global Iris RealMPI is an implementation of “3D Secure” that goes hand-in-hand with the Global Iris RealAuth gateway implementation. It is possible to use the gateway without the integration, and in fact that has to be done in for some card types.

“3D secure” (better known by the branded services such as “Verified by Visa” and “SecureCard”) is a service which helps to reduce the problem of online card fraud (especially fraudulent chargebacks) by getting the customer to authorize the use of a card via the card provider’s own website.

Please see the Global Iris docs for more information.

A summary of the full workflow looks like this:

  1. At checkout, the user fills in their credit card details and presses “Pay” or “Buy” etc.
  2. Your website uses this integration implementation to send a ‘3DS-Verifyenrolled’ request to the Global Iris RealMPI service. The service will return a response indicating:
    • whether the card issuer has a 3D Secure implementation. If they do, the URL of that service will be provided.
    • whether the card user is enrolled in the 3D Secure service.
  3. Assuming there is a 3D Secure service and the card user is enrolled, your website returns a response that contains a form. This form is targetted at the URL provided in step 2, and auto-submits itself, taking the user to the card issuer’s 3D Secure site (e.g. Verified by Visa). The form includes data relevant to the transaction (including card details and the amount etc.), and also a return URL that you must provide (the ‘TermURL’). You will also need to provide some custom ‘merchant data’ that will be passed back, so that you can continue the process in step 5.
  4. The user enters authentication details at the card issuer’s site (e.g. parts of a password) and hits submit.
  5. The card issuer’s site returns the user to your website, to the URL you specified in step 3, along with some POST data indicating the status of the authentication attempt. (The ‘PaRes’ data plus the ‘merchant data’ you supplied in step 3).
  6. Using the integration implementation, your website sends another request to the RealMPI service to verify the information POSTed in step 5 (since it is possible for the user to tamper with this data).
  7. If everything is OK, your website proceeds to use the Global Iris gateway to make the purchase. If there was an error, you will need to display an error message to the user and give them the opportunity to try again.

Many of these steps require custom code and views, but the integration does the heavy lifting of implementing the Real MPI spec.

Also, at various points there might be errors and shortcuts, so the full process is more complex. A full example is below. It assumes you are able to store order details etc. in some way. You will also need to be able to generate unique transaction IDs for each attempt at authentication. The best way is to create some kind of ‘pending payment’ model that is related to your order model.

Example:

This example assumes you are using the messages framework, and will display messages on the payment page. This simplifies some of the flows. Bits of code that you need to implement are marked TODO.

In some_project/views.py:

from django.contrib import messages
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt

from billing import get_gateway, get_integration
from billing.forms.global_iris_forms import CreditCardForm


# 'Pay' page - user fills in credit card.

def pay(request):

    # Order should have been checked, with shipping details etc.
    order = order_from_request(request) # TODO

    gateway = get_gateway('global_iris')
    integration = get_integration('global_iris_real_mpi')

    if request.method == "POST":
        form = CreditCardForm(request.POST, gateway=gateway)

        if form.is_valid(): # This has already included the gateway.validate_card check
            card = form.get_credit_card() # Returns a standard billing.CreditCard instance, validated.
            pending_payment = create_pending_payment(order)
            transaction_id = pending_payment.transaction_id
            global_iris_data = build_global_iris_data(order, transaction_id, card)

            # Some cards aren't supported, so we have to skip the integration
            do_purchase_directly = not integration.card_supported(card)

            if not do_purchase_directly:
                enrolled_resp = integration.send_3ds_verifyenrolled(global_iris_data)
                if enrolled_resp.error:
                    messages.error(request, "There was an error processing the credit card information: %s" % enrolled_resp.message)
                else:
                    # In some cases we can proceed to gateway.purchase at this point without doing
                    # the full 3Dsecure external checks (e.g. the user is not enrolled)

                    can_proceed, extra_data = enrolled_resp.proceed_with_auth(card)
                    if can_proceed:
                        global_iris_data.update(extra_data) # Includes some extra MPI data
                        do_purchase_directly = True
                    else:
                        return integration.redirect_to_acs_url(enrolled_resp,
                                                               term_url(request),
                                                               global_iris_data)

            if do_purchase_directly:
                return attempt_purchase(request, gateway, order, card, global_iris_data)

     else:
         form = CreditCardForm() # Could supply some initial data for 'cardholders_name' here

    return render(request, 'pay.html', {'form': form})


# View that handles POST from 3D secure site

@csrf_exempt
def handle_3ds(request):

    gateway = get_gateway('global_iris')
    integration = get_integration('global_iris_real_mpi')

    pares, merchant_data = integration.parse_3d_secure_request(request)
    verifysig_response = integration.send_3ds_verifysig(pares, merchant_data)
    card = merchant_data['card']
    proceed, extra_data = verifysig_response.proceed_with_auth(card)
    if proceed:
        merchant_data.update(extra_data)
        order = order_from_merchant_data(merchant_data)
        return attempt_purchase(request, cc_gateway, order, card, merchant_data)
    else:
        messages.error(request, "The use of your credit card could not be verified. Please try again.")
        return redirect_to_payment_page(request)

# Final view - when payment is complete:

def success(request):
    # TODO
    return render(request, 'success.html', {})

# Utilities:

def build_global_iris_data(order, transaction_id, card):
    return {
         'order_id': transaction_id,
         'card': card,
         'amount': order.balance, # TODO
         # Other 'merchant data' can be put here. (see Gateway docs)
         # It will be serialised to JSON, so must be supported by the JSON encoder.
         # The JSON encoder has special support for:
         # * CreditCard objects
         # * Decimal objects
    }

def create_pending_payment(order)
    # A pending payment should have a unique transaction_id. It's not
    # enough to use the order ID, because it needs to be different for
    # each *attempt* to pay for the order.
    # You will also probably need routines to clean up these things.

    # TODO

def term_url(request):
    # TODO - change
    return reverse('some_project.views.handle_3ds_request')

def attempt_purchase(request, gateway, order, card, options):
    transaction_id = options['order_id']
    purchase_resp = gateway.purchase(options['amount'],
                                     card,
                                     options=options)
    if purchase_resp['status'] == 'SUCCESS':
        record_payment(order, transaction_id)
        return redirect_to_success_page(request)
    else:
        record_failure(order, transaction_id, purchase_resp.get('response_code', ''), purchase_resp['message'])
        messages.error(request, "There was a problem with your credit card information: %s" % purchase_resp['message'])
        return redirect_to_payment_page(request)


def record_payment(order, transaction_id):
    # TODO:
    # record the payment and start processing the order
    # (change status etc). if it is fully paid.


def record_failure(order, transaction_id, response_code, message):
    # TODO
    # record a payment failure somehow.


def redirect_to_success_page(request):
    # TODO - change
    return HttpResponseRedirect(reverse('some_project.views.success'))


def redirect_to_payment_page(request):
    # TODO - change
    return HttpResponseRedirect(reverse('some_project.views.pay'))


def order_from_merchant_data(merchant_data):
    transaction_id = merchant_data['order_id']
    # TODO - change
    pending_payment = PendingPayment.objects.get(transaction_id=transaction_id)
    return pending_payment.order

In the urls.py:

urlpatterns += patterns('',
  (r'^pay/', 'some_project.views.pay'),
  (r'^success/', 'some_project.views.success'),
  (r'^3ds/', 'some_project.views.handle_3ds'),
)