diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3c934ab..78bc2e7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,5 @@ -name: CI +name: Server on: push: diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml new file mode 100644 index 0000000..6b504fa --- /dev/null +++ b/.github/workflows/linter.yml @@ -0,0 +1,54 @@ +name: Linter + +on: + pull_request: + workflow_dispatch: + push: + branches: [ develop ] + +permissions: + contents: read + +concurrency: + group: commitcheck-frappe-${{ github.event.number }} + cancel-in-progress: true + +jobs: + commit-lint: + name: 'Semantic Commits' + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 200 + - uses: actions/setup-node@v3 + with: + node-version: 16 + check-latest: true + + - name: Check commit titles + run: | + npm install @commitlint/cli @commitlint/config-conventional + npx commitlint --verbose --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} + + linter: + name: 'Frappe Linter' + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + - uses: pre-commit/action@v3.0.0 + + - name: Download Semgrep rules + run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules + + - name: Run Semgrep rules + run: | + pip install semgrep==0.97.0 + semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..b231221 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,50 @@ +exclude: 'node_modules|.git' +default_stages: [commit] +fail_fast: false + + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: trailing-whitespace + files: "frappe.*" + exclude: ".*json$|.*txt$|.*csv|.*md|.*svg" + - id: check-yaml + - id: no-commit-to-branch + args: ['--branch', 'develop'] + - id: check-merge-conflict + - id: check-ast + - id: check-json + - id: check-toml + - id: check-yaml + - id: debug-statements + + - repo: https://github.com/asottile/pyupgrade + rev: v2.34.0 + hooks: + - id: pyupgrade + args: ['--py310-plus'] + + - repo: https://github.com/adityahase/black + rev: 9cb0a69f4d0030cdf687eddf314468b39ed54119 + hooks: + - id: black + additional_dependencies: ['click==8.0.4'] + + - repo: https://github.com/timothycrosley/isort + rev: 5.9.1 + hooks: + - id: isort + + - repo: https://gitlab.com/pycqa/flake8 + rev: 3.9.2 + hooks: + - id: flake8 + additional_dependencies: ['flake8-bugbear',] + args: ['--config', '.github/helper/flake8.conf'] + +ci: + autoupdate_schedule: weekly + skip: [] + submodules: false diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 0000000..8847564 --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,25 @@ +module.exports = { + parserPreset: 'conventional-changelog-conventionalcommits', + rules: { + 'subject-empty': [2, 'never'], + 'type-case': [2, 'always', 'lower-case'], + 'type-empty': [2, 'never'], + 'type-enum': [ + 2, + 'always', + [ + 'build', + 'chore', + 'ci', + 'docs', + 'feat', + 'fix', + 'perf', + 'refactor', + 'revert', + 'style', + 'test', + ], + ], + }, +}; diff --git a/payments/config/desktop.py b/payments/config/desktop.py index ae10187..e2ac674 100644 --- a/payments/config/desktop.py +++ b/payments/config/desktop.py @@ -1,10 +1,5 @@ from frappe import _ + def get_data(): - return [ - { - "module_name": "Payments", - "type": "module", - "label": _("Payments") - } - ] + return [{"module_name": "Payments", "type": "module", "label": _("Payments")}] diff --git a/payments/config/docs.py b/payments/config/docs.py index 81052aa..584c308 100644 --- a/payments/config/docs.py +++ b/payments/config/docs.py @@ -6,5 +6,6 @@ Configuration for docs # headline = "App that does everything" # sub_heading = "Yes, you got that right the first time, everything" + def get_context(context): context.brand_html = "Payments" diff --git a/payments/hooks.py b/payments/hooks.py index 9ccf5f0..1e1e32e 100644 --- a/payments/hooks.py +++ b/payments/hooks.py @@ -42,7 +42,7 @@ app_license = "MIT" # website user home page (by Role) # role_home_page = { -# "Role": "home_page" +# "Role": "home_page" # } # Generators @@ -107,7 +107,7 @@ override_doctype_class = { # "on_update": "method", # "on_cancel": "method", # "on_trash": "method" -# } +# } # } # Scheduled Tasks diff --git a/payments/overrides/payment_webform.py b/payments/overrides/payment_webform.py index e2a9fd2..b1fe4cb 100644 --- a/payments/overrides/payment_webform.py +++ b/payments/overrides/payment_webform.py @@ -25,12 +25,13 @@ class PaymentWebForm(WebForm): if getattr(self, "accept_payment", False): controller = get_payment_gateway_controller(self.payment_gateway) - title = "Payment for {0} {1}".format(doc.doctype, doc.name) + title = f"Payment for {doc.doctype} {doc.name}" amount = self.amount if self.amount_based_on_field: amount = doc.get(self.amount_field) from decimal import Decimal + if amount is None or Decimal(amount) <= 0: return frappe.utils.get_url(self.success_url or self.route) @@ -44,14 +45,13 @@ class PaymentWebForm(WebForm): "payer_name": frappe.utils.get_fullname(frappe.session.user), "order_id": doc.name, "currency": self.currency, - "redirect_to": frappe.utils.get_url(self.success_url or self.route) + "redirect_to": frappe.utils.get_url(self.success_url or self.route), } # Redirect the user to this url return controller.get_payment_url(**payment_details) - @frappe.whitelist(allow_guest=True) @rate_limit(key="web_form", limit=5, seconds=60, methods=["POST"]) def accept(web_form, data, docname=None, for_payment=False): diff --git a/payments/payment_gateways/doctype/braintree_settings/braintree_settings.py b/payments/payment_gateways/doctype/braintree_settings/braintree_settings.py index 9aa28ba..7e0f341 100644 --- a/payments/payment_gateways/doctype/braintree_settings/braintree_settings.py +++ b/payments/payment_gateways/doctype/braintree_settings/braintree_settings.py @@ -4,7 +4,6 @@ from urllib.parse import urlencode import braintree - import frappe from frappe import _ from frappe.integrations.utils import create_request_log @@ -225,7 +224,9 @@ class BraintreeSettings(Document): if result.is_success: self.integration_request.db_set("status", "Completed", update_modified=False) self.flags.status_changed_to = "Completed" - self.integration_request.db_set("output", result.transaction.status, update_modified=False) + self.integration_request.db_set( + "output", result.transaction.status, update_modified=False + ) elif result.transaction: self.integration_request.db_set("status", "Failed", update_modified=False) diff --git a/payments/payment_gateways/doctype/paypal_settings/paypal_settings.py b/payments/payment_gateways/doctype/paypal_settings/paypal_settings.py index 3aab758..88a120c 100644 --- a/payments/payment_gateways/doctype/paypal_settings/paypal_settings.py +++ b/payments/payment_gateways/doctype/paypal_settings/paypal_settings.py @@ -65,9 +65,8 @@ More Details: import json from urllib.parse import urlencode -import pytz - import frappe +import pytz from frappe import _ from frappe.integrations.utils import create_request_log, make_post_request from frappe.model.document import Document @@ -75,7 +74,9 @@ from frappe.utils import call_hook_method, cint, get_datetime, get_url from payments.utils import create_payment_gateway -api_path = "/api/method/payments.payment_gateways.doctype.paypal_settings.paypal_settings" +api_path = ( + "/api/method/payments.payment_gateways.doctype.paypal_settings.paypal_settings" +) class PayPalSettings(Document): @@ -176,7 +177,9 @@ class PayPalSettings(Document): response = self.execute_set_express_checkout(**kwargs) if self.paypal_sandbox or self.use_sandbox: - return_url = "https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token={0}" + return_url = ( + "https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token={0}" + ) else: return_url = "https://www.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token={0}" @@ -212,7 +215,9 @@ class PayPalSettings(Document): response = make_post_request(url, data=params.encode("utf-8")) if response.get("ACK")[0] != "Success": - frappe.throw(_("Looks like something is wrong with this site's Paypal configuration.")) + frappe.throw( + _("Looks like something is wrong with this site's Paypal configuration.") + ) return response @@ -294,7 +299,9 @@ def get_express_checkout_details(token): ) frappe.local.response["type"] = "redirect" - frappe.local.response["location"] = get_redirect_uri(doc, token, response.get("PAYERID")[0]) + frappe.local.response["location"] = get_redirect_uri( + doc, token, response.get("PAYERID")[0] + ) except Exception: frappe.log_error(frappe.get_traceback()) @@ -360,7 +367,9 @@ def create_recurring_profile(token, payerid): if data.get("subscription_id"): if addons: updating = True - manage_recurring_payment_profile_status(data["subscription_id"], "Cancel", params, url) + manage_recurring_payment_profile_status( + data["subscription_id"], "Cancel", params, url + ) params.update( { @@ -382,10 +391,12 @@ def create_recurring_profile(token, payerid): "Completed" if data.get("starting_immediately") or updating else "Verified" ) - starts_at = get_datetime(subscription_details.get("start_date")) or frappe.utils.now_datetime() - starts_at = starts_at.replace(tzinfo=pytz.timezone(frappe.utils.get_time_zone())).astimezone( - pytz.utc + starts_at = ( + get_datetime(subscription_details.get("start_date")) or frappe.utils.now_datetime() ) + starts_at = starts_at.replace( + tzinfo=pytz.timezone(frappe.utils.get_time_zone()) + ).astimezone(pytz.utc) # "PROFILESTARTDATE": datetime.utcfromtimestamp(get_timestamp(starts_at)).isoformat() params.update({"PROFILESTARTDATE": starts_at.isoformat()}) diff --git a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py index 7c40a18..563519e 100644 --- a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py +++ b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py @@ -4,15 +4,15 @@ import json from urllib.parse import urlencode -import requests -from paytmchecksum import generateSignature, verifySignature - import frappe +import requests from frappe import _ from frappe.integrations.utils import create_request_log from frappe.model.document import Document -from frappe.utils import call_hook_method, cint, cstr, flt, get_request_site_address, get_url +from frappe.utils import (call_hook_method, cint, cstr, flt, + get_request_site_address, get_url) from frappe.utils.password import get_decrypted_password +from paytmchecksum import generateSignature, verifySignature from payments.utils import create_payment_gateway @@ -46,7 +46,11 @@ def get_paytm_config(): paytm_config = frappe.db.get_singles_dict("Paytm Settings") paytm_config.update( - dict(merchant_key=get_decrypted_password("Paytm Settings", "Paytm Settings", "merchant_key")) + dict( + merchant_key=get_decrypted_password( + "Paytm Settings", "Paytm Settings", "merchant_key" + ) + ) ) if cint(paytm_config.staging): @@ -110,7 +114,9 @@ def verify_transaction(**paytm_params): if paytm_params and paytm_config and paytm_checksum: # Verify checksum - is_valid_checksum = verifySignature(paytm_params, paytm_config.merchant_key, paytm_checksum) + is_valid_checksum = verifySignature( + paytm_params, paytm_config.merchant_key, paytm_checksum + ) if is_valid_checksum and paytm_params.get("RESPCODE") == "01": verify_transaction_status(paytm_config, paytm_params["ORDERID"]) @@ -136,7 +142,9 @@ def verify_transaction_status(paytm_config, order_id): post_data = json.dumps(paytm_params) url = paytm_config.transaction_status_url - response = requests.post(url, data=post_data, headers={"Content-type": "application/json"}).json() + response = requests.post( + url, data=post_data, headers={"Content-type": "application/json"} + ).json() finalize_request(order_id, response) diff --git a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py index db21bd8..a3fec60 100644 --- a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py +++ b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py @@ -67,11 +67,8 @@ from urllib.parse import urlencode import frappe import razorpay from frappe import _ -from frappe.integrations.utils import ( - create_request_log, - make_get_request, - make_post_request, -) +from frappe.integrations.utils import (create_request_log, make_get_request, + make_post_request) from frappe.model.document import Document from frappe.utils import call_hook_method, cint, get_timestamp, get_url diff --git a/payments/templates/pages/braintree_checkout.py b/payments/templates/pages/braintree_checkout.py index ce79cd8..3d0bea3 100644 --- a/payments/templates/pages/braintree_checkout.py +++ b/payments/templates/pages/braintree_checkout.py @@ -8,9 +8,7 @@ from frappe import _ from frappe.utils import flt from payments.payment_gateways.doctype.braintree_settings.braintree_settings import ( - get_client_token, - get_gateway_controller, -) + get_client_token, get_gateway_controller) no_cache = 1 diff --git a/payments/templates/pages/paytm_checkout.py b/payments/templates/pages/paytm_checkout.py index 216b0b2..857ec37 100644 --- a/payments/templates/pages/paytm_checkout.py +++ b/payments/templates/pages/paytm_checkout.py @@ -6,9 +6,7 @@ import frappe from frappe import _ from payments.payment_gateways.doctype.paytm_settings.paytm_settings import ( - get_paytm_config, - get_paytm_params, -) + get_paytm_config, get_paytm_params) def get_context(context): @@ -18,7 +16,9 @@ def get_context(context): try: doc = frappe.get_doc("Integration Request", frappe.form_dict["order_id"]) - context.payment_details = get_paytm_params(json.loads(doc.data), doc.name, paytm_config) + context.payment_details = get_paytm_params( + json.loads(doc.data), doc.name, paytm_config + ) context.url = paytm_config.url diff --git a/payments/templates/pages/razorpay_checkout.py b/payments/templates/pages/razorpay_checkout.py index d0e77f6..76eaabc 100644 --- a/payments/templates/pages/razorpay_checkout.py +++ b/payments/templates/pages/razorpay_checkout.py @@ -59,7 +59,9 @@ def get_api_key(): @frappe.whitelist(allow_guest=True) -def make_payment(razorpay_payment_id, options, reference_doctype, reference_docname, token): +def make_payment( + razorpay_payment_id, options, reference_doctype, reference_docname, token +): data = {} if isinstance(options, str): diff --git a/payments/templates/pages/stripe_checkout.py b/payments/templates/pages/stripe_checkout.py index 242eb9e..359df64 100644 --- a/payments/templates/pages/stripe_checkout.py +++ b/payments/templates/pages/stripe_checkout.py @@ -6,7 +6,8 @@ import frappe from frappe import _ from frappe.utils import cint, fmt_money -from payments.payment_gateways.doctype.stripe_settings.stripe_settings import get_gateway_controller +from payments.payment_gateways.doctype.stripe_settings.stripe_settings import \ + get_gateway_controller no_cache = 1 @@ -31,7 +32,9 @@ def get_context(context): for key in expected_keys: context[key] = frappe.form_dict[key] - gateway_controller = get_gateway_controller(context.reference_doctype, context.reference_docname) + gateway_controller = get_gateway_controller( + context.reference_doctype, context.reference_docname + ) context.publishable_key = get_api_key(context.reference_docname, gateway_controller) context.image = get_header_image(context.reference_docname, gateway_controller) @@ -48,14 +51,18 @@ def get_context(context): else: frappe.redirect_to_message( _("Some information is missing"), - _("Looks like someone sent you to an incomplete URL. Please ask them to look into it."), + _( + "Looks like someone sent you to an incomplete URL. Please ask them to look into it." + ), ) frappe.local.flags.redirect_location = frappe.local.response.location raise frappe.Redirect def get_api_key(doc, gateway_controller): - publishable_key = frappe.db.get_value("Stripe Settings", gateway_controller, "publishable_key") + publishable_key = frappe.db.get_value( + "Stripe Settings", gateway_controller, "publishable_key" + ) if cint(frappe.form_dict.get("use_sandbox")): publishable_key = frappe.conf.sandbox_publishable_key diff --git a/payments/utils/__init__.py b/payments/utils/__init__.py index 518a939..0b8d071 100644 --- a/payments/utils/__init__.py +++ b/payments/utils/__init__.py @@ -1,7 +1,4 @@ -from payments.utils.utils import ( - before_install, - create_payment_gateway, - delete_custom_fields, - get_payment_gateway_controller, - make_custom_fields, -) +from payments.utils.utils import (before_install, create_payment_gateway, + delete_custom_fields, + get_payment_gateway_controller, + make_custom_fields) diff --git a/payments/utils/utils.py b/payments/utils/utils.py index a34c16d..0caa8bd 100644 --- a/payments/utils/utils.py +++ b/payments/utils/utils.py @@ -1,9 +1,9 @@ import click - import frappe from frappe import _ +from frappe.custom.doctype.custom_field.custom_field import \ + create_custom_fields from frappe.utils.data import cint -from frappe.custom.doctype.custom_field.custom_field import create_custom_fields def get_payment_gateway_controller(payment_gateway): @@ -58,81 +58,83 @@ def make_custom_fields(): if not frappe.get_meta("Web Form").has_field("payments_tab"): click.secho("* Installing Payment Custom Fields in Web Form") - create_custom_fields({ - 'Web Form': [ - { - "fieldname": "payments_tab", - "fieldtype": "Tab Break", - "label": "Payments", - "insert_after": "custom_css" - }, - { - "default": "0", - "fieldname": "accept_payment", - "fieldtype": "Check", - "label": "Accept Payment", - "insert_after": "payments" - }, - { - "depends_on": "accept_payment", - "fieldname": "payment_gateway", - "fieldtype": "Link", - "label": "Payment Gateway", - "options": "Payment Gateway", - "insert_after": "accept_payment" - }, - { - "default": "Buy Now", - "depends_on": "accept_payment", - "fieldname": "payment_button_label", - "fieldtype": "Data", - "label": "Button Label", - "insert_after": "payment_gateway" - }, - { - "depends_on": "accept_payment", - "fieldname": "payment_button_help", - "fieldtype": "Text", - "label": "Button Help", - "insert_after": "payment_button_label" - }, - { - "fieldname": "payments_cb", - "fieldtype": "Column Break", - "insert_after": "payment_button_help" - }, - { - "default": "0", - "depends_on": "accept_payment", - "fieldname": "amount_based_on_field", - "fieldtype": "Check", - "label": "Amount Based On Field", - "insert_after": "payments_cb" - }, - { - "depends_on": "eval:doc.accept_payment && doc.amount_based_on_field", - "fieldname": "amount_field", - "fieldtype": "Select", - "label": "Amount Field", - "insert_after": "amount_based_on_field" - }, - { - "depends_on": "eval:doc.accept_payment && !doc.amount_based_on_field", - "fieldname": "amount", - "fieldtype": "Currency", - "label": "Amount", - "insert_after": "amount_field" - }, - { - "depends_on": "accept_payment", - "fieldname": "currency", - "fieldtype": "Link", - "label": "Currency", - "options": "Currency", - "insert_after": "amount" - } - ] - }) + create_custom_fields( + { + "Web Form": [ + { + "fieldname": "payments_tab", + "fieldtype": "Tab Break", + "label": "Payments", + "insert_after": "custom_css", + }, + { + "default": "0", + "fieldname": "accept_payment", + "fieldtype": "Check", + "label": "Accept Payment", + "insert_after": "payments", + }, + { + "depends_on": "accept_payment", + "fieldname": "payment_gateway", + "fieldtype": "Link", + "label": "Payment Gateway", + "options": "Payment Gateway", + "insert_after": "accept_payment", + }, + { + "default": "Buy Now", + "depends_on": "accept_payment", + "fieldname": "payment_button_label", + "fieldtype": "Data", + "label": "Button Label", + "insert_after": "payment_gateway", + }, + { + "depends_on": "accept_payment", + "fieldname": "payment_button_help", + "fieldtype": "Text", + "label": "Button Help", + "insert_after": "payment_button_label", + }, + { + "fieldname": "payments_cb", + "fieldtype": "Column Break", + "insert_after": "payment_button_help", + }, + { + "default": "0", + "depends_on": "accept_payment", + "fieldname": "amount_based_on_field", + "fieldtype": "Check", + "label": "Amount Based On Field", + "insert_after": "payments_cb", + }, + { + "depends_on": "eval:doc.accept_payment && doc.amount_based_on_field", + "fieldname": "amount_field", + "fieldtype": "Select", + "label": "Amount Field", + "insert_after": "amount_based_on_field", + }, + { + "depends_on": "eval:doc.accept_payment && !doc.amount_based_on_field", + "fieldname": "amount", + "fieldtype": "Currency", + "label": "Amount", + "insert_after": "amount_field", + }, + { + "depends_on": "accept_payment", + "fieldname": "currency", + "fieldtype": "Link", + "label": "Currency", + "options": "Currency", + "insert_after": "amount", + }, + ] + } + ) frappe.clear_cache(doctype="Web Form") @@ -151,19 +153,18 @@ def delete_custom_fields(): "amount_field", "amount_based_on_field", "amount", - "currency" + "currency", ) for fieldname in fieldnames: - frappe.db.delete( - "Custom Field", - {"name": "Web Form-" + fieldname} - ) + frappe.db.delete("Custom Field", {"name": "Web Form-" + fieldname}) frappe.clear_cache(doctype="Web Form") def before_install(): # only install for v14 - if cint(frappe.get_module("frappe").__version__[:2]) < 14 or not frappe.get_meta("Module Def").has_field("custom"): + if cint(frappe.get_module("frappe").__version__[:2]) < 14 or not frappe.get_meta( + "Module Def" + ).has_field("custom"): return False