def process_payment(order_id, pg_paymentid): order = Order.query.get(order_id) order_amounts = order.get_amounts(LINE_ITEM_STATUS.PURCHASE_ORDER) online_payment = OnlinePayment.query.filter_by( pg_paymentid=pg_paymentid, order=order ).first() if online_payment is None: online_payment = OnlinePayment(pg_paymentid=pg_paymentid, order=order) rp_resp = razorpay.capture_payment( online_payment.pg_paymentid, order_amounts.final_amount ) if rp_resp.status_code == 200: online_payment.confirm() db.session.add(online_payment) # Only INR is supported as of now transaction = PaymentTransaction( order=order, online_payment=online_payment, amount=order_amounts.final_amount, currency=CURRENCY.INR, ) db.session.add(transaction) order.confirm_sale() db.session.add(order) invoice_organization = ( order.organization.invoicer if order.organization.invoicer else order.organization ) invoice = Invoice(order=order, organization=invoice_organization) db.session.add(invoice) db.session.commit() for line_item in order.line_items: line_item.confirm() db.session.add(line_item) if line_item.discount_coupon: line_item.discount_coupon.update_used_count() db.session.add(line_item.discount_coupon) db.session.commit() with app.test_request_context(): send_receipt_mail.queue(order.id) return make_response(jsonify(message="Payment verified"), 201) else: online_payment.fail() db.session.add(online_payment) db.session.commit() raise PaymentGatewayError( "Online payment failed for order - {order} with the following details - {msg}".format( order=order.id, msg=rp_resp.content ), 424, 'Your payment failed. Please try again or contact us at {email}.'.format( email=order.organization.contact_email ), )
def test_cancel_line_item_in_order(self): original_quantity = 2 order_item = Item.query.filter_by(name='t-shirt').first() total_amount = order_item.current_price().amount * original_quantity data = { 'line_items': [{'item_id': unicode(order_item.id), 'quantity': original_quantity}], 'buyer': { 'fullname': 'Testing', 'phone': '9814141414', 'email': '*****@*****.**', } } ic = ItemCollection.query.first() # make a purchase order resp = self.client.post('/ic/{ic}/order'.format(ic=ic.id), data=json.dumps(data), content_type='application/json', headers=[('X-Requested-With', 'XMLHttpRequest'), ('Origin', app.config['BASE_URL'])]) self.assertEquals(resp.status_code, 201) resp_json = json.loads(resp.data)['result'] self.assertEquals(resp_json['final_amount'], total_amount) order = Order.query.get(resp_json['order_id']) # Create fake payment and transaction objects online_payment = OnlinePayment(pg_paymentid='pg_testpayment', order=order) online_payment.confirm() order_amounts = order.get_amounts(LINE_ITEM_STATUS.PURCHASE_ORDER) transaction = PaymentTransaction(order=order, online_payment=online_payment, amount=order_amounts.final_amount, currency=CURRENCY.INR) db.session.add(transaction) order.confirm_sale() db.session.commit() refund_amount = total_amount - 1 razorpay.refund_payment = MagicMock(return_value=MockResponse(response_data={'id': buid()})) pre_refund_transactions_count = order.refund_transactions.count() formdata = { 'amount': refund_amount, 'internal_note': 'internal reference', 'refund_description': 'receipt description', 'note_to_user': '******' } refund_form = OrderRefundForm(data=formdata, parent=order, meta={'csrf': False}) partial_refund_args = { 'order': order, 'form': refund_form, 'request_method': 'POST' } with app.request_context(self.post_env): process_partial_refund_for_order(partial_refund_args) self.assertEquals(pre_refund_transactions_count + 1, order.refund_transactions.count()) first_line_item = order.line_items[0] # Mock Razorpay's API razorpay.refund_payment = MagicMock(return_value=MockResponse(response_data={'id': buid()})) process_line_item_cancellation(first_line_item) self.assertEquals(first_line_item.status, LINE_ITEM_STATUS.CANCELLED) expected_refund_amount = total_amount - refund_amount refund_transaction1 = PaymentTransaction.query.filter_by(order=order, transaction_type=TRANSACTION_TYPE.REFUND).order_by(PaymentTransaction.created_at.desc()).first() self.assertEquals(refund_transaction1.amount, expected_refund_amount)
def test_partial_refund_in_order(self): original_quantity = 5 discounted_item = Item.query.filter_by(name='t-shirt').first() total_amount = discounted_item.current_price().amount * original_quantity data = { 'line_items': [{'item_id': unicode(discounted_item.id), 'quantity': original_quantity}], 'buyer': { 'fullname': 'Testing', 'phone': '9814141414', 'email': '*****@*****.**', } } ic = ItemCollection.query.first() # make a purchase order resp = self.client.post('/ic/{ic}/order'.format(ic=ic.id), data=json.dumps(data), content_type='application/json', headers=[('X-Requested-With', 'XMLHttpRequest'), ('Origin', app.config['BASE_URL'])]) self.assertEquals(resp.status_code, 201) resp_data = json.loads(resp.data)['result'] self.assertEquals(resp_data['final_amount'], (total_amount - 5 * total_amount / decimal.Decimal(100))) order = Order.query.get(resp_data['order_id']) # Create fake payment and transaction objects online_payment = OnlinePayment(pg_paymentid='pg_testpayment', order=order) online_payment.confirm() order_amounts = order.get_amounts(LINE_ITEM_STATUS.PURCHASE_ORDER) transaction = PaymentTransaction(order=order, online_payment=online_payment, amount=order_amounts.final_amount, currency=CURRENCY.INR) db.session.add(transaction) order.confirm_sale() db.session.commit() # Mock Razorpay's API razorpay.refund_payment = MagicMock(return_value=MockResponse(response_data={'id': buid()})) valid_refund_amount = 500 formdata = { 'amount': valid_refund_amount, 'internal_note': 'internal reference', 'note_to_user': '******', 'refund_description': 'test refund' } refund_form = OrderRefundForm(data=formdata, parent=order, meta={'csrf': False}) partial_refund_args = { 'order': order, 'form': refund_form, 'request_method': 'POST' } with app.request_context(self.post_env): process_partial_refund_for_order(partial_refund_args) refund_transactions = order.transactions.filter_by(transaction_type=TRANSACTION_TYPE.REFUND).all() self.assertIsInstance(refund_transactions[0].refunded_at, datetime.datetime) self.assertEquals(refund_transactions[0].amount, decimal.Decimal(valid_refund_amount)) self.assertEquals(refund_transactions[0].internal_note, formdata['internal_note']) self.assertEquals(refund_transactions[0].note_to_user, formdata['note_to_user']) self.assertEquals(refund_transactions[0].refund_description, formdata['refund_description']) invalid_refund_amount = 100000000 formdata = { 'amount': invalid_refund_amount, } refund_form = OrderRefundForm(data=formdata, parent=order, meta={'csrf': False}) partial_refund_args = { 'order': order, 'form': refund_form, 'request_method': 'POST' } with app.request_context(self.post_env): resp = process_partial_refund_for_order(partial_refund_args) self.assertEquals(resp.status_code, 403) refund_transactions = order.transactions.filter_by(transaction_type=TRANSACTION_TYPE.REFUND).all() self.assertEquals(refund_transactions[0].amount, decimal.Decimal(valid_refund_amount)) resp = self.make_free_order() self.assertEquals(resp.status_code, 201) resp_data = json.loads(resp.data)['result'] order = Order.query.get(resp_data.get('order_id')) invalid_refund_amount = 100000000 formdata = { 'amount': invalid_refund_amount, } refund_form = OrderRefundForm(data=formdata, parent=order, meta={'csrf': False}) partial_refund_args = { 'order': order, 'form': refund_form, 'request_method': 'POST' } with app.request_context(self.post_env): refund_resp = process_partial_refund_for_order(partial_refund_args) self.assertEquals(refund_resp.status_code, 403)
def test_cancel_line_item_in_bulk_order(self): original_quantity = 5 discounted_item = Item.query.filter_by(name='t-shirt').first() total_amount = discounted_item.current_price().amount * original_quantity data = { 'line_items': [{'item_id': unicode(discounted_item.id), 'quantity': original_quantity}], 'buyer': { 'fullname': 'Testing', 'phone': '9814141414', 'email': '*****@*****.**', } } ic = ItemCollection.query.first() # make a purchase order resp = self.client.post('/ic/{ic}/order'.format(ic=ic.id), data=json.dumps(data), content_type='application/json', headers=[('X-Requested-With', 'XMLHttpRequest'), ('Origin', app.config['BASE_URL'])]) self.assertEquals(resp.status_code, 201) resp_json = json.loads(resp.data)['result'] self.assertEquals(resp_json['final_amount'], (total_amount - 5 * total_amount / decimal.Decimal(100))) order = Order.query.get(resp_json['order_id']) # Create fake payment and transaction objects online_payment = OnlinePayment(pg_paymentid='pg_testpayment', order=order) online_payment.confirm() order_amounts = order.get_amounts(LINE_ITEM_STATUS.PURCHASE_ORDER) transaction = PaymentTransaction(order=order, online_payment=online_payment, amount=order_amounts.final_amount, currency=CURRENCY.INR) db.session.add(transaction) order.confirm_sale() db.session.commit() first_line_item = order.line_items[0] to_be_void_line_items = order.line_items[1:] precancellation_order_amount = order.net_amount # Mock Razorpay's API razorpay.refund_payment = MagicMock(return_value=MockResponse(response_data={'id': buid()})) process_line_item_cancellation(first_line_item) self.assertEquals(first_line_item.status, LINE_ITEM_STATUS.CANCELLED) for void_line_item in to_be_void_line_items: self.assertEquals(void_line_item.status, LINE_ITEM_STATUS.VOID) expected_refund_amount = precancellation_order_amount - order.get_amounts(LINE_ITEM_STATUS.CONFIRMED).final_amount refund_transaction1 = PaymentTransaction.query.filter_by(order=order, transaction_type=TRANSACTION_TYPE.REFUND).first() self.assertEquals(refund_transaction1.amount, expected_refund_amount) second_line_item = order.confirmed_line_items[0] razorpay.refund_payment = MagicMock(return_value=MockResponse(response_data={'id': buid()})) process_line_item_cancellation(second_line_item) self.assertEquals(second_line_item.status, LINE_ITEM_STATUS.CANCELLED) refund_transaction2 = PaymentTransaction.query.filter_by(order=order, transaction_type=TRANSACTION_TYPE.REFUND).order_by(PaymentTransaction.created_at.desc()).first() self.assertEquals(refund_transaction2.amount, second_line_item.final_amount) # test failed cancellation third_line_item = order.confirmed_line_items[0] razorpay.refund_payment = MagicMock( return_value=MockResponse( response_data={ "error": { "code": "BAD_REQUEST_ERROR", "description": "The amount is invalid", "field": "amount" } }, status_code=400 )) self.assertRaises(PaymentGatewayError, lambda: process_line_item_cancellation(third_line_item)) # refund the remaining amount paid, and attempt to cancel a line item # this should cancel the line item without resulting in a new refund transaction refund_amount = order.net_amount refund_dict = {'id': buid(), 'amount': refund_amount, 'internal_note': 'internal reference', 'note_to_user': '******'} razorpay.refund_payment = MagicMock(return_value=MockResponse(response_data=refund_dict)) formdata = { 'amount': refund_amount, 'internal_note': 'internal reference', 'refund_description': 'receipt description', 'note_to_user': '******' } refund_form = OrderRefundForm(data=formdata, parent=order, meta={'csrf': False}) partial_refund_args = { 'order': order, 'form': refund_form, 'request_method': 'POST' } with app.request_context(self.post_env): process_partial_refund_for_order(partial_refund_args) third_line_item = order.confirmed_line_items[0] pre_cancellation_transactions_count = order.refund_transactions.count() cancelled_refund_amount = process_line_item_cancellation(third_line_item) self.assertEquals(cancelled_refund_amount, decimal.Decimal(0)) self.assertEquals(pre_cancellation_transactions_count, order.refund_transactions.count()) # test free line item cancellation free_order_resp = self.make_free_order() free_order_resp_data = json.loads(free_order_resp.data)['result'] free_order = Order.query.get(free_order_resp_data.get('order_id')) free_line_item = free_order.line_items[0] process_line_item_cancellation(free_line_item) self.assertEquals(free_line_item.status, LINE_ITEM_STATUS.CANCELLED) self.assertEquals(free_order.transactions.count(), 0)
def test_partial_refund_in_order(self): original_quantity = 5 discounted_item = Item.query.filter_by(name='t-shirt').first() total_amount = discounted_item.current_price( ).amount * original_quantity data = { 'line_items': [{ 'item_id': str(discounted_item.id), 'quantity': original_quantity }], 'buyer': { 'fullname': 'Testing', 'phone': '9814141414', 'email': '*****@*****.**', } } ic = ItemCollection.query.first() # make a purchase order resp = self.client.post('/ic/{ic}/order'.format(ic=ic.id), data=json.dumps(data), content_type='application/json', headers=[('X-Requested-With', 'XMLHttpRequest'), ('Origin', app.config['BASE_URL'])]) self.assertEqual(resp.status_code, 201) resp_data = json.loads(resp.data)['result'] self.assertEqual( resp_data['final_amount'], (total_amount - 5 * total_amount / decimal.Decimal(100))) order = Order.query.get(resp_data['order_id']) # Create fake payment and transaction objects online_payment = OnlinePayment(pg_paymentid='pg_testpayment', order=order) online_payment.confirm() order_amounts = order.get_amounts(LINE_ITEM_STATUS.PURCHASE_ORDER) transaction = PaymentTransaction(order=order, online_payment=online_payment, amount=order_amounts.final_amount, currency=CURRENCY.INR) db.session.add(transaction) order.confirm_sale() db.session.commit() # Mock Razorpay's API razorpay.refund_payment = MagicMock(return_value=MockResponse( response_data={'id': buid()})) valid_refund_amount = 500 formdata = { 'amount': valid_refund_amount, 'internal_note': 'internal reference', 'note_to_user': '******', 'refund_description': 'test refund' } refund_form = OrderRefundForm(data=formdata, parent=order, meta={'csrf': False}) partial_refund_args = { 'order': order, 'form': refund_form, 'request_method': 'POST' } with app.request_context(self.post_env): process_partial_refund_for_order(partial_refund_args) refund_transactions = order.transactions.filter_by( transaction_type=TRANSACTION_TYPE.REFUND).all() self.assertIsInstance(refund_transactions[0].refunded_at, datetime.datetime) self.assertEqual(refund_transactions[0].amount, decimal.Decimal(valid_refund_amount)) self.assertEqual(refund_transactions[0].internal_note, formdata['internal_note']) self.assertEqual(refund_transactions[0].note_to_user, formdata['note_to_user']) self.assertEqual(refund_transactions[0].refund_description, formdata['refund_description']) invalid_refund_amount = 100000000 formdata = { 'amount': invalid_refund_amount, } refund_form = OrderRefundForm(data=formdata, parent=order, meta={'csrf': False}) partial_refund_args = { 'order': order, 'form': refund_form, 'request_method': 'POST' } with app.request_context(self.post_env): resp = process_partial_refund_for_order(partial_refund_args) self.assertEqual(resp.status_code, 403) refund_transactions = order.transactions.filter_by( transaction_type=TRANSACTION_TYPE.REFUND).all() self.assertEqual(refund_transactions[0].amount, decimal.Decimal(valid_refund_amount)) resp = self.make_free_order() self.assertEqual(resp.status_code, 201) resp_data = json.loads(resp.data)['result'] order = Order.query.get(resp_data.get('order_id')) invalid_refund_amount = 100000000 formdata = { 'amount': invalid_refund_amount, } refund_form = OrderRefundForm(data=formdata, parent=order, meta={'csrf': False}) partial_refund_args = { 'order': order, 'form': refund_form, 'request_method': 'POST' } with app.request_context(self.post_env): refund_resp = process_partial_refund_for_order(partial_refund_args) self.assertEqual(refund_resp.status_code, 403)
def test_cancel_line_item_in_bulk_order(self): original_quantity = 5 discounted_item = Item.query.filter_by(name='t-shirt').first() total_amount = discounted_item.current_price( ).amount * original_quantity data = { 'line_items': [{ 'item_id': str(discounted_item.id), 'quantity': original_quantity }], 'buyer': { 'fullname': 'Testing', 'phone': '9814141414', 'email': '*****@*****.**', } } ic = ItemCollection.query.first() # make a purchase order resp = self.client.post('/ic/{ic}/order'.format(ic=ic.id), data=json.dumps(data), content_type='application/json', headers=[('X-Requested-With', 'XMLHttpRequest'), ('Origin', app.config['BASE_URL'])]) self.assertEqual(resp.status_code, 201) resp_json = json.loads(resp.data)['result'] self.assertEqual( resp_json['final_amount'], (total_amount - 5 * total_amount / decimal.Decimal(100))) order = Order.query.get(resp_json['order_id']) # Create fake payment and transaction objects online_payment = OnlinePayment(pg_paymentid='pg_testpayment', order=order) online_payment.confirm() order_amounts = order.get_amounts(LINE_ITEM_STATUS.PURCHASE_ORDER) transaction = PaymentTransaction(order=order, online_payment=online_payment, amount=order_amounts.final_amount, currency=CURRENCY.INR) db.session.add(transaction) order.confirm_sale() db.session.commit() first_line_item = order.line_items[0] to_be_void_line_items = order.line_items[1:] precancellation_order_amount = order.net_amount # Mock Razorpay's API razorpay.refund_payment = MagicMock(return_value=MockResponse( response_data={'id': buid()})) process_line_item_cancellation(first_line_item) self.assertEqual(first_line_item.status, LINE_ITEM_STATUS.CANCELLED) for void_line_item in to_be_void_line_items: self.assertEqual(void_line_item.status, LINE_ITEM_STATUS.VOID) expected_refund_amount = precancellation_order_amount - order.get_amounts( LINE_ITEM_STATUS.CONFIRMED).final_amount refund_transaction1 = PaymentTransaction.query.filter_by( order=order, transaction_type=TRANSACTION_TYPE.REFUND).first() self.assertEqual(refund_transaction1.amount, expected_refund_amount) second_line_item = order.confirmed_line_items[0] razorpay.refund_payment = MagicMock(return_value=MockResponse( response_data={'id': buid()})) process_line_item_cancellation(second_line_item) self.assertEqual(second_line_item.status, LINE_ITEM_STATUS.CANCELLED) refund_transaction2 = PaymentTransaction.query.filter_by( order=order, transaction_type=TRANSACTION_TYPE.REFUND).order_by( PaymentTransaction.created_at.desc()).first() self.assertEqual(refund_transaction2.amount, second_line_item.final_amount) # test failed cancellation third_line_item = order.confirmed_line_items[0] razorpay.refund_payment = MagicMock( return_value=MockResponse(response_data={ "error": { "code": "BAD_REQUEST_ERROR", "description": "The amount is invalid", "field": "amount" } }, status_code=400)) self.assertRaises( PaymentGatewayError, lambda: process_line_item_cancellation(third_line_item)) # refund the remaining amount paid, and attempt to cancel a line item # this should cancel the line item without resulting in a new refund transaction refund_amount = order.net_amount refund_dict = { 'id': buid(), 'amount': refund_amount, 'internal_note': 'internal reference', 'note_to_user': '******' } razorpay.refund_payment = MagicMock(return_value=MockResponse( response_data=refund_dict)) formdata = { 'amount': refund_amount, 'internal_note': 'internal reference', 'refund_description': 'receipt description', 'note_to_user': '******' } refund_form = OrderRefundForm(data=formdata, parent=order, meta={'csrf': False}) partial_refund_args = { 'order': order, 'form': refund_form, 'request_method': 'POST' } with app.request_context(self.post_env): process_partial_refund_for_order(partial_refund_args) third_line_item = order.confirmed_line_items[0] pre_cancellation_transactions_count = order.refund_transactions.count() cancelled_refund_amount = process_line_item_cancellation( third_line_item) self.assertEqual(cancelled_refund_amount, decimal.Decimal(0)) self.assertEqual(pre_cancellation_transactions_count, order.refund_transactions.count()) # test free line item cancellation free_order_resp = self.make_free_order() free_order_resp_data = json.loads(free_order_resp.data)['result'] free_order = Order.query.get(free_order_resp_data.get('order_id')) free_line_item = free_order.line_items[0] process_line_item_cancellation(free_line_item) self.assertEqual(free_line_item.status, LINE_ITEM_STATUS.CANCELLED) self.assertEqual(free_order.transactions.count(), 0)
def test_cancel_line_item_in_order(self): original_quantity = 2 order_item = Item.query.filter_by(name='t-shirt').first() total_amount = order_item.current_price().amount * original_quantity data = { 'line_items': [{ 'item_id': str(order_item.id), 'quantity': original_quantity }], 'buyer': { 'fullname': 'Testing', 'phone': '9814141414', 'email': '*****@*****.**', } } ic = ItemCollection.query.first() # make a purchase order resp = self.client.post('/ic/{ic}/order'.format(ic=ic.id), data=json.dumps(data), content_type='application/json', headers=[('X-Requested-With', 'XMLHttpRequest'), ('Origin', app.config['BASE_URL'])]) self.assertEqual(resp.status_code, 201) resp_json = json.loads(resp.data)['result'] self.assertEqual(resp_json['final_amount'], total_amount) order = Order.query.get(resp_json['order_id']) # Create fake payment and transaction objects online_payment = OnlinePayment(pg_paymentid='pg_testpayment', order=order) online_payment.confirm() order_amounts = order.get_amounts(LINE_ITEM_STATUS.PURCHASE_ORDER) transaction = PaymentTransaction(order=order, online_payment=online_payment, amount=order_amounts.final_amount, currency=CURRENCY.INR) db.session.add(transaction) order.confirm_sale() db.session.commit() refund_amount = total_amount - 1 razorpay.refund_payment = MagicMock(return_value=MockResponse( response_data={'id': buid()})) pre_refund_transactions_count = order.refund_transactions.count() formdata = { 'amount': refund_amount, 'internal_note': 'internal reference', 'refund_description': 'receipt description', 'note_to_user': '******' } refund_form = OrderRefundForm(data=formdata, parent=order, meta={'csrf': False}) partial_refund_args = { 'order': order, 'form': refund_form, 'request_method': 'POST' } with app.request_context(self.post_env): process_partial_refund_for_order(partial_refund_args) self.assertEqual(pre_refund_transactions_count + 1, order.refund_transactions.count()) first_line_item = order.line_items[0] # Mock Razorpay's API razorpay.refund_payment = MagicMock(return_value=MockResponse( response_data={'id': buid()})) process_line_item_cancellation(first_line_item) self.assertEqual(first_line_item.status, LINE_ITEM_STATUS.CANCELLED) expected_refund_amount = total_amount - refund_amount refund_transaction1 = PaymentTransaction.query.filter_by( order=order, transaction_type=TRANSACTION_TYPE.REFUND).order_by( PaymentTransaction.created_at.desc()).first() self.assertEqual(refund_transaction1.amount, expected_refund_amount)