aboutsummaryrefslogtreecommitdiff
path: root/payments/sagepay/__init__.py
blob: 1767e56cb45a009733ee07b5fc42b8304f81c596 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
from __future__ import unicode_literals
import binascii

from Crypto.Cipher import AES
from django.core.exceptions import ImproperlyConfigured
from django.shortcuts import redirect

from ..core import BasicProvider


class SagepayProvider(BasicProvider):
    '''
    sagepay.com payment provider

    vendor:
        vendor name
    encryption_key:
        encryption key
    endpoint:
        gateway URL to post transaction data to
    '''
    _version = '2.23'
    _action = 'https://test.sagepay.com/Simulator/VSPFormGateway.asp'

    def __init__(self, vendor, encryption_key, endpoint=_action, **kwargs):
        self._vendor = vendor
        self._enckey = encryption_key.encode('utf-8')
        self._action = endpoint
        super(SagepayProvider, self).__init__(**kwargs)
        if not self._capture:
            raise ImproperlyConfigured(
                'Sagepay does not support pre-authorization.')

    def _aes_pad(self, crypt):
        padding = ""
        padlength = 16 - (len(crypt.encode('utf-8')) % 16)
        for _i in range(1, padlength + 1):
            padding += chr(padlength)
        return crypt + padding

    def aes_enc(self, data):
        aes = AES.new(self._enckey, AES.MODE_CBC, self._enckey)
        data = self._aes_pad(data)
        enc = aes.encrypt(data.encode('utf-8'))
        enc = b"@" + binascii.hexlify(enc)
        return enc

    def aes_dec(self, data):
        data = data.lstrip(b'@').decode('utf-8')
        aes = AES.new(self._enckey, AES.MODE_CBC, self._enckey)
        dec = binascii.unhexlify(data)
        dec = aes.decrypt(dec)
        return dec

    def get_hidden_fields(self, payment):
        payment.save()
        return_url = self.get_return_url(payment)
        data = {
            'VendorTxCode': payment.pk,
            'Amount': "%.2f" % (payment.total,),
            'Currency': payment.currency,
            'SuccessURL': return_url,
            'FailureURL': return_url,
            'Description': "Payment #%s" % (payment.pk,),
            'BillingSurname': payment.billing_last_name,
            'BillingFirstnames': payment.billing_first_name,
            'BillingAddress1': payment.billing_address_1,
            'BillingAddress2': payment.billing_address_2,
            'BillingCity': payment.billing_city,
            'BillingPostCode': payment.billing_postcode,
            'BillingCountry': payment.billing_country_code,
            'DeliverySurname': payment.billing_last_name,
            'DeliveryFirstnames': payment.billing_first_name,
            'DeliveryAddress1': payment.billing_address_1,
            'DeliveryAddress2': payment.billing_address_2,
            'DeliveryCity': payment.billing_city,
            'DeliveryPostCode': payment.billing_postcode,
            'DeliveryCountry': payment.billing_country_code}
        udata = "&".join("%s=%s" % kv for kv in data.items())
        crypt = self.aes_enc(udata)
        return {'VPSProtocol': self._version, 'TxType': 'PAYMENT',
                'Vendor': self._vendor, 'Crypt': crypt}

    def process_data(self, payment, request):
        udata = self.aes_dec(request.GET['crypt'])
        data = {}
        for kv in udata.split('&'):
            k, v = kv.split('=')
            data[k] = v
        success_url = payment.get_success_url()
        if payment.status == 'waiting':
            # If the payment is not in waiting state, we probably have a page reload.
            # We should neither throw 404 nor alter the payment again in such case.
            if data['Status'] == 'OK':
                payment.captured_amount = payment.total
                payment.change_status('confirmed')
                return redirect(success_url)
            else:
                # XXX: We should recognize AUTHENTICATED and REGISTERED in the future.
                payment.change_status('rejected')
                return redirect(payment.get_failure_url())
        return redirect(success_url)