def test_capture_should_return_input_amount_invalid(self): auth = Processor.authorize(self.pm.payment_method_token, 100.00) capture = auth.capture(100.10) self.assertFalse(capture.success) err = {'context': 'input.amount', 'key': 'invalid', 'subclass': 'error'} self.assertIn(err, capture.error_messages) self.assertIn('The transaction amount was invalid.', capture.errors['input.amount'])
def test_capture_should_return_processor_transaction_declined(self): auth = Processor.authorize(self.pm.payment_method_token, 100.00) capture = auth.capture(100.02) self.assertFalse(capture.success) err = {'context': 'processor.transaction', 'key': 'declined', 'subclass': 'error'} self.assertIn(err, capture.error_messages) self.assertIn('The card was declined.', capture.errors['processor.transaction'])
def test_purchase_should_return_invalid_sandbox_card_error(self): data = { 'custom': '', 'first_name': '', 'last_name': '', 'address_1': '', 'address_2': '', 'city': '', 'state': '', 'zip': '10101', 'card_number': '5256486068715680', 'cvv': '111', 'expiry_month': '05', 'expiry_year': '2014', } token = test_helper.default_payment_method(data).payment_method_token purchase = Processor.purchase(token, 1.00) self.assertIn( { 'context': 'system.general', 'key': 'default', 'subclass': 'error', 'text': 'Invalid Sandbox Card Number. For more information, see: https://samurai.feefighters.com/developers/sandbox' }, purchase.error_messages)
def test_cvv_should_return_processor_cvv_result_code_N(self): token = test_helper.default_payment_method({'cvv':'222'}).payment_method_token purchase = Processor.purchase(token, 1.00, billing_reference=self.rand) self.assertTrue(purchase.success) self.assertEqual(purchase.processor_response['cvv_result_code'], 'N')
def test_cvv_should_return_processor_cvv_result_code_N(self): token = test_helper.default_payment_method({ 'cvv': '222' }).payment_method_token purchase = Processor.purchase(token, 1.00, billing_reference=self.rand) self.assertTrue(purchase.success) self.assertEqual(purchase.processor_response['cvv_result_code'], 'N')
def authorize(participant_id, pmt): """Given two unicodes, return a dict. This function attempts to authorize the credit card details referenced by pmt. If the attempt succeeds we cancel the transaction. If it fails we log the failure. Even for failure we keep the payment_method_token, we don't reset it to None/NULL. It's useful for loading the previous (bad) credit card info from Samurai in order to prepopulate the form. """ typecheck(pmt, unicode, participant_id, unicode) transaction = Processor.authorize(pmt, '1.00', custom=participant_id) if transaction.errors: last_bill_result = json.dumps(transaction.errors) out = dict(transaction.errors) else: transaction.reverse() last_bill_result = '' out = {} STANDING = """\ UPDATE participants SET payment_method_token=%s , last_bill_result=%s WHERE id=%s """ db.execute(STANDING, (pmt, last_bill_result, participant_id)) return out
def authorize(participant_id, pmt): """Given two unicodes, return a dict. This function attempts to authorize the credit card details referenced by pmt. If the attempt succeeds we cancel the transaction. If it fails we log the failure. Even for failure we keep the payment_method_token, we don't reset it to None/NULL. It's useful for loading the previous (bad) credit card info from Samurai in order to prepopulate the form. """ typecheck(pmt, unicode, participant_id, unicode) transaction = Processor.authorize(pmt, '1.00', custom=participant_id) if transaction.errors: last_bill_result = json.dumps(transaction.errors) out = dict(transaction.errors) else: transaction.reverse() last_bill_result = '' out = {} STANDING = """\ UPDATE participants SET payment_method_token=%s , last_bill_result=%s WHERE id=%s """ db.execute(STANDING, (pmt, last_bill_result, participant_id)) return out
def test_authorize_should_return_processor_avs_result_code_Y(self): token = test_helper.default_payment_method({'address_1':'1000 1st Av', 'address_2':'', 'zip':'10101'}).payment_method_token purchase = Processor.authorize(token, 1.00, billing_reference=self.rand) self.assertTrue(purchase.success) self.assertEqual(purchase.processor_response['avs_result_code'], 'Y')
def test_purchase_should_return_processor_transaction_declined(self): token = self.pm.payment_method_token purchase = Processor.purchase(token, 1.02, billing_reference=self.rand) self.assertFalse(purchase.success) err = {'context': 'processor.transaction', 'key': 'declined', 'subclass': 'error'} self.assertIn(err, purchase.error_messages) self.assertIn('The card was declined.' , purchase.errors['processor.transaction'])
def test_purchase_should_return_input_amount_invalid(self): token = self.pm.payment_method_token purchase = Processor.purchase(token, 1.10, billing_reference=self.rand) self.assertFalse(purchase.success) err = {'context': 'input.amount', 'key': 'invalid', 'subclass': 'error'} self.assertIn(err, purchase.error_messages) self.assertIn('The transaction amount was invalid.', purchase.errors['input.amount'])
def test_should_return_processor_avs_result_code_N(self): token = test_helper.default_payment_method({'address_1':'123 Main St', 'address_2':'', 'zip':'60610'}).payment_method_token purchase = Processor.purchase(token, 1.00, billing_reference=self.rand) self.assertTrue(purchase.success) self.assertEqual(purchase.processor_response['avs_result_code'], 'N')
def test_should_return_processor_avs_result_code_N(self): token = test_helper.default_payment_method({ 'address_1': '123 Main St', 'address_2': '', 'zip': '60610' }).payment_method_token purchase = Processor.purchase(token, 1.00, billing_reference=self.rand) self.assertTrue(purchase.success) self.assertEqual(purchase.processor_response['avs_result_code'], 'N')
def test_authorize_capture_failed(self): auth = Processor.authorize(self.pm.payment_method_token, 10.0) trans = auth.capture(10.02) self.assertFalse(trans.success) errors = [{ 'context': 'processor.transaction', 'key': 'declined', 'subclass': 'error' }] self.assertEquals(trans.errors, errors)
def purchase(request): token = request.GET.get('payment_method_token', None) trans = Processor.purchase(token, 10) if trans.errors: errors = parse_error(trans.errors) for err in errors: messages.error(request, err, fail_silently=True) return redirect('/transparent_redirect/payment_form') else: messages.success(request, 'Purchase Successful.', fail_silently=True) return render_to_response('/transparent_redirect/receipt.html')
def test_authorize_should_return_processor_avs_result_code_Y(self): token = test_helper.default_payment_method({ 'address_1': '1000 1st Av', 'address_2': '', 'zip': '10101' }).payment_method_token purchase = Processor.authorize(token, 1.00, billing_reference=self.rand) self.assertTrue(purchase.success) self.assertEqual(purchase.processor_response['avs_result_code'], 'Y')
def authorize(self, money, credit_card, options=None): if not self.validate_card(credit_card): raise InvalidCard("Invalid Card") try: from samurai.payment_method import PaymentMethod from samurai.processor import Processor pm = PaymentMethod.create(credit_card.number, credit_card.verification_value, credit_card.month, credit_card.year) payment_method_token = pm.payment_method_token response = Processor.authorize(payment_method_token, money) except Exception, error: return {'status': 'FAILURE', 'response': error}
def authorize(self, money, credit_card, options=None): payment_method_token = credit_card if isinstance(credit_card, CreditCard): if not self.validate_card(credit_card): raise InvalidCard("Invalid Card") pm = PaymentMethod.create(credit_card.number, credit_card.verification_value, credit_card.month, credit_card.year) payment_method_token = pm.payment_method_token response = Processor.authorize(payment_method_token, money) if response.errors: return {'status': 'FAILURE', 'response': response} return {'status': 'SUCCESS', 'response': response}
def test_purchase_should_return_processor_transaction_declined(self): token = self.pm.payment_method_token purchase = Processor.purchase(token, 1.02, billing_reference=self.rand) self.assertFalse(purchase.success) err = { 'context': 'processor.transaction', 'key': 'declined', 'subclass': 'error' } self.assertIn(err, purchase.error_messages) self.assertIn('The card was declined.', purchase.errors['processor.transaction'])
def test_purchase_should_return_input_amount_invalid(self): token = self.pm.payment_method_token purchase = Processor.purchase(token, 1.10, billing_reference=self.rand) self.assertFalse(purchase.success) err = { 'context': 'input.amount', 'key': 'invalid', 'subclass': 'error' } self.assertIn(err, purchase.error_messages) self.assertIn('The transaction amount was invalid.', purchase.errors['input.amount'])
def test_capture_should_return_processor_transaction_declined(self): auth = Processor.authorize(self.pm.payment_method_token, 100.00) capture = auth.capture(100.02) self.assertFalse(capture.success) err = { 'context': 'processor.transaction', 'key': 'declined', 'subclass': 'error' } self.assertIn(err, capture.error_messages) self.assertIn('The card was declined.', capture.errors['processor.transaction'])
def test_capture_should_return_processor_transaction_invalid_with_declined_auth( self): auth = Processor.authorize(self.pm.payment_method_token, 100.02) # declined auth capture = auth.capture() self.assertFalse(capture.success) err = { 'context': 'processor.transaction', 'key': 'not_allowed', 'subclass': 'error' } self.assertIn(err, capture.error_messages)
def purchase(request): if request.method == 'POST': token = request.POST.get('payment_method_token', None) trans = Processor.purchase(token, 10) if trans.errors: success=False else: success=True return_data = simplejson.dumps({'transaction':{'success':success}}) return HttpResponse(return_data) else: return redirect('/samurai_js/payment_form')
def auth(self, amount, credit_card=None, billing_info=None, shipping_info=None): # set up the card for charging, obviously card_token = self.charge_setup(credit_card, billing_info) # start the timer start = time.time() # send it over for processing response = Processor.authorize(card_token, amount) # measure time end = time.time() # done timing it response_time = '%0.2f' % (end-start) # return parsed response return self.parse(response, response_time)
def test_capture_should_return_input_amount_invalid(self): auth = Processor.authorize(self.pm.payment_method_token, 100.00) capture = auth.capture(100.10) self.assertFalse(capture.success) err = { 'context': 'input.amount', 'key': 'invalid', 'subclass': 'error' } self.assertIn(err, capture.error_messages) self.assertIn('The transaction amount was invalid.', capture.errors['input.amount'])
def auth(self, amount, credit_card=None, billing_info=None, shipping_info=None): # set up the card for charging, obviously card_token = self.charge_setup(credit_card, billing_info) # start the timer start = time.time() # send it over for processing response = Processor.authorize(card_token, amount) # measure time end = time.time() # done timing it response_time = '%0.2f' % (end-start) # return parsed response return self.parse(response, response_time)
def purchase(self, money, credit_card): # Cases where token is directly sent for e.g. from Samurai.js payment_method_token = credit_card if isinstance(credit_card, CreditCard): if not self.validate_card(credit_card): raise InvalidCard("Invalid Card") pm = PaymentMethod.create(credit_card.number, credit_card.verification_value, credit_card.month, credit_card.year) payment_method_token = pm.payment_method_token response = Processor.purchase(payment_method_token, money) if response.errors: return {'status': 'FAILURE', 'response': response} return {'status': 'SUCCESS', 'response': response}
def authorize(self, money, credit_card, options=None): if not self.validate_card(credit_card): raise InvalidCard("Invalid Card") try: from samurai.payment_method import PaymentMethod from samurai.processor import Processor pm = PaymentMethod.create(credit_card.number, credit_card.verification_value, credit_card.month, credit_card.year) payment_method_token = pm.payment_method_token response = Processor.authorize(payment_method_token, money) except Exception, error: return {'status': 'FAILURE', 'response': error}
def test_purchase_should_be_successful(self): options = {'description':'description', 'descriptor_name':'descriptor_name', 'descriptor_phone':'descriptor_phone', 'custom':'custom', 'billing_reference':'ABC123%s' % self.rand, 'customer_reference':'Customer (123)'} token = self.pm.payment_method_token purchase = Processor.purchase(token, 10.0, None, **options) self.assertTrue(purchase.success) self.assertEquals(purchase.error_messages, []) self.assertEqual(purchase.description, 'description') self.assertEqual(purchase.descriptor_name, 'descriptor_name') self.assertEqual(purchase.descriptor_phone, 'descriptor_phone') self.assertEqual(purchase.custom, 'custom') self.assertEqual(purchase.billing_reference, 'ABC123%s' % self.rand) self.assertEqual(purchase.customer_reference, 'Customer (123)')
def purchase(request): if request.method == "POST": data = request.POST token = PaymentMethod.create(data.get('card_number'), data.get('cvv'), data.get('expiry_month'), data.get('expiry_year'), first_name=data.get('first_name'), last_name=data.get('last_name')) trans = Processor.purchase(token.payment_method_token, 10) if trans.errors: errors = parse_error(trans.errors) for err in errors: messages.error(request, err, fail_silently=True) return redirect('/server_to_server/payment_form') else: messages.success(request, 'Purchase Successful.', fail_silently=True) return render_to_response('/server_to_server/receipt.html') else: return redirect('/server_to_server/payment_form')
def test_purchase_should_return_invalid_sandbox_card_error(self): data = { 'custom' : '', 'first_name' : '', 'last_name' : '', 'address_1' : '', 'address_2' : '', 'city' : '', 'state' : '', 'zip' : '10101', 'card_number' : '5256486068715680', 'cvv' : '111', 'expiry_month' : '05', 'expiry_year' : '2014', } token = test_helper.default_payment_method(data).payment_method_token purchase = Processor.purchase(token, 1.00) self.assertIn({'context': 'system.general', 'key': 'default', 'subclass': 'error', 'text': 'Invalid Sandbox Card Number. For more information, see: https://samurai.feefighters.com/developers/sandbox'}, purchase.error_messages)
def test_purchase_should_be_successful(self): options = { 'description': 'description', 'descriptor_name': 'descriptor_name', 'descriptor_phone': 'descriptor_phone', 'custom': 'custom', 'billing_reference': 'ABC123%s' % self.rand, 'customer_reference': 'Customer (123)' } token = self.pm.payment_method_token purchase = Processor.purchase(token, 10.0, None, **options) self.assertTrue(purchase.success) self.assertEquals(purchase.error_messages, []) self.assertEqual(purchase.description, 'description') self.assertEqual(purchase.descriptor_name, 'descriptor_name') self.assertEqual(purchase.descriptor_phone, 'descriptor_phone') self.assertEqual(purchase.custom, 'custom') self.assertEqual(purchase.billing_reference, 'ABC123%s' % self.rand) self.assertEqual(purchase.customer_reference, 'Customer (123)')
def test_purchase_should_return_payment_method_errors_on_blank_pm(self): data = { 'custom' : '', 'first_name' : '', 'last_name' : '', 'address_1' : '', 'address_2' : '', 'city' : '', 'state' : '', 'zip' : '', 'card_number' : '', 'cvv' : '', 'expiry_month' : '05', 'expiry_year' : '2014', } token = test_helper.default_payment_method(data).payment_method_token purchase = Processor.purchase(token, 1.00) self.assertFalse(purchase.success) self.assertIn({'context': 'input.card_number', 'key': 'not_numeric', 'subclass': 'error'}, purchase.error_messages) self.assertIn({'context': 'input.card_number', 'key': 'too_short', 'subclass': 'error'}, purchase.error_messages) self.assertIn({'context': 'input.card_number', 'key': 'is_blank', 'subclass': 'error'}, purchase.error_messages)
def test_purchase_should_return_payment_method_errors_on_blank_pm(self): data = { 'custom': '', 'first_name': '', 'last_name': '', 'address_1': '', 'address_2': '', 'city': '', 'state': '', 'zip': '', 'card_number': '', 'cvv': '', 'expiry_month': '05', 'expiry_year': '2014', } token = test_helper.default_payment_method(data).payment_method_token purchase = Processor.purchase(token, 1.00) self.assertFalse(purchase.success) self.assertIn( { 'context': 'input.card_number', 'key': 'not_numeric', 'subclass': 'error' }, purchase.error_messages) self.assertIn( { 'context': 'input.card_number', 'key': 'too_short', 'subclass': 'error' }, purchase.error_messages) self.assertIn( { 'context': 'input.card_number', 'key': 'is_blank', 'subclass': 'error' }, purchase.error_messages)
def charge(participant_id, pmt, amount): """Given two unicodes and a Decimal, return a boolean indicating success. This is the only place where we actually charge credit cards. Amount should be the nominal amount. We compute Gittip's fee in this function and add it to amount. """ typecheck( pmt, (unicode, None) , participant_id, unicode , amount, decimal.Decimal ) if pmt is None: STATS = """\ UPDATE paydays SET npmt_missing = npmt_missing + 1 WHERE ts_end='1970-01-01T00:00:00+00'::timestamptz RETURNING id """ assert_one_payday(db.fetchone(STATS)) return False # We have a purported payment method token. Try to use it. # ======================================================== charge_amount = (amount + FEE[0]) * FEE[1] charge_amount = charge_amount.quantize(FEE[0], rounding=decimal.ROUND_UP) fee = charge_amount - amount log("Charging %s $%s + $%s fee = $%s." % (participant_id, amount, fee, charge_amount)) transaction = Processor.purchase(pmt, charge_amount, custom=participant_id) # XXX If the power goes out at this point then Postgres will be out of sync # with Samurai. We'll have to resolve that manually be reviewing the # Samurai transaction log and modifying Postgres accordingly. with db.get_connection() as conn: cur = conn.cursor() if transaction.errors: last_bill_result = json.dumps(transaction.errors) amount = decimal.Decimal('0.00') STATS = """\ UPDATE paydays SET npmt_failing = npmt_failing + 1 WHERE ts_end='1970-01-01T00:00:00+00'::timestamptz RETURNING id """ cur.execute(STATS) assert_one_payday(cur.fetchone()) else: last_bill_result = '' EXCHANGE = """\ INSERT INTO exchanges (amount, fee, participant_id) VALUES (%s, %s, %s) """ cur.execute(EXCHANGE, (amount, fee, participant_id)) STATS = """\ UPDATE paydays SET nexchanges = nexchanges + 1 , exchange_volume = exchange_volume + %s , exchange_fees_volume = exchange_fees_volume + %s WHERE ts_end='1970-01-01T00:00:00+00'::timestamptz RETURNING id """ cur.execute(STATS, (charge_amount, fee)) assert_one_payday(cur.fetchone()) # Update the participant's balance. # ================================= # Credit card charges go immediately to balance, not to pending. RESULT = """\ UPDATE participants SET last_bill_result=%s , balance=(balance + %s) WHERE id=%s """ cur.execute(RESULT, (last_bill_result, amount, participant_id)) conn.commit() return not bool(last_bill_result) # True indicates success
def test_capture_should_return_processor_transaction_invalid_with_declined_auth(self): auth = Processor.authorize(self.pm.payment_method_token, 100.02) # declined auth capture = auth.capture() self.assertFalse(capture.success) err = {'context': 'processor.transaction', 'key': 'not_allowed', 'subclass': 'error'} self.assertIn(err, capture.error_messages)
def test_purchase(self): token = self.pm.payment_method_token trans = Processor.purchase(token, 10.0) self.assertTrue(trans.success) self.assertEquals(trans.errors, [])
def test_authorize_failure(self): token = self.pm.payment_method_token trans = Processor.authorize(token, 10.02) errors = [{'context': 'processor.transaction', 'key': 'declined', 'subclass': 'error'}] self.assertEquals(trans.errors, errors)
def setUp(self): self.pm = test_helper.default_payment_method() self.rand = randint(100, 999) self.auth = Processor.authorize(self.pm.payment_method_token, 100.0) self.purchase = Processor.purchase(self.pm.payment_method_token, 100.0)
def test_authorize_void(self): auth = Processor.authorize(self.pm.payment_method_token, 10.0) trans = auth.void() self.assertTrue(trans.success) self.assertEquals(trans.errors, [])
def charge(participant_id, pmt, amount): """Given two unicodes and a Decimal, return a boolean indicating success. This is the only place where we actually charge credit cards. Amount should be the nominal amount. We compute Gittip's fee in this function and add it to amount. """ typecheck(pmt, (unicode, None), participant_id, unicode, amount, decimal.Decimal) if pmt is None: STATS = """\ UPDATE paydays SET npmt_missing = npmt_missing + 1 WHERE ts_end='1970-01-01T00:00:00+00'::timestamptz RETURNING id """ assert_one_payday(db.fetchone(STATS)) return False # We have a purported payment method token. Try to use it. # ======================================================== charge_amount = (amount + FEE[0]) * FEE[1] charge_amount = charge_amount.quantize(FEE[0], rounding=decimal.ROUND_UP) fee = charge_amount - amount log("Charging %s $%s + $%s fee = $%s." % (participant_id, amount, fee, charge_amount)) transaction = Processor.purchase(pmt, charge_amount, custom=participant_id) # XXX If the power goes out at this point then Postgres will be out of sync # with Samurai. We'll have to resolve that manually be reviewing the # Samurai transaction log and modifying Postgres accordingly. with db.get_connection() as conn: cur = conn.cursor() if transaction.errors: last_bill_result = json.dumps(transaction.errors) amount = decimal.Decimal('0.00') STATS = """\ UPDATE paydays SET npmt_failing = npmt_failing + 1 WHERE ts_end='1970-01-01T00:00:00+00'::timestamptz RETURNING id """ cur.execute(STATS) assert_one_payday(cur.fetchone()) else: last_bill_result = '' EXCHANGE = """\ INSERT INTO exchanges (amount, fee, participant_id) VALUES (%s, %s, %s) """ cur.execute(EXCHANGE, (amount, fee, participant_id)) STATS = """\ UPDATE paydays SET nexchanges = nexchanges + 1 , exchange_volume = exchange_volume + %s , exchange_fees_volume = exchange_fees_volume + %s WHERE ts_end='1970-01-01T00:00:00+00'::timestamptz RETURNING id """ cur.execute(STATS, (charge_amount, fee)) assert_one_payday(cur.fetchone()) # Update the participant's balance. # ================================= # Credit card charges go immediately to balance, not to pending. RESULT = """\ UPDATE participants SET last_bill_result=%s , balance=(balance + %s) WHERE id=%s """ cur.execute(RESULT, (last_bill_result, amount, participant_id)) conn.commit() return not bool(last_bill_result) # True indicates success
def test_authorize_capture(self): auth = Processor.authorize(self.pm.payment_method_token, 10.0) trans = auth.capture(10.0) self.assertTrue(trans.success) self.assertEquals(trans.errors, [])
def test_purchase_partial_reverse(self): purchase = Processor.purchase(self.pm.payment_method_token, 10.0) trans = purchase.reverse(5.0) self.assertTrue(trans.success) self.assertEquals(trans.errors, []) self.assertEquals(trans.amount, '5.0')
def test_should_return_a_processor(self): processor = Processor('abc123') self.assertNotEqual(processor, None) self.assertEqual(processor.processor_token, 'abc123')
def test_authorize_capture_reverse(self): auth = Processor.authorize(self.pm.payment_method_token, 10.0) capture = auth.capture(10.0) trans = capture.reverse() self.assertTrue(trans.success) self.assertEquals(trans.errors, [])
def setUp(self): self.pm = test_helper.default_payment_method() self.rand = randint(100, 999) self.auth = Processor.authorize(self.pm.payment_method_token, 100.0) self.purchase = Processor.purchase(self.pm.payment_method_token, 100.0)
def test_authorize_capture_failed(self): auth = Processor.authorize(self.pm.payment_method_token, 10.0) trans = auth.capture(10.02) self.assertFalse(trans.success) errors = [{'context': 'processor.transaction', 'key': 'declined', 'subclass': 'error'}] self.assertEquals(trans.errors, errors)
def test_purchase_partial_reverse(self): purchase = Processor.purchase(self.pm.payment_method_token, 10.0) trans = purchase.reverse(5.0) self.assertTrue(trans.success) self.assertEquals(trans.errors, []) self.assertEquals(trans.amount, '5.0')
def test_authorize_partial_capture(self): auth = Processor.authorize(self.pm.payment_method_token, 10.0) trans = auth.capture(8.0) self.assertTrue(trans.success) self.assertEquals(trans.amount, '8.0') self.assertEquals(trans.errors, [])