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
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
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
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