class Facade(object):
    """
    A bridge between oscar's objects and the core gateway object
    """
    def __init__(self):
        self.gateway = Gateway(
            settings.PAYMENTEXPRESS_POST_URL, settings.PAYMENTEXPRESS_USERNAME,
            settings.PAYMENTEXPRESS_PASSWORD,
            getattr(settings, 'PAYMENTEXPRESS_CURRENCY', 'AUD'))

    def _check_amount(self, amount):
        if amount == 0 or amount is None:
            raise UnableToTakePayment("Order amount must be non-zero")

    def _get_merchant_reference(self, order_number, txn_type):
        num_previous = OrderTransaction.objects.filter(
            order_number=order_number, txn_type=txn_type).count()

        # Get a random number to append to the end.  This solves the problem
        # where a previous request crashed out and didn't save a model instance
        # Hence we can get a clash of merchant references.
        rand = "%04.f" % (random.random() * 10000)
        return u'%s_%s_%d_%s' % (order_number, txn_type.upper(),
                                 num_previous + 1, rand)

    def _get_friendly_decline_message(self):
        return ('The transaction was declined by your bank - ' +
                'please check your bankcard details and try again')

    def _handle_response(self, txn_type, order_number, amount, response):

        OrderTransaction.objects.create(
            order_number=order_number,
            txn_type=txn_type,
            txn_ref=response['dps_txn_ref'],
            amount=amount,
            response_code=response['response_code'],
            response_message=response.get_message(),
            request_xml=response.request_xml,
            response_xml=response.response_xml)

        if response.is_successful():
            return {
                'txn_reference': response['dps_txn_ref'],
                'partner_reference': response['dps_billing_id'],
            }

        elif response.is_declined():
            raise UnableToTakePayment(self._get_friendly_decline_message())

        else:
            raise InvalidGatewayRequestError(response.get_message())

    def _format_card_date(self, str_date):
        # Dirty hack so that Oscar's BankcardForm doesn't need to be overridden
        if str_date is None:
            return None
        return str_date.replace('/', '')

    def authorise(self, order_number, amount, bankcard):
        """
        Authorizes a transaction.
        Must be completed within 7 days using the "Complete" TxnType
        """
        self._check_amount(amount)

        card_issue_date = self._format_card_date(bankcard.start_date)
        card_expiry_date = self._format_card_date(bankcard.expiry_date)

        merchant_ref = self._get_merchant_reference(order_number, AUTH)
        res = self.gateway.authorise(card_holder=bankcard.card_holder_name,
                                     card_number=bankcard.card_number,
                                     card_issue_date=card_issue_date,
                                     card_expiry=card_expiry_date,
                                     cvc2=bankcard.cvv,
                                     amount=amount,
                                     merchant_ref=merchant_ref)
        return self._handle_response(AUTH, order_number, amount, res)

    def complete(self, order_number, amount, dps_txn_ref):
        """
        Completes (settles) a pre-approved Auth Transaction.
        The DpsTxnRef value returned by the original approved Auth transaction
        must be supplied.
        """
        self._check_amount(amount)
        merchant_ref = self._get_merchant_reference(order_number, COMPLETE)
        res = self.gateway.complete(amount=amount,
                                    dps_txn_ref=dps_txn_ref,
                                    merchant_ref=merchant_ref)
        return self._handle_response(COMPLETE, order_number, amount, res)

    def purchase(self, order_number, amount, billing_id=None, bankcard=None):
        """
        Purchase - Funds are transferred immediately.
        """
        self._check_amount(amount)

        res = None
        merchant_ref = self._get_merchant_reference(order_number, PURCHASE)

        if billing_id:
            res = self.gateway.purchase(amount=amount,
                                        dps_billing_id=billing_id,
                                        merchant_ref=merchant_ref)
        elif bankcard:
            card_issue_date = self._format_card_date(bankcard.start_date)
            card_expiry_date = self._format_card_date(bankcard.expiry_date)
            res = self.gateway.purchase(amount=amount,
                                        card_holder=bankcard.card_holder_name,
                                        card_number=bankcard.card_number,
                                        card_issue_date=card_issue_date,
                                        card_expiry=card_expiry_date,
                                        cvc2=bankcard.cvv,
                                        merchant_ref=merchant_ref,
                                        enable_add_bill_card=1)
        else:
            raise ValueError("You must specify either a billing id or " +
                             "a merchant reference")

        return self._handle_response(PURCHASE, order_number, amount, res)

    def refund(self, order_number, amount, dps_txn_ref):
        """
        Refund - Funds transferred immediately.
        Must be enabled as a special option.
        """
        self._check_amount(amount)
        merchant_ref = self._get_merchant_reference(order_number, REFUND)
        res = self.gateway.refund(amount=amount,
                                  dps_txn_ref=dps_txn_ref,
                                  merchant_ref=merchant_ref)
        return self._handle_response(REFUND, order_number, amount, res)

    def validate(self, bankcard):
        """
        Validation Transaction.
        Effects a $1.00 Auth to validate card details including expiry date.
        Often utilised with the EnableAddBillCard property set to 1 to
        automatically add to Billing Database if the transaction is approved.
        """
        amount = 1.00
        card_issue_date = self._format_card_date(bankcard.start_date)
        card_expiry_date = self._format_card_date(bankcard.expiry_date)

        res = self.gateway.validate(amount=amount,
                                    card_holder=bankcard.card_holder_name,
                                    card_number=bankcard.card_number,
                                    card_issue_date=card_issue_date,
                                    card_expiry=card_expiry_date,
                                    cvc2=bankcard.cvv,
                                    enable_add_bill_card=1)
        return self._handle_response(VALIDATE, None, amount, res)
