Exemple #1
0
    def test_known_good_itn_signature(self):
        known_good_itn_data = {
            'amount_fee': '-2.80',
            'amount_gross': '123.00',
            'amount_net': '120.20',
            'custom_int1': '',
            'custom_int2': '',
            'custom_int3': '',
            'custom_int4': '',
            'custom_int5': '',
            'custom_str1': '',
            'custom_str2': '',
            'custom_str3': '',
            'custom_str4': '',
            'custom_str5': '',
            'email_address': '*****@*****.**',
            'item_description': '',
            'item_name': 'Flux capacitor',
            'm_payment_id': '',
            'merchant_id': '10000100',
            'name_first': 'Test',
            'name_last': 'User 01',
            'payment_status': 'COMPLETE',
            'pf_payment_id': '558900',
            'signature': '94b05677771813701468289ed3cabed1',
        }

        calculated_signature = api.itn_signature(known_good_itn_data)
        assert known_good_itn_data['signature'] == calculated_signature
Exemple #2
0
def _itn_data_from_checkout(checkout_data, payment_form):
    notify_data = checkout_data.copy()
    # prepare server data
    notify_data['m_payment_id'] = payment_form.order.m_payment_id
    notify_data['amount_gross'] = checkout_data['amount']
    # Fields not part of ITN submissions:
    del notify_data['notify_url']
    del notify_data['amount']
    del notify_data['merchant_key']

    # Sign:
    notify_data['signature'] = api.itn_signature(notify_data)
    return notify_data
Exemple #3
0
    def clean(self):
        self.ip = self.request.META.get(conf.IP_HEADER, None)
        if not is_payfast_ip_address(self.ip):
            raise forms.ValidationError('untrusted ip: %s' % self.ip)

        # Verify signature
        sig = api.itn_signature(self.data)
        if sig != self.cleaned_data['signature']:
            raise forms.ValidationError('Signature is invalid: %s != %s' % (
                sig, self.cleaned_data['signature'],))

        if conf.USE_POSTBACK:
            is_valid = api.data_is_valid(self.request.POST, conf.SERVER)
            if is_valid is None:
                raise forms.ValidationError('Postback fails')
            if not is_valid:
                raise forms.ValidationError('Postback validation fails')

        return self.cleaned_data
Exemple #4
0
    def test_invalid_request(self):
        form = PayFastForm(initial={'amount': 100, 'item_name': 'foo'})
        notify_data = {'m_payment_id': form.order.m_payment_id}
        notify_data['signature'] = api.itn_signature(notify_data)
        response = self.client.post(notify_url(), notify_data)
        expected_amount = ('100' if django.VERSION <
                           (1, 8) else '100.00' if django.VERSION <
                           (2, 0) else '100')
        self._assertBadRequest(
            response, {
                'amount_gross': [{
                    'code':
                    '',
                    'message': ('Amount is not the same: {} != None'.format(
                        expected_amount))
                }],
                'item_name': [{
                    'code': 'required',
                    'message': 'This field is required.'
                }],
                'merchant_id': [{
                    'code': 'required',
                    'message': 'This field is required.'
                }],
            })
        self.assertEqual(self.notify_handler_orders, [])

        order = _order()
        self.assertEqual(order.request_ip, '127.0.0.1')
        self.assertEqual(
            set(order.debug_info.split('|')),
            {
                'amount_gross: Amount is not the same: {} != None'.format(
                    '100' if django.VERSION < (1, 8) else
                    # Django 1.8+ returns more precise DecimalField values
                    '100.00' if django.VERSION < (2, 0) else
                    # Django 2.0+ returns less precise DecimalField values again.
                    '100'),
                'item_name: This field is required.',
                'merchant_id: This field is required.',
            })
        self.assertEqual(order.trusted, False)
def do_payment(
        checkout_data,  # Dict[str, str]
        parsed_checkout,  # Dict[str, str]
        enable_itn,  # type: bool
):  # type: (...) -> Dict[str, str]
    """
    Common test helper: do a payment, and assert results.

    This takes a checkout's data and page parse (for session info and assertions).
    This will enable and verify ITN processing if `enable_itn` is true.

    Return the payment confirmation page's parse.
    """
    def _post_payment():  # type: () -> requests.Response
        return post_sandbox_payment(
            parsed_checkout['session_type'],
            parsed_checkout['session_id'],
            parsed_checkout['payment_method'],
        )

    if enable_itn:
        require_itn_configured()
        with itn_handler(ITN_HOST, ITN_PORT) as itn_queue:  # type: Queue
            response = _post_payment()
            itn_data = itn_queue.get(timeout=2)
    else:
        response = _post_payment()

    parsed_payment = parse_payfast_page(response)
    assert {
        'payment_summary': parsed_checkout['payment_summary'],
        'notice': 'Your payment was successful\n'
    } == parsed_payment

    if enable_itn:
        # Check the ITN result.

        # Expect whitespace-stripped versions of the checkout data.
        expected = {
            name: value.strip(api.CHECKOUT_SIGNATURE_IGNORED_WHITESPACE)
            for (name, value) in checkout_data.items()
        }

        expected_amount_gross = '{:.2f}'.format(
            decimal.Decimal(checkout_data['amount'].strip()))

        expected_signature = api.itn_signature(itn_data)
        assert {
            'm_payment_id': expected.get('m_payment_id', ''),
            'pf_payment_id': itn_data.get('pf_payment_id', 'MISSING'),
            'payment_status': 'COMPLETE',
            'item_name': expected.get('item_name', 'MISSING'),
            'item_description': expected.get('item_description', ''),
            'amount_gross': expected_amount_gross,
            'amount_fee': itn_data.get('amount_fee', 'MISSING'),
            'amount_net': itn_data.get('amount_net', 'MISSING'),
            'custom_str1': expected.get('custom_str1', ''),
            'custom_str2': expected.get('custom_str2', ''),
            'custom_str3': expected.get('custom_str3', ''),
            'custom_str4': expected.get('custom_str4', ''),
            'custom_str5': expected.get('custom_str5', ''),
            'custom_int1': expected.get('custom_int1', ''),
            'custom_int2': expected.get('custom_int2', ''),
            'custom_int3': expected.get('custom_int3', ''),
            'custom_int4': expected.get('custom_int4', ''),
            'custom_int5': expected.get('custom_int5', ''),

            # The sandbox seems to fix these names, rather than using the checkout submission data.
            'name_first': 'Test',
            'name_last': 'User 01',
            'email_address': expected.get('email_address',
                                          '*****@*****.**'),
            'merchant_id': '10000100',
            'signature': expected_signature,
        } == itn_data

    return parsed_payment