text: from __future__ import unicode_literals
  import binascii
  
  from cryptography.hazmat.primitives import padding
  from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
  from cryptography.hazmat.backends import default_backend
  from django.core.exceptions import ImproperlyConfigured
  from django.shortcuts import redirect
  
  from ..core import BasicProvider
  from .. import PaymentStatus
  
  
  class SagepayProvider(BasicProvider):
      '''
      sagepay.com payment provider
  
      vendor:
          vendor name
      encryption_key:
          encryption key
      endpoint:
          gateway URL to post transaction data to
      '''
      _version = '3.00'
      _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 _get_cipher(self):
          backend = default_backend()
          return Cipher(algorithms.AES(self._enckey), modes.CBC(self._enckey),
                        backend=backend)
  
      def _get_padding(self):
          return padding.PKCS7(128)
  
      def aes_enc(self, data):
          data = data.encode('utf-8')
          padder = self._get_padding().padder()
          data = padder.update(data) + padder.finalize()
          encryptor = self._get_cipher().encryptor()
          enc = encryptor.update(data) + encryptor.finalize()
          return b"@" + binascii.hexlify(enc)
  
      def aes_dec(self, data):
          data = data.lstrip(b'@')
          data = binascii.unhexlify(data)
          decryptor = self._get_cipher().decryptor()
          data = decryptor.update(data) + decryptor.finalize()
          return data.decode('utf-8')
  
      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,
              'Description': "Payment #%s" % (payment.pk,),
              'SuccessURL': return_url,
              'FailureURL': return_url,
              '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}
          if payment.billing_country_code == 'US':
              data['BillingState'] = payment.billing_country_area
              data['DeliveryState'] = payment.billing_country_area
          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 == PaymentStatus.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(PaymentStatus.CONFIRMED)
                  return redirect(success_url)
              else:
                  # XXX: We should recognize AUTHENTICATED and REGISTERED in the future.
                  payment.change_status(PaymentStatus.REJECTED)
                  return redirect(payment.get_failure_url())
          return redirect(success_url)
  