class ApiResponseTests(MockedResponseTestCase):
    def setUp(self):
        self.gateway = Gateway(
            post_url='https://sec.paymentexpress.com/pxpost.aspx',
            username='******',
            password='******',
            currency='AUD')

    def test_authorise_returns_response(self):
        with patch('requests.post') as post:
            post.return_value = self.create_mock_response(
                SAMPLE_SUCCESSFUL_RESPONSE)
            self.assertIsInstance(
                self.gateway.authorise(card_holder='Frankie',
                                       card_number=CARD_VISA,
                                       cvc2='123',
                                       amount=1.23), Response)

    def test_complete_returns_response(self):
        with patch('requests.post') as post:
            post.return_value = self.create_mock_response(
                SAMPLE_SUCCESSFUL_RESPONSE)
            self.assertIsInstance(
                self.gateway.complete(
                    dps_txn_ref='1234',
                    amount=1.23,
                ), Response)

    def test_purchase_with_billing_id_returns_response(self):
        with patch('requests.post') as post:
            post.return_value = self.create_mock_response(
                SAMPLE_SUCCESSFUL_RESPONSE)
            self.assertIsInstance(
                self.gateway.purchase(dps_billing_id='123', amount=1.23),
                Response)

    def test_purchase_with_bankcard_returns_response(self):
        with patch('requests.post') as post:
            post.return_value = self.create_mock_response(
                SAMPLE_SUCCESSFUL_RESPONSE)
            self.assertIsInstance(
                self.gateway.purchase(card_holder='Frankie',
                                      card_number=CARD_VISA,
                                      card_expiry='1015',
                                      cvc2='123',
                                      merchant_ref='abc123',
                                      enable_add_bill_card=1,
                                      amount=1.23), Response)

    def test_refund_returns_response(self):
        with patch('requests.post') as post:
            post.return_value = self.create_mock_response(
                SAMPLE_SUCCESSFUL_RESPONSE)
            self.assertIsInstance(
                self.gateway.refund(dps_txn_ref='1234',
                                    merchant_ref='abc123',
                                    amount=1.23), Response)

    def test_validate_returns_response(self):
        with patch('requests.post') as post:
            post.return_value = self.create_mock_response(
                SAMPLE_SUCCESSFUL_RESPONSE)
            self.assertIsInstance(
                self.gateway.validate(card_holder='Frankie',
                                      card_number=CARD_VISA,
                                      cvc2='123',
                                      card_expiry='1015',
                                      amount=1.23), Response)
