diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000..ef6a54aa --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,12 @@ +# Since version 2.23 (released in August 2019), git-blame has a feature +# to ignore or bypass certain commits. +# +# This file contains a list of commits that are not likely what you +# are looking for in a blame, such as mass reformatting or renaming. +# You can set this file as a default ignore file for blame by running +# the following command. +# +# $ git config blame.ignoreRevsFile .git-blame-ignore-revs + +# Ruff style the codebase +02510e3526de8aedd3dea6a693a86221922df7cb diff --git a/.gitignore b/.gitignore index 175055b1..da873c80 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ tags payments/docs/current node_modules/ __pycache__/ +.helix +.aider* diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f552a81d..1b1f487b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,24 +20,19 @@ repos: - id: check-yaml - id: debug-statements - - repo: https://github.com/asottile/pyupgrade - rev: v2.34.0 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.2.0 hooks: - - id: pyupgrade - args: ['--py310-plus'] + - id: ruff + name: "Run ruff import sorter" + args: ["--select=I", "--fix"] - - repo: https://github.com/adityahase/black - rev: 9cb0a69f4d0030cdf687eddf314468b39ed54119 - hooks: - - id: black - additional_dependencies: ['click==8.0.4'] + - id: ruff + name: "Run ruff linter" + + - id: ruff-format + name: "Run ruff formatter" - - repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 - hooks: - - id: flake8 - additional_dependencies: ['flake8-bugbear',] - args: ['--config', '.github/helper/flake8.conf'] ci: autoupdate_schedule: weekly diff --git a/payments/payment_gateways/doctype/braintree_settings/braintree_settings.py b/payments/payment_gateways/doctype/braintree_settings/braintree_settings.py index 717443e8..5c0fe801 100644 --- a/payments/payment_gateways/doctype/braintree_settings/braintree_settings.py +++ b/payments/payment_gateways/doctype/braintree_settings/braintree_settings.py @@ -14,7 +14,7 @@ class BraintreeSettings(Document): - supported_currencies = [ + supported_currencies = ( "AED", "AMD", "AOA", @@ -150,7 +150,7 @@ class BraintreeSettings(Document): "ZAR", "ZMK", "ZWD", - ] + ) def validate(self): if not self.flags.ignore_mandatory: diff --git a/payments/payment_gateways/doctype/gocardless_settings/__init__.py b/payments/payment_gateways/doctype/gocardless_settings/__init__.py index 65be5993..d8f8a3d3 100644 --- a/payments/payment_gateways/doctype/gocardless_settings/__init__.py +++ b/payments/payment_gateways/doctype/gocardless_settings/__init__.py @@ -34,7 +34,7 @@ def set_status(event): def set_mandate_status(event): mandates = [] - if isinstance(event["links"], (list,)): + if isinstance(event["links"], list): for link in event["links"]: mandates.append(link["mandate"]) else: diff --git a/payments/payment_gateways/doctype/gocardless_settings/gocardless_settings.py b/payments/payment_gateways/doctype/gocardless_settings/gocardless_settings.py index ac6b83a9..4abb7e97 100644 --- a/payments/payment_gateways/doctype/gocardless_settings/gocardless_settings.py +++ b/payments/payment_gateways/doctype/gocardless_settings/gocardless_settings.py @@ -13,7 +13,7 @@ class GoCardlessSettings(Document): - supported_currencies = ["EUR", "DKK", "GBP", "SEK", "AUD", "NZD", "CAD", "USD"] + supported_currencies = ("EUR", "DKK", "GBP", "SEK", "AUD", "NZD", "CAD", "USD") def validate(self): self.initialize_client() @@ -21,9 +21,7 @@ def validate(self): def initialize_client(self): self.environment = self.get_environment() try: - self.client = gocardless_pro.Client( - access_token=self.access_token, environment=self.environment - ) + self.client = gocardless_pro.Client(access_token=self.access_token, environment=self.environment) return self.client except Exception as e: frappe.throw(e) @@ -64,7 +62,6 @@ def on_payment_request_submission(self, data): return True def check_mandate_validity(self, data): - if frappe.db.exists("GoCardless Mandate", dict(customer=data.get("payer_name"), disabled=0)): registered_mandate = frappe.db.get_value( "GoCardless Mandate", dict(customer=data.get("payer_name"), disabled=0), "mandate" @@ -124,9 +121,7 @@ def create_charge_on_gocardless(self): redirect_to = self.data.get("redirect_to") or None redirect_message = self.data.get("redirect_message") or None - reference_doc = frappe.get_doc( - self.data.get("reference_doctype"), self.data.get("reference_docname") - ) + reference_doc = frappe.get_doc(self.data.get("reference_doctype"), self.data.get("reference_docname")) self.initialize_client() try: @@ -172,7 +167,7 @@ def create_charge_on_gocardless(self): frappe.log_error("Gocardless payment failed") self.integration_request.db_set("error", payment.status, update_modified=False) - except Exception as e: + except Exception: frappe.log_error("GoCardless Payment Error") if self.flags.status_changed_to == "Completed": diff --git a/payments/payment_gateways/doctype/mpesa_settings/mpesa_connector.py b/payments/payment_gateways/doctype/mpesa_settings/mpesa_connector.py index 7eb8b9c0..ce6df833 100644 --- a/payments/payment_gateways/doctype/mpesa_settings/mpesa_connector.py +++ b/payments/payment_gateways/doctype/mpesa_settings/mpesa_connector.py @@ -119,10 +119,8 @@ def stk_push( errorMessage(str): This is a predefined code that indicates the reason for request failure. """ - time = ( - str(datetime.datetime.now()).split(".")[0].replace("-", "").replace(" ", "").replace(":", "") - ) - password = f"{str(business_shortcode)}{str(passcode)}{time}" + time = str(datetime.datetime.now()).split(".")[0].replace("-", "").replace(" ", "").replace(":", "") + password = f"{business_shortcode!s}{passcode!s}{time}" encoded = base64.b64encode(bytes(password, encoding="utf8")) payload = { "BusinessShortCode": business_shortcode, @@ -135,9 +133,7 @@ def stk_push( "CallBackURL": callback_url, "AccountReference": reference_code, "TransactionDesc": description, - "TransactionType": "CustomerPayBillOnline" - if self.env == "sandbox" - else "CustomerBuyGoodsOnline", + "TransactionType": "CustomerPayBillOnline" if self.env == "sandbox" else "CustomerBuyGoodsOnline", } headers = { "Authorization": f"Bearer {self.authentication_token}", diff --git a/payments/payment_gateways/doctype/mpesa_settings/mpesa_settings.py b/payments/payment_gateways/doctype/mpesa_settings/mpesa_settings.py index 43d5348b..97e9aacc 100644 --- a/payments/payment_gateways/doctype/mpesa_settings/mpesa_settings.py +++ b/payments/payment_gateways/doctype/mpesa_settings/mpesa_settings.py @@ -18,7 +18,7 @@ class MpesaSettings(Document): - supported_currencies = ["KES"] + supported_currencies = ("KES",) def validate_transaction_currency(self, currency): if currency not in self.supported_currencies: @@ -51,7 +51,7 @@ def request_for_payment(self, **kwargs): args = frappe._dict(kwargs) request_amounts = self.split_request_amount_according_to_transaction_limit(args) - for i, amount in enumerate(request_amounts): + for _i, amount in enumerate(request_amounts): args.request_amount = amount if frappe.flags.in_test: from payments.payment_gateways.doctype.mpesa_settings.test_mpesa_settings import ( @@ -104,8 +104,8 @@ def get_account_balance_info(self): def handle_api_response(self, global_id, request_dict, response): """Response received from API calls returns a global identifier for each transaction, this code is returned during the callback.""" # check error response - if getattr(response, "requestId"): - req_name = getattr(response, "requestId") + if response.requestId: + req_name = response.requestId error = response else: # global checkout id used as request name @@ -116,7 +116,7 @@ def handle_api_response(self, global_id, request_dict, response): create_request_log(request_dict, "Host", "Mpesa", req_name, error) if error: - frappe.throw(_(getattr(response, "errorMessage")), title=_("Transaction Error")) + frappe.throw(_(response.errorMessage), title=_("Transaction Error")) def generate_stk_push(**kwargs): @@ -197,7 +197,7 @@ def verify_transaction(**kwargs): ) total_paid = amount + sum(completed_payments) - mpesa_receipts = ", ".join(mpesa_receipts + [mpesa_receipt]) + mpesa_receipts = ", ".join([*mpesa_receipts, mpesa_receipt]) if total_paid >= pr.grand_total: pr.run_method("on_payment_authorized", "Completed") @@ -318,9 +318,7 @@ def process_balance_info(**kwargs): ) except Exception: request.handle_failure(account_balance_response) - frappe.log_error( - title="Mpesa Account Balance Processing Error", message=account_balance_response - ) + frappe.log_error(title="Mpesa Account Balance Processing Error", message=account_balance_response) else: request.handle_failure(account_balance_response) diff --git a/payments/payment_gateways/doctype/mpesa_settings/test_mpesa_settings.py b/payments/payment_gateways/doctype/mpesa_settings/test_mpesa_settings.py index aab37d93..c0eb227e 100644 --- a/payments/payment_gateways/doctype/mpesa_settings/test_mpesa_settings.py +++ b/payments/payment_gateways/doctype/mpesa_settings/test_mpesa_settings.py @@ -5,17 +5,16 @@ from json import dumps import frappe - from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_customer from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice -from erpnext.stock.doctype.item.test_item import make_item from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile +from erpnext.stock.doctype.item.test_item import make_item from payments.payment_gateways.doctype.mpesa_settings.mpesa_settings import ( + create_mode_of_payment, process_balance_info, verify_transaction, ) -from payments.payment_gateways.doctype.mpesa_settings.mpesa_settings import create_mode_of_payment class TestMpesaSettings(unittest.TestCase): @@ -120,9 +119,7 @@ def test_processing_of_callback_payload(self): pluck="name", ) - callback_response = get_payment_callback_payload( - Amount=500, CheckoutRequestID=integration_req_ids[0] - ) + callback_response = get_payment_callback_payload(Amount=500, CheckoutRequestID=integration_req_ids[0]) verify_transaction(**callback_response) # test creation of integration request integration_request = frappe.get_doc("Integration Request", integration_req_ids[0]) diff --git a/payments/payment_gateways/doctype/paypal_settings/paypal_settings.py b/payments/payment_gateways/doctype/paypal_settings/paypal_settings.py index 04943a5c..5bf4a2fb 100644 --- a/payments/payment_gateways/doctype/paypal_settings/paypal_settings.py +++ b/payments/payment_gateways/doctype/paypal_settings/paypal_settings.py @@ -79,7 +79,7 @@ def on_payment_authorized(payment_status): class PayPalSettings(Document): - supported_currencies = [ + supported_currencies = ( "AUD", "BRL", "CAD", @@ -105,14 +105,14 @@ class PayPalSettings(Document): "THB", "TRY", "USD", - ] + ) def __setup__(self): - setattr(self, "use_sandbox", 0) + self.use_sandbox = 0 def setup_sandbox_env(self, token): data = json.loads(frappe.db.get_value("Integration Request", token, "data")) - setattr(self, "use_sandbox", cint(frappe._dict(data).use_sandbox) or 0) + self.use_sandbox = cint(frappe._dict(data).use_sandbox) or 0 def validate(self): create_payment_gateway("PayPal") @@ -171,7 +171,7 @@ def validate_paypal_credentails(self): frappe.throw(_("Invalid payment gateway credentials")) def get_payment_url(self, **kwargs): - setattr(self, "use_sandbox", cint(kwargs.get("use_sandbox", 0))) + self.use_sandbox = cint(kwargs.get("use_sandbox", 0)) response = self.execute_set_express_checkout(**kwargs) diff --git a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py index 99e9f909..db879255 100644 --- a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py +++ b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py @@ -24,7 +24,7 @@ class PaytmSettings(Document): - supported_currencies = ["INR"] + supported_currencies = ("INR",) def validate(self): create_payment_gateway("Paytm") @@ -75,7 +75,6 @@ def get_paytm_config(): def get_paytm_params(payment_details, order_id, paytm_config): - # initialize a dictionary paytm_params = dict() @@ -127,9 +126,7 @@ def verify_transaction(**paytm_params): http_status_code=401, indicator_color="red", ) - frappe.log_error( - "Order unsuccessful. Failed Response:" + cstr(paytm_params), "Paytm Payment Failed" - ) + frappe.log_error("Order unsuccessful. Failed Response:" + cstr(paytm_params), "Paytm Payment Failed") def verify_transaction_status(paytm_config, order_id): diff --git a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py index ebf16056..7394456f 100644 --- a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py +++ b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py @@ -79,7 +79,7 @@ def on_payment_authorized(payment_status): class RazorpaySettings(Document): - supported_currencies = ["INR"] + supported_currencies = ("INR",) def init_client(self): if self.api_key: @@ -251,8 +251,8 @@ def create_request(self, data): def authorize_payment(self): """ - An authorization is performed when user’s payment details are successfully authenticated by the bank. - The money is deducted from the customer’s account, but will not be transferred to the merchant’s account + An authorization is performed when user's payment details are successfully authenticated by the bank. + The money is deducted from the customer's account, but will not be transferred to the merchant's account until it is explicitly captured by merchant. """ data = json.loads(self.integration_request.data) @@ -306,8 +306,8 @@ def authorize_payment(self): if custom_redirect_to: redirect_to = custom_redirect_to - redirect_url = "payment-success?doctype={}&docname={}".format( - self.data.reference_doctype, self.data.reference_docname + redirect_url = ( + f"payment-success?doctype={self.data.reference_doctype}&docname={self.data.reference_docname}" ) else: redirect_url = "payment-failed" @@ -341,7 +341,7 @@ def cancel_subscription(self, subscription_id): settings = self.get_settings({}) try: - resp = make_post_request( + make_post_request( f"https://api.razorpay.com/v1/subscriptions/{subscription_id}/cancel", auth=(settings.api_key, settings.api_secret), ) @@ -393,7 +393,9 @@ def capture_payment(is_sandbox=False, sanbox_response=None): if resp.get("status") == "authorized": resp = make_post_request( - "https://api.razorpay.com/v1/payments/{}/capture".format(data.get("razorpay_payment_id")), + "https://api.razorpay.com/v1/payments/{}/capture".format( + data.get("razorpay_payment_id") + ), auth=(settings.api_key, settings.api_secret), data={"amount": data.get("amount")}, ) diff --git a/payments/payment_gateways/doctype/stripe_settings/stripe_settings.py b/payments/payment_gateways/doctype/stripe_settings/stripe_settings.py index 816b77e9..d6136c6c 100644 --- a/payments/payment_gateways/doctype/stripe_settings/stripe_settings.py +++ b/payments/payment_gateways/doctype/stripe_settings/stripe_settings.py @@ -1,6 +1,7 @@ # Copyright (c) 2017, Frappe Technologies and contributors # License: MIT. See LICENSE +from types import MappingProxyType from urllib.parse import urlencode import frappe @@ -11,9 +12,27 @@ from payments.utils import create_payment_gateway +currency_wise_minimum_charge_amount = { + "JPY": 50, + "MXN": 10, + "DKK": 2.50, + "HKD": 4.00, + "NOK": 3.00, + "SEK": 3.00, + "USD": 0.50, + "AUD": 0.50, + "BRL": 0.50, + "CAD": 0.50, + "CHF": 0.50, + "EUR": 0.50, + "GBP": 0.30, + "NZD": 0.50, + "SGD": 0.50, +} + class StripeSettings(Document): - supported_currencies = [ + supported_currencies = ( "AED", "ALL", "ANG", @@ -128,25 +147,9 @@ class StripeSettings(Document): "XPF", "YER", "ZAR", - ] - - currency_wise_minimum_charge_amount = { - "JPY": 50, - "MXN": 10, - "DKK": 2.50, - "HKD": 4.00, - "NOK": 3.00, - "SEK": 3.00, - "USD": 0.50, - "AUD": 0.50, - "BRL": 0.50, - "CAD": 0.50, - "CHF": 0.50, - "EUR": 0.50, - "GBP": 0.30, - "NZD": 0.50, - "SGD": 0.50, - } + ) + + currency_wise_minimum_charge_amount = MappingProxyType(currency_wise_minimum_charge_amount) def on_update(self): create_payment_gateway( @@ -225,7 +228,7 @@ def create_charge_on_stripe(self): receipt_email=self.data.payer_email, ) - if charge.captured == True: + if charge.captured is True: self.integration_request.db_set("status", "Completed", update_modified=False) self.flags.status_changed_to = "Completed" @@ -255,9 +258,7 @@ def finalize_request(self): if custom_redirect_to: redirect_to = custom_redirect_to - redirect_url = "payment-success?doctype={}&docname={}".format( - self.data.reference_doctype, self.data.reference_docname - ) + redirect_url = f"payment-success?doctype={self.data.reference_doctype}&docname={self.data.reference_docname}" if self.redirect_url: redirect_url = self.redirect_url diff --git a/payments/payment_gateways/stripe_integration.py b/payments/payment_gateways/stripe_integration.py index 35c63c55..2d7e8a5d 100644 --- a/payments/payment_gateways/stripe_integration.py +++ b/payments/payment_gateways/stripe_integration.py @@ -1,8 +1,8 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -import stripe import frappe +import stripe from frappe import _ from frappe.integrations.utils import create_request_log diff --git a/payments/templates/pages/braintree_checkout.py b/payments/templates/pages/braintree_checkout.py index 12b01369..a0dd968f 100644 --- a/payments/templates/pages/braintree_checkout.py +++ b/payments/templates/pages/braintree_checkout.py @@ -40,9 +40,7 @@ def get_context(context): context["amount"] = flt(context["amount"]) gateway_controller = get_gateway_controller(context.reference_docname) - context["header_img"] = frappe.db.get_value( - "Braintree Settings", gateway_controller, "header_img" - ) + context["header_img"] = frappe.db.get_value("Braintree Settings", gateway_controller, "header_img") else: frappe.redirect_to_message( diff --git a/payments/templates/pages/gocardless_checkout.py b/payments/templates/pages/gocardless_checkout.py index fa780d23..89665995 100644 --- a/payments/templates/pages/gocardless_checkout.py +++ b/payments/templates/pages/gocardless_checkout.py @@ -38,9 +38,7 @@ def get_context(context): context["amount"] = flt(context["amount"]) gateway_controller = get_gateway_controller(context.reference_docname) - context["header_img"] = frappe.db.get_value( - "GoCardless Settings", gateway_controller, "header_img" - ) + context["header_img"] = frappe.db.get_value("GoCardless Settings", gateway_controller, "header_img") else: frappe.redirect_to_message( @@ -95,6 +93,6 @@ def check_mandate(data, reference_doctype, reference_docname): return {"redirect_to": redirect_flow.redirect_url} - except Exception as e: + except Exception: frappe.log_error("GoCardless Payment Error") return {"redirect_to": "payment-failed"} diff --git a/payments/templates/pages/gocardless_confirmation.py b/payments/templates/pages/gocardless_confirmation.py index 3fe7d99b..02420641 100644 --- a/payments/templates/pages/gocardless_confirmation.py +++ b/payments/templates/pages/gocardless_confirmation.py @@ -33,7 +33,6 @@ def get_context(context): @frappe.whitelist(allow_guest=True) def confirm_payment(redirect_flow_id, reference_doctype, reference_docname): - client = gocardless_initialization(reference_docname) try: @@ -59,7 +58,7 @@ def confirm_payment(redirect_flow_id, reference_doctype, reference_docname): try: create_mandate(data) - except Exception as e: + except Exception: frappe.log_error("GoCardless Mandate Registration Error") gateway_controller = get_gateway_controller(reference_docname) @@ -67,7 +66,7 @@ def confirm_payment(redirect_flow_id, reference_doctype, reference_docname): return {"redirect_to": confirmation_url} - except Exception as e: + except Exception: frappe.log_error("GoCardless Payment Error") return {"redirect_to": "payment-failed"} diff --git a/payments/templates/pages/payment_success.py b/payments/templates/pages/payment_success.py index 8985850a..e2d1115b 100644 --- a/payments/templates/pages/payment_success.py +++ b/payments/templates/pages/payment_success.py @@ -7,7 +7,6 @@ def get_context(context): - token = frappe.local.form_dict.token doc = frappe.get_doc(frappe.local.form_dict.doctype, frappe.local.form_dict.docname) context.payment_message = "" diff --git a/payments/templates/pages/razorpay_checkout.py b/payments/templates/pages/razorpay_checkout.py index d0e77f6d..dab9a7ab 100644 --- a/payments/templates/pages/razorpay_checkout.py +++ b/payments/templates/pages/razorpay_checkout.py @@ -38,7 +38,7 @@ def get_context(context): payment_details["subscription_id"] if payment_details.get("subscription_id") else "" ) - except Exception as e: + except Exception: frappe.redirect_to_message( _("Invalid Token"), _("Seems token you are using is invalid!"), diff --git a/payments/utils/__init__.py b/payments/utils/__init__.py index fb540bd5..1a494cb7 100644 --- a/payments/utils/__init__.py +++ b/payments/utils/__init__.py @@ -2,7 +2,7 @@ before_install, create_payment_gateway, delete_custom_fields, + erpnext_app_import_guard, get_payment_gateway_controller, make_custom_fields, - erpnext_app_import_guard, ) diff --git a/payments/utils/utils.py b/payments/utils/utils.py index e56a89c2..fed9ac04 100644 --- a/payments/utils/utils.py +++ b/payments/utils/utils.py @@ -1,7 +1,8 @@ +from contextlib import contextmanager + import click import frappe from frappe import _ -from contextlib import contextmanager from frappe.custom.doctype.custom_field.custom_field import create_custom_fields diff --git a/pyproject.toml b/pyproject.toml index 1dbe5220..dc78b7cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,14 +20,39 @@ dependencies = [ requires = ["flit_core >=3.4,<4"] build-backend = "flit_core.buildapi" -[tool.black] -line-length = 99 +[tool.ruff] +line-length = 110 +target-version = "py310" -[tool.isort] -line_length = 99 -multi_line_output = 3 -include_trailing_comma = true -force_grid_wrap = 0 -use_parentheses = true -ensure_newline_before_comments = true -indent = "\t" +[tool.ruff.lint] +select = [ + "F", + "E", + "W", + "I", + "UP", + "B", + "RUF", +] +ignore = [ + "B017", # assertRaises(Exception) - should be more specific + "B018", # useless expression, not assigned to anything + "B023", # function doesn't bind loop variable - will have last iteration's value + "B904", # raise inside except without from + "E101", # indentation contains mixed spaces and tabs + "E402", # module level import not at top of file + "E501", # line too long + "E741", # ambiguous variable name + "F401", # "unused" imports + "F403", # can't detect undefined names from * import + "F405", # can't detect undefined names from * import + "F722", # syntax error in forward type annotation + "W191", # indentation contains tabs + "RUF001", # string contains ambiguous unicode character +] +typing-modules = ["frappe.types.DF"] + +[tool.ruff.format] +quote-style = "double" +indent-style = "tab" +docstring-code-format = true