def test_retry_limit_via_exception_predicate(self): mockFn = Mock(side_effect=Exception("Unknown Error")) retry(mockFn, 3, None, lambda ex, i: i == 1) self.assertTrue(mockFn.call_count == 2, "Retries: {} != {}".format(mockFn.call_count, 2))
def test_retry_limit_via_result_predicate(self): mockFn = Mock() retry(mockFn, 3, lambda r, i: i == 1) self.assertTrue(mockFn.call_count == 2, "Retries: {} != {}".format(mockFn.call_count, 2))
def test_retry_exception(self): mockFn = Mock(side_effect=Exception("Unknown Error")) retry(lambda i: mockFn(), 3) self.assertTrue(mockFn.call_count == 3, "Retries: {} != {}".format(mockFn.call_count, 3))
def test_retry_exception_with_predicate(self): mockFn = Mock(side_effect=Exception("Unknown Error")) exceptionFn = Mock(return_value=False) retry(lambda i: mockFn(), 3, None, lambda ex, i: exceptionFn()) self.assertTrue(mockFn.call_count == 3, "Retries: {} != {}".format(mockFn.call_count, 3)) self.assertTrue( exceptionFn.call_count == 3, "Exception Runs: {} != {}".format(exceptionFn.call_count, 3))
def test_retry_on_exception_value(self): mockFn = Mock(side_effect=Exception("Unknown Error")) result = retry(mockFn, on_exception=lambda ex, i: 200) mockFn.assert_called_once() self.assertTrue(result == 200, "{} != 200".format(result))
def test_retry_on_result_value(self): mockFn = Mock(return_value=100) result = retry(mockFn, on_result=lambda ex, i: 200) mockFn.assert_called_once() self.assertTrue(result == 200, "{} != 200".format(result))
def test_retry_result_value(self): mockFn = Mock(return_value=100) result = retry(mockFn) mockFn.assert_called_once() self.assertTrue(result == 100, "{} != 100".format(result))
def test_retry_on_exception_fail_value(self): mockFn = Mock(side_effect=Exception("Unknown Error")) result = retry(mockFn, on_exception=lambda ex, i: ex) mockFn.assert_called_once() self.assertTrue(isinstance(result, Exception), "Did not return expected exception")
def test_retry_success_first_call(self): mockFn = Mock(return_value=True) retry(lambda i: mockFn()) mockFn.assert_called_once()
def test_retry_fail_via_result_predicate(self): mockFn = Mock() retry(lambda i: mockFn(), 3, lambda r, i: False) self.assertTrue(mockFn.call_count == 3, "Retry 3 times")
def charge_credit_card(card, data): """ Charge a credit card """ data = frappe._dict(json.loads(data)) card = frappe._dict(json.loads(card)) # sanity gate keeper. Don't process anything without reference doctypes if not data.reference_doctype or not data.reference_docname: return { "status": "Failed", "description": "Invalid request. Submitted data contains no order reference. This seems to be an internal error. Please contact support." } # fetch previous requests info request_info = get_order_request_info(data.reference_doctype, data.reference_docname) # and document references pr = frappe.get_doc(data.reference_doctype, data.reference_docname) reference_doc = frappe.get_doc(pr.reference_doctype, pr.reference_name) # Sanity check. Make sure to not process card if a previous request was fullfilled status = get_status(data) if status.get("status") != "Failed" and status.get("status") != "NotFound": return status # sanity gate keeper. Don't process anything without reference doctypes if not data.reference_doctype or not data.reference_docname: return { "status": "Failed", "description": "Invalid request. Submitted data contains no order reference. This seems to be an internal error. Please contact support." } # fetch previous requests info request_info = get_order_request_info(data.reference_doctype, data.reference_docname) # and document references pr = frappe.get_doc(data.reference_doctype, data.reference_docname) reference_doc = frappe.get_doc(pr.reference_doctype, pr.reference_name) # Sanity check. Make sure to not process card if a previous request was fullfilled status = get_status(data) if status.get("status") != "Failed" and status.get("status") != "NotFound": return status # Create Integration Request integration_request = create_request_log(data, "Host", "Authorizenet") # Setup Authentication on Authorizenet merchant_auth = apicontractsv1.merchantAuthenticationType() merchant_auth.name = frappe.db.get_single_value("Authorizenet Settings", "api_login_id") merchant_auth.transactionKey = get_decrypted_password('Authorizenet Settings', 'Authorizenet Settings', fieldname='api_transaction_key', raise_exception=False) # create request dict to ease passing around data request = frappe._dict({ "pr": pr, "reference_doc": reference_doc, "data": data, "merchant_auth": merchant_auth, "integration_request": integration_request, "card": card }) # Charge card step def tryChargeCard(i): # ping authnet first to check if transaction was previously made order_transaction = query_successful_authnet_transaction(request) # if transaction was already recorded on authnet side, update integration # request and return success if order_transaction: return frappe._dict({ "status": "Completed" }) # Otherwise, begine charging card response = authnet_charge(request) # translate result to response payload status = "Failed" if response is not None: # Check to see if the API request was successfully received and acted upon if response.messages.resultCode == "Ok" and hasattr(response.transactionResponse, 'messages') is True: status = "Completed" response_dict = to_dict(response) result = frappe._dict({ "status": status }) if status == "Completed": description = response_dict.get( "transactionResponse", {}) \ .get("messages",{}) \ .get("message", {}) \ .get("description", "") if description: result.update({"description": description}) elif status == "Failed": description = response_dict.get( "transactionResponse", {}) \ .get("errors",{}) \ .get("error", {}) \ .get("errorText", "Unknown Error") if description: result.update({"description": description}) integration_request.error = description else: # TODO: Check what errors would trickle here... result.update({ "description": "Something went wrong while trying to complete the transaction. Please try again." }) # update integration request with result integration_request.update_status(data, status) # Finally update PR and order if status != "Failed": try: pr.run_method("on_payment_authorized", status) # now update Payment Entry to store card holder name for record keeping pe_list = frappe.get_all("Payment Entry", filters={"reference_no": pr.name}, limit=1) if len(pe_list) > 0: pe_name = pe_list[0] remarks = frappe.db.get_value("Payment Entry", pe_name, "remarks") remarks = "{}\n---\nCard Holder: {}".format(remarks, card.holder_name) frappe.db.set_value("Payment Entry", pe_name, "remarks", remarks) except Exception as ex: # we have a payment, however an internal process broke # so, log it and add a comment on the reference document. # continue to process the request as if it was succesful traceback = frappe.get_traceback() err_doc = frappe.log_error(message=traceback , title="Error processing credit card") request.reference_doc.add_comment("Comment", text="[INTERNAL ERROR] There was a problem while processing this order's payment.\n"+ "The transaction was record successfully and the card was charged.\n\n" + "However, please contact support to track down this issue and provide the following " + "error id: " + err_doc.name ) # Flag declined transactions as a validation error which the user should correct. if "declined" in (result.get("description") or "").lower(): result.type = "Validation" if "duplicate" in (result.get("description") or "").lower(): result.type = "Duplicate" return result # validate result to trigger a retry or complete calls def chargeResultPredicate(result, i): return result # handle exceptions during card charges def chargeExceptionPredicate(exception, i): result_obj = {} if isinstance(exception, Exception): description = "There was an internal error while processing your payment." + \ "To avoid double charges, please contact us." traceback = frappe.get_traceback() frappe.log_error(message=traceback , title="Error processing credit card") result_obj.update({ "status": "Failed", "description": description, "error": exception.message }) return result_obj result = retry(tryChargeCard, 3, chargeResultPredicate, chargeExceptionPredicate) return result