class ApiResponseTests(MockedResponseTestCase):

    def setUp(self):
        self.gateway = Gateway(
            post_url='https://sec.paymentexpress.com/pxpost.aspx',
            username='******',
            password='******',
            currency='AUD')

    def test_authorise_returns_response(self):
        with patch('requests.post') as post:
            post.return_value = self.create_mock_response(
                SAMPLE_SUCCESSFUL_RESPONSE
            )
            self.assertIsInstance(
                self.gateway.authorise(card_holder='Frankie',
                                       card_number=CARD_VISA,
                                       cvc2='123',
                                       amount=1.23), Response
            )

    def test_complete_returns_response(self):
        with patch('requests.post') as post:
            post.return_value = self.create_mock_response(
                SAMPLE_SUCCESSFUL_RESPONSE)
            self.assertIsInstance(
                self.gateway.complete(dps_txn_ref='1234', amount=1.23, ),
                Response)

    def test_purchase_with_billing_id_returns_response(self):
        with patch('requests.post') as post:
            post.return_value = self.create_mock_response(
                SAMPLE_SUCCESSFUL_RESPONSE
            )
            self.assertIsInstance(
                self.gateway.purchase(dps_billing_id='123', amount=1.23),
                Response
            )

    def test_purchase_with_bankcard_returns_response(self):
        with patch('requests.post') as post:
            post.return_value = self.create_mock_response(
                SAMPLE_SUCCESSFUL_RESPONSE
            )
            self.assertIsInstance(
                self.gateway.purchase(card_holder='Frankie',
                                      card_number=CARD_VISA,
                                      card_expiry='1015',
                                      cvc2='123',
                                      merchant_ref='abc123',
                                      enable_add_bill_card=1,
                                      amount=1.23), Response
                )

    def test_refund_returns_response(self):
        with patch('requests.post') as post:
            post.return_value = self.create_mock_response(
                SAMPLE_SUCCESSFUL_RESPONSE
            )
            self.assertIsInstance(
                self.gateway.refund(dps_txn_ref='1234',
                                    merchant_ref='abc123',
                                    amount=1.23), Response
                )

    def test_validate_returns_response(self):
        with patch('requests.post') as post:
            post.return_value = self.create_mock_response(
                SAMPLE_SUCCESSFUL_RESPONSE
            )
            self.assertIsInstance(
                self.gateway.validate(card_holder='Frankie',
                                      card_number=CARD_VISA,
                                      cvc2='123',
                                      card_expiry='1015',
                                      amount=1.23), Response
                )
