예제 #1
0
    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))
예제 #2
0
    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))
예제 #3
0
    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))
예제 #4
0
    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))
예제 #5
0
    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))
예제 #6
0
    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))
예제 #7
0
    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))
예제 #8
0
    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")
예제 #9
0
    def test_retry_success_first_call(self):
        mockFn = Mock(return_value=True)

        retry(lambda i: mockFn())

        mockFn.assert_called_once()
예제 #10
0
    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")
예제 #11
0
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