class Facade(object):
    """
    A bridge between oscar's objects and the core gateway object
    """

    def __init__(self):
        self.gateway = Gateway(
            settings.PAYMENTEXPRESS_POST_URL,
            settings.PAYMENTEXPRESS_USERNAME,
            settings.PAYMENTEXPRESS_PASSWORD,
            getattr(settings, 'PAYMENTEXPRESS_CURRENCY', 'AUD')
        )

    def _check_amount(self, amount):
        if amount == 0 or amount is None:
            raise UnableToTakePayment("Order amount must be non-zero")

    def _get_merchant_reference(self, order_number, txn_type):
        num_previous = OrderTransaction.objects.filter(
            order_number=order_number,
            txn_type=txn_type).count()

        # Get a random number to append to the end.  This solves the problem
        # where a previous request crashed out and didn't save a model instance
        # Hence we can get a clash of merchant references.
        rand = "%04.f" % (random.random() * 10000)
        return u'%s_%s_%d_%s' % (
            order_number, txn_type.upper(), num_previous + 1, rand
        )

    def _get_friendly_decline_message(self):
        return ('The transaction was declined by your bank - ' +
            'please check your bankcard details and try again')

    def _handle_response(self, txn_type, order_number, amount, response):

        OrderTransaction.objects.create(
            order_number=order_number,
            txn_type=txn_type,
            txn_ref=response['dps_txn_ref'],
            amount=amount,
            response_code=response['response_code'],
            response_message=response.get_message(),
            request_xml=response.request_xml,
            response_xml=response.response_xml
            )

        if response.is_successful():
            return {
                'txn_reference': response['dps_txn_ref'],
                'partner_reference': response['dps_billing_id'],
                }

        elif response.is_declined():
            raise UnableToTakePayment(self._get_friendly_decline_message())

        else:
            raise InvalidGatewayRequestError(response.get_message())

    def _format_card_date(self, str_date):
        # Dirty hack so that Oscar's BankcardForm doesn't need to be overridden
        if str_date is None:
            return None
        return str_date.replace('/', '')

    def authorise(self, order_number, amount, bankcard):
        """
        Authorizes a transaction.
        Must be completed within 7 days using the "Complete" TxnType
        """
        self._check_amount(amount)

        card_issue_date = self._format_card_date(bankcard.start_date)
        card_expiry_date = self._format_card_date(bankcard.expiry_date)

        merchant_ref = self._get_merchant_reference(order_number, AUTH)
        res = self.gateway.authorise(card_holder=bankcard.card_holder_name,
                                     card_number=bankcard.card_number,
                                     card_issue_date=card_issue_date,
                                     card_expiry=card_expiry_date,
                                     cvc2=bankcard.cvv,
                                     amount=amount,
                                     merchant_ref=merchant_ref)
        return self._handle_response(AUTH, order_number, amount, res)

    def complete(self, order_number, amount, dps_txn_ref):
        """
        Completes (settles) a pre-approved Auth Transaction.
        The DpsTxnRef value returned by the original approved Auth transaction
        must be supplied.
        """
        self._check_amount(amount)
        merchant_ref = self._get_merchant_reference(order_number, COMPLETE)
        res = self.gateway.complete(amount=amount,
                                    dps_txn_ref=dps_txn_ref,
                                    merchant_ref=merchant_ref)
        return self._handle_response(COMPLETE, order_number, amount, res)

    def purchase(self, order_number, amount, billing_id=None, bankcard=None):
        """
        Purchase - Funds are transferred immediately.
        """
        self._check_amount(amount)

        res = None
        merchant_ref = self._get_merchant_reference(order_number, PURCHASE)

        if billing_id:
            res = self.gateway.purchase(amount=amount,
                                        dps_billing_id=billing_id,
                                        merchant_ref=merchant_ref)
        elif bankcard:
            card_issue_date = self._format_card_date(bankcard.start_date)
            card_expiry_date = self._format_card_date(bankcard.expiry_date)
            res = self.gateway.purchase(amount=amount,
                                        card_holder=bankcard.card_holder_name,
                                        card_number=bankcard.card_number,
                                        card_issue_date=card_issue_date,
                                        card_expiry=card_expiry_date,
                                        cvc2=bankcard.cvv,
                                        merchant_ref=merchant_ref,
                                        enable_add_bill_card=1)
        else:
            raise ValueError("You must specify either a billing id or " +
                "a merchant reference")

        return self._handle_response(PURCHASE, order_number, amount, res)

    def refund(self, order_number, amount, dps_txn_ref):
        """
        Refund - Funds transferred immediately.
        Must be enabled as a special option.
        """
        self._check_amount(amount)
        merchant_ref = self._get_merchant_reference(order_number, REFUND)
        res = self.gateway.refund(amount=amount,
                                  dps_txn_ref=dps_txn_ref,
                                  merchant_ref=merchant_ref)
        return self._handle_response(REFUND, order_number, amount, res)

    def validate(self, bankcard):
        """
        Validation Transaction.
        Effects a $1.00 Auth to validate card details including expiry date.
        Often utilised with the EnableAddBillCard property set to 1 to
        automatically add to Billing Database if the transaction is approved.
        """
        amount = 1.00
        card_issue_date = self._format_card_date(bankcard.start_date)
        card_expiry_date = self._format_card_date(bankcard.expiry_date)

        res = self.gateway.validate(amount=amount,
                                    card_holder=bankcard.card_holder_name,
                                    card_number=bankcard.card_number,
                                    card_issue_date=card_issue_date,
                                    card_expiry=card_expiry_date,
                                    cvc2=bankcard.cvv,
                                    enable_add_bill_card=1)
        return self._handle_response(VALIDATE, None, amount, res)