From 99f9955afb9f98f7d46ca9dadce4fb81c02c9b98 Mon Sep 17 00:00:00 2001 From: Avunu LLC Date: Mon, 2 Oct 2023 16:54:39 -0400 Subject: [PATCH 01/27] feat: dynamic payer name & email from form fields --- payments/hooks.py | 8 +- payments/overrides/payment_webform.py | 16 ++- payments/payments/custom/web_form.json | 154 +++++++++++++++++++++++++ payments/public/js/web_form.js | 76 ++++++++++++ payments/utils/__init__.py | 6 +- payments/utils/utils.py | 109 ----------------- 6 files changed, 250 insertions(+), 119 deletions(-) create mode 100644 payments/payments/custom/web_form.json create mode 100644 payments/public/js/web_form.js diff --git a/payments/hooks.py b/payments/hooks.py index 4ad29caf..8193cb6d 100644 --- a/payments/hooks.py +++ b/payments/hooks.py @@ -29,7 +29,7 @@ # page_js = {"page" : "public/js/file.js"} # include js in doctype views -# doctype_js = {"doctype" : "public/js/doctype.js"} +doctype_js = {"Web Form" : "public/js/web_form.js"} # doctype_list_js = {"doctype" : "public/js/doctype_list.js"} # doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"} # doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"} @@ -64,12 +64,12 @@ # ------------ before_install = "payments.utils.before_install" -after_install = "payments.utils.make_custom_fields" +# after_install = "payments.utils.make_custom_fields" # Uninstallation # ------------ -before_uninstall = "payments.utils.delete_custom_fields" +# before_uninstall = "payments.utils.delete_custom_fields" # after_uninstall = "pay.uninstall.after_uninstall" # Desk Notifications @@ -94,7 +94,7 @@ # --------------- # Override standard doctype classes -override_doctype_class = {"Web Form": "payments.overrides.payment_webform.PaymentWebForm"} +override_doctype_class = {"Web Form": "payments.overrides.payment_webform.PaymentWebForm"} # Document Events # --------------- diff --git a/payments/overrides/payment_webform.py b/payments/overrides/payment_webform.py index 3b2d7afa..42789d8d 100644 --- a/payments/overrides/payment_webform.py +++ b/payments/overrides/payment_webform.py @@ -27,6 +27,8 @@ def get_payment_gateway_url(self, doc): controller = get_payment_gateway_controller(self.payment_gateway) title = f"Payment for {doc.doctype} {doc.name}" + + # Set Amount amount = self.amount if self.amount_based_on_field: amount = doc.get(self.amount_field) @@ -35,6 +37,16 @@ def get_payment_gateway_url(self, doc): if amount is None or Decimal(amount) <= 0: return frappe.utils.get_url(self.success_url or self.route) + + # Set Payer Name + payer_name = frappe.utils.get_fullname(frappe.session.user) + if self.payer_name_based_on_field: + payer_name = doc.get(self.payer_name_field) + + # Set Payer Email + payer_email = frappe.session.user + if self.payer_email_based_on_field: + payer_email = doc.get(self.payer_email_field) payment_details = { "amount": amount, @@ -42,8 +54,8 @@ def get_payment_gateway_url(self, doc): "description": title, "reference_doctype": doc.doctype, "reference_docname": doc.name, - "payer_email": frappe.session.user, - "payer_name": frappe.utils.get_fullname(frappe.session.user), + "payer_email": payer_email, + "payer_name": payer_name, "order_id": doc.name, "currency": self.currency, "redirect_to": frappe.utils.get_url(self.success_url or self.route), diff --git a/payments/payments/custom/web_form.json b/payments/payments/custom/web_form.json new file mode 100644 index 00000000..50172401 --- /dev/null +++ b/payments/payments/custom/web_form.json @@ -0,0 +1,154 @@ +{ + "custom_fields": [ + { + "dt": "Web Form", + "fieldname": "payments_tab", + "fieldtype": "Tab Break", + "label": "Payments", + "insert_after": "custom_css", + "is_system_generated": 1 + }, + { + "dt": "Web Form", + "fieldname": "payments_sb_0", + "fieldtype": "Section Break", + "insert_after": "payments_tab", + "is_system_generated": 1 + }, + { + "default": "0", + "dt": "Web Form", + "fieldname": "accept_payment", + "fieldtype": "Check", + "label": "Accept Payments", + "insert_after": "payments_sb_0", + "is_system_generated": 1 + }, + { + "dt": "Web Form", + "fieldname": "payments_cb", + "fieldtype": "Column Break", + "insert_after": "accept_payment", + "is_system_generated": 1 + }, + { + "default": "0", + "depends_on": "accept_payment", + "dt": "Web Form", + "fieldname": "amount_based_on_field", + "fieldtype": "Check", + "label": "Amount Based On Field", + "insert_after": "payments_cb", + "is_system_generated": 1 + }, + { + "depends_on": "eval:doc.accept_payment && doc.amount_based_on_field", + "dt": "Web Form", + "fieldname": "amount_field", + "fieldtype": "Select", + "label": "Amount Field", + "insert_after": "amount_based_on_field", + "is_system_generated": 1 + }, + { + "default": "0", + "depends_on": "accept_payment", + "dt": "Web Form", + "fieldname": "payer_name_based_on_field", + "fieldtype": "Check", + "label": "Payer Name Based On Field", + "insert_after": "amount_field", + "is_system_generated": 1 + }, + { + "depends_on": "eval:doc.accept_payment && doc.payer_name_based_on_field", + "dt": "Web Form", + "fieldname": "payer_name_field", + "fieldtype": "Select", + "label": "Payer Name Field", + "insert_after": "payer_name_based_on_field", + "is_system_generated": 1 + }, + { + "default": "0", + "depends_on": "accept_payment", + "dt": "Web Form", + "fieldname": "payer_email_based_on_field", + "fieldtype": "Check", + "label": "Payer Email Based On Field", + "insert_after": "payer_name_field", + "is_system_generated": 1 + }, + { + "depends_on": "eval:doc.accept_payment && doc.payer_email_based_on_field", + "dt": "Web Form", + "fieldname": "payer_email_field", + "fieldtype": "Select", + "label": "Payer Email Field", + "insert_after": "payer_email_based_on_field", + "is_system_generated": 1 + }, + { + "depends_on": "accept_payment", + "dt": "Web Form", + "fieldname": "payments_sb_1", + "fieldtype": "Section Break", + "insert_after": "payer_email_field", + "is_system_generated": 1 + }, + { + "default": "Buy Now", + "dt": "Web Form", + "fieldname": "payment_button_label", + "fieldtype": "Data", + "label": "Button Label", + "insert_after": "payments_sb_1", + "is_system_generated": 1 + }, + { + "dt": "Web Form", + "fieldname": "payment_button_help", + "fieldtype": "Small Text", + "label": "Button Help", + "insert_after": "payment_button_label", + "is_system_generated": 1 + }, + { + "dt": "Web Form", + "fieldname": "payments_cb_1", + "fieldtype": "Column Break", + "insert_after": "payment_button_help", + "is_system_generated": 1 + }, + { + "depends_on": "eval:!doc.amount_based_on_field", + "dt": "Web Form", + "fieldname": "amount", + "fieldtype": "Currency", + "label": "Amount", + "insert_after": "payments_cb_1", + "is_system_generated": 1 + }, + { + "dt": "Web Form", + "fieldname": "currency", + "fieldtype": "Link", + "label": "Currency", + "options": "Currency", + "insert_after": "amount", + "is_system_generated": 1 + }, + { + "dt": "Web Form", + "fieldname": "payment_gateway", + "fieldtype": "Link", + "label": "Payment Gateway", + "options": "Payment Gateway", + "insert_after": "currency", + "is_system_generated": 1 + } + ], + "property_setters": [], + "sync_on_migrate": 1, + "doctype": "Web Form" +} \ No newline at end of file diff --git a/payments/public/js/web_form.js b/payments/public/js/web_form.js new file mode 100644 index 00000000..33bbd61e --- /dev/null +++ b/payments/public/js/web_form.js @@ -0,0 +1,76 @@ +frappe.ui.form.on("Web Form", { + set_fields(frm) { + let doc = frm.doc; + + let update_options = (options) => { + [frm.fields_dict.web_form_fields.grid, frm.fields_dict.list_columns.grid].forEach( + (obj) => { + obj.update_docfield_property("fieldname", "options", options); + } + ); + }; + + if (!doc.doc_type) { + update_options([]); + frm.set_df_property("amount_field", "options", []); + frm.set_df_property("payer_name_field", "options", []); + frm.set_df_property("payer_email_field", "options", []); + return; + } + + update_options([`Fetching fields from ${doc.doc_type}...`]); + + get_fields_for_doctype(doc.doc_type).then((fields) => { + let as_select_option = (df) => ({ + label: df.label, + value: df.fieldname, + }); + update_options(fields.map(as_select_option)); + + // Amount Field Options + let currency_fields = fields + .filter((df) => ["Currency", "Float"].includes(df.fieldtype)) + .map(as_select_option); + if (!currency_fields.length) { + currency_fields = [ + { + label: `No currency fields in ${doc.doc_type}`, + value: "", + disabled: true, + }, + ]; + } + frm.set_df_property("amount_field", "options", currency_fields); + + // Payer Name Field Options + let payer_name_fields = fields + .filter((df) => ["Data", "Link"].includes(df.fieldtype)) + .map(as_select_option); + if (!payer_name_fields.length) { + payer_name_fields = [ + { + label: `No data or link fields in ${doc.doc_type}`, + value: "", + disabled: true, + }, + ]; + } + frm.set_df_property("payer_name_field", "options", payer_name_fields); + + // Payer Email Field Options + let payer_email_fields = fields + .filter((df) => ["Email"].includes(df.options)) + .map(as_select_option); + if (!payer_email_fields.length) { + payer_email_fields = [ + { + label: `No email fields in ${doc.doc_type}`, + value: "", + disabled: true, + }, + ]; + } + frm.set_df_property("payer_email_field", "options", payer_email_fields); + }); + } +}); diff --git a/payments/utils/__init__.py b/payments/utils/__init__.py index 54a5c2e8..a42d725d 100644 --- a/payments/utils/__init__.py +++ b/payments/utils/__init__.py @@ -1,7 +1,5 @@ from payments.utils.utils import ( before_install, create_payment_gateway, - delete_custom_fields, - get_payment_gateway_controller, - make_custom_fields, -) + get_payment_gateway_controller + ) diff --git a/payments/utils/utils.py b/payments/utils/utils.py index 20c6bd1e..a43033b0 100644 --- a/payments/utils/utils.py +++ b/payments/utils/utils.py @@ -1,7 +1,6 @@ import click import frappe from frappe import _ -from frappe.custom.doctype.custom_field.custom_field import create_custom_fields def get_payment_gateway_controller(payment_gateway): @@ -52,114 +51,6 @@ def create_payment_gateway(gateway, settings=None, controller=None): payment_gateway.insert(ignore_permissions=True) -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", - }, - ] - } - ) - - frappe.clear_cache(doctype="Web Form") - - -def delete_custom_fields(): - if frappe.get_meta("Web Form").has_field("payments_tab"): - click.secho("* Uninstalling Payment Custom Fields from Web Form") - - fieldnames = ( - "payments_tab", - "accept_payment", - "payment_gateway", - "payment_button_label", - "payment_button_help", - "payments_cb", - "amount_field", - "amount_based_on_field", - "amount", - "currency", - ) - - for fieldname in fieldnames: - frappe.db.delete("Custom Field", {"name": "Web Form-" + fieldname}) - - frappe.clear_cache(doctype="Web Form") - - def before_install(): # TODO: remove this # This is done for erpnext CI patch test From e1d37a3998a9c2cd6b5df7930cd394bdf9dfe80f Mon Sep 17 00:00:00 2001 From: Avunu LLC Date: Mon, 2 Oct 2023 16:58:37 -0400 Subject: [PATCH 02/27] fix: remove whitespace --- payments/hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/payments/hooks.py b/payments/hooks.py index 8193cb6d..65bb729f 100644 --- a/payments/hooks.py +++ b/payments/hooks.py @@ -94,7 +94,7 @@ # --------------- # Override standard doctype classes -override_doctype_class = {"Web Form": "payments.overrides.payment_webform.PaymentWebForm"} +override_doctype_class = {"Web Form": "payments.overrides.payment_webform.PaymentWebForm"} # Document Events # --------------- From 7878942d8b5f4dfd4ba1787554a8bfc97fc17528 Mon Sep 17 00:00:00 2001 From: Avunu LLC Date: Mon, 2 Oct 2023 17:16:33 -0400 Subject: [PATCH 03/27] fix: remove "Button Label" & "Button Help" fields --- payments/payments/custom/web_form.json | 112 +++++++++---------------- 1 file changed, 39 insertions(+), 73 deletions(-) diff --git a/payments/payments/custom/web_form.json b/payments/payments/custom/web_form.json index 50172401..bf0aeaa1 100644 --- a/payments/payments/custom/web_form.json +++ b/payments/payments/custom/web_form.json @@ -9,31 +9,58 @@ "is_system_generated": 1 }, { + "default": "0", "dt": "Web Form", - "fieldname": "payments_sb_0", - "fieldtype": "Section Break", + "fieldname": "accept_payment", + "fieldtype": "Check", + "label": "Accept Payments", "insert_after": "payments_tab", "is_system_generated": 1 }, { - "default": "0", + "depends_on": "accept_payment", "dt": "Web Form", - "fieldname": "accept_payment", - "fieldtype": "Check", - "label": "Accept Payments", - "insert_after": "payments_sb_0", + "fieldname": "payments_sb", + "fieldtype": "Section Break", + "insert_after": "accept_payment", + "is_system_generated": 1 + }, + { + "depends_on": "eval:!doc.amount_based_on_field", + "dt": "Web Form", + "fieldname": "amount", + "fieldtype": "Currency", + "label": "Amount", + "insert_after": "payments_sb", + "is_system_generated": 1 + }, + { + "dt": "Web Form", + "fieldname": "currency", + "fieldtype": "Link", + "label": "Currency", + "options": "Currency", + "insert_after": "amount", + "is_system_generated": 1 + }, + { + "dt": "Web Form", + "fieldname": "payment_gateway", + "fieldtype": "Link", + "label": "Payment Gateway", + "options": "Payment Gateway", + "insert_after": "currency", "is_system_generated": 1 }, { "dt": "Web Form", "fieldname": "payments_cb", "fieldtype": "Column Break", - "insert_after": "accept_payment", + "insert_after": "payment_gateway", "is_system_generated": 1 }, { "default": "0", - "depends_on": "accept_payment", "dt": "Web Form", "fieldname": "amount_based_on_field", "fieldtype": "Check", @@ -42,7 +69,7 @@ "is_system_generated": 1 }, { - "depends_on": "eval:doc.accept_payment && doc.amount_based_on_field", + "depends_on": "eval:doc.amount_based_on_field", "dt": "Web Form", "fieldname": "amount_field", "fieldtype": "Select", @@ -52,7 +79,6 @@ }, { "default": "0", - "depends_on": "accept_payment", "dt": "Web Form", "fieldname": "payer_name_based_on_field", "fieldtype": "Check", @@ -61,7 +87,7 @@ "is_system_generated": 1 }, { - "depends_on": "eval:doc.accept_payment && doc.payer_name_based_on_field", + "depends_on": "eval:doc.payer_name_based_on_field", "dt": "Web Form", "fieldname": "payer_name_field", "fieldtype": "Select", @@ -71,7 +97,6 @@ }, { "default": "0", - "depends_on": "accept_payment", "dt": "Web Form", "fieldname": "payer_email_based_on_field", "fieldtype": "Check", @@ -80,72 +105,13 @@ "is_system_generated": 1 }, { - "depends_on": "eval:doc.accept_payment && doc.payer_email_based_on_field", + "depends_on": "eval:doc.payer_email_based_on_field", "dt": "Web Form", "fieldname": "payer_email_field", "fieldtype": "Select", "label": "Payer Email Field", "insert_after": "payer_email_based_on_field", "is_system_generated": 1 - }, - { - "depends_on": "accept_payment", - "dt": "Web Form", - "fieldname": "payments_sb_1", - "fieldtype": "Section Break", - "insert_after": "payer_email_field", - "is_system_generated": 1 - }, - { - "default": "Buy Now", - "dt": "Web Form", - "fieldname": "payment_button_label", - "fieldtype": "Data", - "label": "Button Label", - "insert_after": "payments_sb_1", - "is_system_generated": 1 - }, - { - "dt": "Web Form", - "fieldname": "payment_button_help", - "fieldtype": "Small Text", - "label": "Button Help", - "insert_after": "payment_button_label", - "is_system_generated": 1 - }, - { - "dt": "Web Form", - "fieldname": "payments_cb_1", - "fieldtype": "Column Break", - "insert_after": "payment_button_help", - "is_system_generated": 1 - }, - { - "depends_on": "eval:!doc.amount_based_on_field", - "dt": "Web Form", - "fieldname": "amount", - "fieldtype": "Currency", - "label": "Amount", - "insert_after": "payments_cb_1", - "is_system_generated": 1 - }, - { - "dt": "Web Form", - "fieldname": "currency", - "fieldtype": "Link", - "label": "Currency", - "options": "Currency", - "insert_after": "amount", - "is_system_generated": 1 - }, - { - "dt": "Web Form", - "fieldname": "payment_gateway", - "fieldtype": "Link", - "label": "Payment Gateway", - "options": "Payment Gateway", - "insert_after": "currency", - "is_system_generated": 1 } ], "property_setters": [], From f6a790eb698ee8fe7c116248b2bdb92f0bd5ee22 Mon Sep 17 00:00:00 2001 From: Avunu LLC Date: Tue, 5 Dec 2023 21:04:01 -0500 Subject: [PATCH 04/27] Add line in override to set payment gateway. Modify pyproject.toml and .gitignore to support newer versions of stripe. --- .gitignore | 1 + payments/overrides/payment_webform.py | 2 ++ pyproject.toml | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 175055b1..e7dc1714 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .DS_Store +*~ *.pyc *.egg-info *.swp diff --git a/payments/overrides/payment_webform.py b/payments/overrides/payment_webform.py index 42789d8d..6b258a9e 100644 --- a/payments/overrides/payment_webform.py +++ b/payments/overrides/payment_webform.py @@ -113,6 +113,8 @@ def accept(web_form, data, docname=None, for_payment=False): if for_payment: web_form.validate_mandatory(doc) doc.run_method("validate_payment") + # set payment gateway + doc.set("payment_gateway", web_form.payment_gateway) if doc.name: if web_form.has_web_form_permission(doc.doctype, doc.name, "write"): diff --git a/pyproject.toml b/pyproject.toml index 46c94ee9..505a93a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ dynamic = ["version"] dependencies = [ "paytmchecksum~=1.7.0", "razorpay~=1.2.0", - "stripe~=2.56.0", + "stripe>=2.56.0,<=5.0.0", "braintree~=4.20.0", "pycryptodome~=3.18.0", ] From af62692cc5e980875f8b125fa49f4e97c8642da2 Mon Sep 17 00:00:00 2001 From: Avunu LLC Date: Thu, 7 Dec 2023 16:17:11 -0500 Subject: [PATCH 05/27] Undo unneccessary changes to .gitignore and pyproject.toml made during debugging. Add custom_redirect_to_override option in stripe_settings.py finalize_request() so that the successful payment redirect address can be fully customized. --- .gitignore | 1 - .../doctype/stripe_settings/stripe_settings.py | 5 ++++- pyproject.toml | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index e7dc1714..175055b1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ .DS_Store -*~ *.pyc *.egg-info *.swp diff --git a/payments/payment_gateways/doctype/stripe_settings/stripe_settings.py b/payments/payment_gateways/doctype/stripe_settings/stripe_settings.py index 0547a528..ea371159 100644 --- a/payments/payment_gateways/doctype/stripe_settings/stripe_settings.py +++ b/payments/payment_gateways/doctype/stripe_settings/stripe_settings.py @@ -241,12 +241,13 @@ def finalize_request(self): redirect_to = self.data.get("redirect_to") or None redirect_message = self.data.get("redirect_message") or None status = self.integration_request.status + custom_redirect_to_override = None if self.flags.status_changed_to == "Completed": if self.data.reference_doctype and self.data.reference_docname: custom_redirect_to = None try: - custom_redirect_to = frappe.get_doc( + custom_redirect_to, custom_redirect_to_override = frappe.get_doc( self.data.reference_doctype, self.data.reference_docname ).run_method("on_payment_authorized", self.flags.status_changed_to) except Exception: @@ -267,6 +268,8 @@ def finalize_request(self): redirect_url += "?" + urlencode({"redirect_to": redirect_to}) if redirect_message: redirect_url += "&" + urlencode({"redirect_message": redirect_message}) + if custom_redirect_to_override: + redirect_url = redirect_to return {"redirect_to": redirect_url, "status": status} diff --git a/pyproject.toml b/pyproject.toml index 505a93a3..46c94ee9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ dynamic = ["version"] dependencies = [ "paytmchecksum~=1.7.0", "razorpay~=1.2.0", - "stripe>=2.56.0,<=5.0.0", + "stripe~=2.56.0", "braintree~=4.20.0", "pycryptodome~=3.18.0", ] From 4b54933623297ca0c713de35e9b66dd6f01f54c7 Mon Sep 17 00:00:00 2001 From: Kevin Shenk Date: Tue, 19 Mar 2024 10:07:42 -0400 Subject: [PATCH 06/27] refactor: re-instate make_custom_fields (for ERPNext) --- payments/utils/utils.py | 60 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/payments/utils/utils.py b/payments/utils/utils.py index a43033b0..0c802c55 100644 --- a/payments/utils/utils.py +++ b/payments/utils/utils.py @@ -1,6 +1,8 @@ import click import frappe from frappe import _ +from contextlib import contextmanager +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields def get_payment_gateway_controller(payment_gateway): @@ -51,6 +53,51 @@ def create_payment_gateway(gateway, settings=None, controller=None): payment_gateway.insert(ignore_permissions=True) +def make_custom_fields(): + if "erpnext" in frappe.get_installed_apps(): + custom_fields = { + "GoCardless Mandate": [ + { + "fieldname": "customer", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Customer", + "options": "Customer", + "reqd": 1, + "insert_after": "disabled", + } + ] + } + + create_custom_fields(custom_fields) + + +def delete_custom_fields(): + if frappe.get_meta("Web Form").has_field("payments_tab"): + click.secho("* Uninstalling Payment Custom Fields from Web Form") + + fieldnames = ( + "accept_payment", + "amount_based_on_field", + "amount_field", + "amount", + "currency", + "payer_email_based_on_field", + "payer_email_field", + "payer_name_based_on_field", + "payer_name_field", + "payment_gateway", + "payments_cb", + "payments_sb", + "payments_tab" + ) + + for fieldname in fieldnames: + frappe.db.delete("Custom Field", {"name": "Web Form-" + fieldname}) + + frappe.clear_cache(doctype="Web Form") + + def before_install(): # TODO: remove this # This is done for erpnext CI patch test @@ -62,3 +109,16 @@ def before_install(): # a lot of apis don;t exist in v10 and this is a (at the moment) required app for erpnext. if not frappe.get_meta("Module Def").has_field("custom"): return False + + +@contextmanager +def erpnext_app_import_guard(): + marketplace_link = 'Marketplace' + github_link = 'GitHub' + msg = _("erpnext app is not installed. Please install it from {} or {}").format( + marketplace_link, github_link + ) + try: + yield + except ImportError: + frappe.throw(msg, title=_("Missing ERPNext App")) From 0f5a90aac31d57d90d48c6cb9281cba5c2c85adf Mon Sep 17 00:00:00 2001 From: Kevin Shenk Date: Tue, 19 Mar 2024 10:08:17 -0400 Subject: [PATCH 07/27] refactor: re-enable make_custom_fields hook --- payments/hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/payments/hooks.py b/payments/hooks.py index 65bb729f..5c485dfd 100644 --- a/payments/hooks.py +++ b/payments/hooks.py @@ -64,7 +64,7 @@ # ------------ before_install = "payments.utils.before_install" -# after_install = "payments.utils.make_custom_fields" +after_install = "payments.utils.make_custom_fields" # Uninstallation # ------------ From 7461c40e44eaf2cf49607a209dad48598a50bbf5 Mon Sep 17 00:00:00 2001 From: Kevin Shenk Date: Tue, 19 Mar 2024 10:11:26 -0400 Subject: [PATCH 08/27] refactor: restore utils __init__.py --- payments/utils/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/payments/utils/__init__.py b/payments/utils/__init__.py index a42d725d..fb540bd5 100644 --- a/payments/utils/__init__.py +++ b/payments/utils/__init__.py @@ -1,5 +1,8 @@ from payments.utils.utils import ( before_install, create_payment_gateway, - get_payment_gateway_controller - ) + delete_custom_fields, + get_payment_gateway_controller, + make_custom_fields, + erpnext_app_import_guard, +) From 36bf21fa10f3600fbd810681ceb55d3f32cdb241 Mon Sep 17 00:00:00 2001 From: Kevin Shenk Date: Tue, 19 Mar 2024 10:13:13 -0400 Subject: [PATCH 09/27] refactor: restore delete_custom_fields --- payments/hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/payments/hooks.py b/payments/hooks.py index 5c485dfd..bbd1ada6 100644 --- a/payments/hooks.py +++ b/payments/hooks.py @@ -69,7 +69,7 @@ # Uninstallation # ------------ -# before_uninstall = "payments.utils.delete_custom_fields" +before_uninstall = "payments.utils.delete_custom_fields" # after_uninstall = "pay.uninstall.after_uninstall" # Desk Notifications From 6510e8f6f7251ff1e81c6a14182d85005874564f Mon Sep 17 00:00:00 2001 From: Avunu LLC Date: Wed, 29 May 2024 15:42:23 -0400 Subject: [PATCH 10/27] refactor: relative stripe URL --- .../payment_gateways/doctype/stripe_settings/stripe_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/payments/payment_gateways/doctype/stripe_settings/stripe_settings.py b/payments/payment_gateways/doctype/stripe_settings/stripe_settings.py index dc2dc1cc..0bf5e9e2 100644 --- a/payments/payment_gateways/doctype/stripe_settings/stripe_settings.py +++ b/payments/payment_gateways/doctype/stripe_settings/stripe_settings.py @@ -188,7 +188,7 @@ def validate_minimum_transaction_amount(self, currency, amount): ) def get_payment_url(self, **kwargs): - return get_url(f"./stripe_checkout?{urlencode(kwargs)}") + return f"/stripe_checkout?{urlencode(kwargs)}" def create_request(self, data): import stripe From b3ada19934f4aeeab46d356c3cb3d5bf90c9d134 Mon Sep 17 00:00:00 2001 From: Kevin Shenk Date: Tue, 10 Dec 2024 19:29:37 -0500 Subject: [PATCH 11/27] fmt: black & prettier --- .pre-commit-config.yaml | 2 +- payments/hooks.py | 2 +- payments/overrides/payment_webform.py | 2 +- payments/public/js/web_form.js | 148 ++++++++++++++------------ payments/utils/utils.py | 2 +- 5 files changed, 85 insertions(+), 71 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e9f5b27e..2fb83911 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ exclude: 'node_modules|.git' -default_stages: [commit] +default_stages: [pre-commit] fail_fast: false diff --git a/payments/hooks.py b/payments/hooks.py index 3a77d1f3..a7a25d5c 100644 --- a/payments/hooks.py +++ b/payments/hooks.py @@ -29,7 +29,7 @@ # page_js = {"page" : "public/js/file.js"} # include js in doctype views -doctype_js = {"Web Form" : "public/js/web_form.js"} +doctype_js = {"Web Form": "public/js/web_form.js"} # doctype_list_js = {"doctype" : "public/js/doctype_list.js"} # doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"} # doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"} diff --git a/payments/overrides/payment_webform.py b/payments/overrides/payment_webform.py index 6b258a9e..163433c9 100644 --- a/payments/overrides/payment_webform.py +++ b/payments/overrides/payment_webform.py @@ -37,7 +37,7 @@ def get_payment_gateway_url(self, doc): if amount is None or Decimal(amount) <= 0: return frappe.utils.get_url(self.success_url or self.route) - + # Set Payer Name payer_name = frappe.utils.get_fullname(frappe.session.user) if self.payer_name_based_on_field: diff --git a/payments/public/js/web_form.js b/payments/public/js/web_form.js index 33bbd61e..eb74fc60 100644 --- a/payments/public/js/web_form.js +++ b/payments/public/js/web_form.js @@ -1,76 +1,90 @@ frappe.ui.form.on("Web Form", { - set_fields(frm) { - let doc = frm.doc; + set_fields(frm) { + let doc = frm.doc; - let update_options = (options) => { - [frm.fields_dict.web_form_fields.grid, frm.fields_dict.list_columns.grid].forEach( - (obj) => { - obj.update_docfield_property("fieldname", "options", options); - } - ); - }; + let update_options = (options) => { + [ + frm.fields_dict.web_form_fields.grid, + frm.fields_dict.list_columns.grid, + ].forEach((obj) => { + obj.update_docfield_property("fieldname", "options", options); + }); + }; - if (!doc.doc_type) { - update_options([]); - frm.set_df_property("amount_field", "options", []); - frm.set_df_property("payer_name_field", "options", []); - frm.set_df_property("payer_email_field", "options", []); - return; - } + if (!doc.doc_type) { + update_options([]); + frm.set_df_property("amount_field", "options", []); + frm.set_df_property("payer_name_field", "options", []); + frm.set_df_property("payer_email_field", "options", []); + return; + } - update_options([`Fetching fields from ${doc.doc_type}...`]); + update_options([`Fetching fields from ${doc.doc_type}...`]); - get_fields_for_doctype(doc.doc_type).then((fields) => { - let as_select_option = (df) => ({ - label: df.label, - value: df.fieldname, - }); - update_options(fields.map(as_select_option)); + new Promise((resolve) => + frappe.model.with_doctype(doc.doc_type, resolve) + ).then(() => { + const fields = frappe.meta.get_docfields(doc.doc_type).filter((df) => { + return ( + (frappe.model.is_value_type(df.fieldtype) && + !["lft", "rgt"].includes(df.fieldname)) || + ["Table", "Table Multiselect"].includes(df.fieldtype) || + frappe.model.layout_fields.includes(df.fieldtype) + ); + }); - // Amount Field Options - let currency_fields = fields - .filter((df) => ["Currency", "Float"].includes(df.fieldtype)) - .map(as_select_option); - if (!currency_fields.length) { - currency_fields = [ - { - label: `No currency fields in ${doc.doc_type}`, - value: "", - disabled: true, - }, - ]; - } - frm.set_df_property("amount_field", "options", currency_fields); + let as_select_option = (df) => ({ + label: df.label, + value: df.fieldname, + }); - // Payer Name Field Options - let payer_name_fields = fields - .filter((df) => ["Data", "Link"].includes(df.fieldtype)) - .map(as_select_option); - if (!payer_name_fields.length) { - payer_name_fields = [ - { - label: `No data or link fields in ${doc.doc_type}`, - value: "", - disabled: true, - }, - ]; - } - frm.set_df_property("payer_name_field", "options", payer_name_fields); + // Update main field options + update_options(fields.map(as_select_option)); - // Payer Email Field Options - let payer_email_fields = fields - .filter((df) => ["Email"].includes(df.options)) - .map(as_select_option); - if (!payer_email_fields.length) { - payer_email_fields = [ - { - label: `No email fields in ${doc.doc_type}`, - value: "", - disabled: true, - }, - ]; - } - frm.set_df_property("payer_email_field", "options", payer_email_fields); - }); - } + // Amount Field Options + let currency_fields = fields + .filter((df) => ["Currency", "Float"].includes(df.fieldtype)) + .map(as_select_option); + if (!currency_fields.length) { + currency_fields = [ + { + label: `No currency fields in ${doc.doc_type}`, + value: "", + disabled: true, + }, + ]; + } + frm.set_df_property("amount_field", "options", currency_fields); + + // Payer Name Field Options + let payer_name_fields = fields + .filter((df) => ["Data", "Link"].includes(df.fieldtype)) + .map(as_select_option); + if (!payer_name_fields.length) { + payer_name_fields = [ + { + label: `No data or link fields in ${doc.doc_type}`, + value: "", + disabled: true, + }, + ]; + } + frm.set_df_property("payer_name_field", "options", payer_name_fields); + + // Payer Email Field Options + let payer_email_fields = fields + .filter((df) => ["Email"].includes(df.options)) + .map(as_select_option); + if (!payer_email_fields.length) { + payer_email_fields = [ + { + label: `No email fields in ${doc.doc_type}`, + value: "", + disabled: true, + }, + ]; + } + frm.set_df_property("payer_email_field", "options", payer_email_fields); + }); + }, }); diff --git a/payments/utils/utils.py b/payments/utils/utils.py index e24f78c3..40665ee8 100644 --- a/payments/utils/utils.py +++ b/payments/utils/utils.py @@ -112,7 +112,7 @@ def delete_custom_fields(): "payment_gateway", "payments_cb", "payments_sb", - "payments_tab" + "payments_tab", ) for fieldname in fieldnames: From 134048df805bdee6cbdf0d0c4568ce88edfdc5f2 Mon Sep 17 00:00:00 2001 From: Avunu LLC Date: Mon, 30 Dec 2024 12:07:14 -0500 Subject: [PATCH 12/27] fix: add payment_gateway to payment_details --- payments/overrides/payment_webform.py | 1 + 1 file changed, 1 insertion(+) diff --git a/payments/overrides/payment_webform.py b/payments/overrides/payment_webform.py index 163433c9..c1a4b56d 100644 --- a/payments/overrides/payment_webform.py +++ b/payments/overrides/payment_webform.py @@ -54,6 +54,7 @@ def get_payment_gateway_url(self, doc): "description": title, "reference_doctype": doc.doctype, "reference_docname": doc.name, + "payment_gateway": self.payment_gateway, "payer_email": payer_email, "payer_name": payer_name, "order_id": doc.name, From 98feda40fbb1fd2210bc12eba28be748ab4f33a9 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 6 May 2025 17:08:17 +0530 Subject: [PATCH 13/27] chore: fix broken CI bump all github actions and bring them to parity with erpnext --- .github/helper/install.sh | 2 +- .github/workflows/ci.yml | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/helper/install.sh b/.github/helper/install.sh index e1efaff1..3e386d6b 100644 --- a/.github/helper/install.sh +++ b/.github/helper/install.sh @@ -6,7 +6,7 @@ cd ~ || exit sudo apt update sudo apt remove mysql-server mysql-client -sudo apt install libcups2-dev redis-server mariadb-client-10.6 +sudo apt install libcups2-dev redis-server mariadb-client pip install frappe-bench diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bfe78a79..4d94a1f5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,10 +43,10 @@ jobs: steps: - name: Clone - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: '3.10' @@ -59,7 +59,7 @@ jobs: fi - name: Setup Node - uses: actions/setup-node@v2 + uses: actions/setup-node@v4 with: node-version: 18 check-latest: true @@ -68,7 +68,7 @@ jobs: run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts - name: Cache pip - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }} @@ -77,7 +77,7 @@ jobs: ${{ runner.os }}- - name: Cache node modules - uses: actions/cache@v2 + uses: actions/cache@v4 env: cache-name: cache-node-modules with: @@ -92,7 +92,7 @@ jobs: id: yarn-cache-dir-path run: echo "::set-output name=dir::$(yarn cache dir)" - - uses: actions/cache@v2 + - uses: actions/cache@v4 id: yarn-cache with: path: ${{ steps.yarn-cache-dir-path.outputs.dir }} @@ -114,7 +114,7 @@ jobs: CAPTURE_COVERAGE: ${{ github.event_name != 'pull_request' }} - name: Upload coverage data - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: github.event_name != 'pull_request' with: name: coverage-${{ matrix.container }} @@ -130,7 +130,7 @@ jobs: uses: actions/checkout@v4 - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 - name: Upload coverage data uses: codecov/codecov-action@v4 From 8c815f4d56ef23bdc41884fdc63d5a70afb3ac1c Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Fri, 9 May 2025 11:10:36 +0530 Subject: [PATCH 14/27] test(mpesa-settings): add opening entry for new POS profile --- .../doctype/mpesa_settings/test_mpesa_settings.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 c0eb227e..ce5dc88a 100644 --- a/payments/payment_gateways/doctype/mpesa_settings/test_mpesa_settings.py +++ b/payments/payment_gateways/doctype/mpesa_settings/test_mpesa_settings.py @@ -7,6 +7,7 @@ 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.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile from erpnext.stock.doctype.item.test_item import make_item @@ -26,7 +27,7 @@ def setUp(self): self.customer = create_customer("_Test Customer", "USD") self.item = make_item(properties={"is_stock_item": 1}).name - self.pos_profile = make_pos_profile( + pos_profile = make_pos_profile( company="Wind Power LLC", cost_center="Main - WP", currency="USD", @@ -37,7 +38,9 @@ def setUp(self): warehouse="Stores - WP", write_off_account="Write Off - WP", write_off_cost_center="Main - WP", - ).name + ) + self.pos_profile = pos_profile.name + create_opening_entry(pos_profile, frappe.session.user) def tearDown(self): frappe.db.sql("delete from `tabMpesa Settings`") From f0045ba86ab81ebd25402339c566bf8290eb18bb Mon Sep 17 00:00:00 2001 From: Karuppasamy923 Date: Tue, 6 May 2025 13:44:32 +0530 Subject: [PATCH 15/27] chore: extend supported currencies list for Razorpay. --- .../doctype/razorpay_settings/razorpay_settings.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py index 4e77afd8..bd455136 100644 --- a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py +++ b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py @@ -79,7 +79,19 @@ def on_payment_authorized(payment_status): class RazorpaySettings(Document): - supported_currencies = ("INR",) + supported_currencies = ("AED","ALL","AMD","ARS","AUD","AWG","AZN","BAM","BBD","BDT", + "BGN","BHD","BIF","BMD","BND","BOB","BRL","BSD","BTN","BWP", + "BZD","CAD","CHF","CLP","CNY","COP","CRC","CUP","CVE","CZK", + "DJF","DKK","DOP","DZD","EGP","ETB","EUR","FJD","GBP","GHS", + "GIP","GMD","GNF","GTQ","GYD","HKD","HNL","HRK","HTG","HUF", + "IDR","ILS","INR","IQD","ISK","JMD","JOD","JPY","KES","KGS", + "KHR","KMF","KRW","KWD","KYD","KZT","LAK","LKR","LRD","LSL", + "MAD","MDL","MGA","MKD","MMK","MNT","MOP","MUR","MVR","MWK", + "MXN","MYR","MZN","NAD","NGN","NIO","NOK","NPR","NZD","OMR", + "PEN","PGK","PHP","PKR","PLN","PYG","QAR","RON","RSD","RUB", + "RWF","SAR","SCR","SEK","SGD","SLL","SOS","SSP","SVC","SZL", + "THB","TND","TRY","TTD","TWD","TZS","UAH","UGX","USD","UYU", + "UZS","VND","VUV","XAF","XCD","XOF","XPF","YER","ZAR","ZMW") def init_client(self): if self.api_key: From d9fd414ca51540338378467f7284c9d59fd7e716 Mon Sep 17 00:00:00 2001 From: Karuppasamy923 Date: Tue, 6 May 2025 16:20:20 +0530 Subject: [PATCH 16/27] chore: Update formatings --- .../razorpay_settings/razorpay_settings.py | 145 ++++++++++++++++-- 1 file changed, 132 insertions(+), 13 deletions(-) diff --git a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py index bd455136..20815477 100644 --- a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py +++ b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py @@ -79,19 +79,138 @@ def on_payment_authorized(payment_status): class RazorpaySettings(Document): - supported_currencies = ("AED","ALL","AMD","ARS","AUD","AWG","AZN","BAM","BBD","BDT", - "BGN","BHD","BIF","BMD","BND","BOB","BRL","BSD","BTN","BWP", - "BZD","CAD","CHF","CLP","CNY","COP","CRC","CUP","CVE","CZK", - "DJF","DKK","DOP","DZD","EGP","ETB","EUR","FJD","GBP","GHS", - "GIP","GMD","GNF","GTQ","GYD","HKD","HNL","HRK","HTG","HUF", - "IDR","ILS","INR","IQD","ISK","JMD","JOD","JPY","KES","KGS", - "KHR","KMF","KRW","KWD","KYD","KZT","LAK","LKR","LRD","LSL", - "MAD","MDL","MGA","MKD","MMK","MNT","MOP","MUR","MVR","MWK", - "MXN","MYR","MZN","NAD","NGN","NIO","NOK","NPR","NZD","OMR", - "PEN","PGK","PHP","PKR","PLN","PYG","QAR","RON","RSD","RUB", - "RWF","SAR","SCR","SEK","SGD","SLL","SOS","SSP","SVC","SZL", - "THB","TND","TRY","TTD","TWD","TZS","UAH","UGX","USD","UYU", - "UZS","VND","VUV","XAF","XCD","XOF","XPF","YER","ZAR","ZMW") + supported_currencies = ( + "AED", + "ALL", + "AMD", + "ARS", + "AUD", + "AWG", + "AZN", + "BAM", + "BBD", + "BDT", + "BGN", + "BHD", + "BIF", + "BMD", + "BND", + "BOB", + "BRL", + "BSD", + "BTN", + "BWP", + "BZD", + "CAD", + "CHF", + "CLP", + "CNY", + "COP", + "CRC", + "CUP", + "CVE", + "CZK", + "DJF", + "DKK", + "DOP", + "DZD", + "EGP", + "ETB", + "EUR", + "FJD", + "GBP", + "GHS", + "GIP", + "GMD", + "GNF", + "GTQ", + "GYD", + "HKD", + "HNL", + "HRK", + "HTG", + "HUF", + "IDR", + "ILS", + "INR", + "IQD", + "ISK", + "JMD", + "JOD", + "JPY", + "KES", + "KGS", + "KHR", + "KMF", + "KRW", + "KWD", + "KYD", + "KZT", + "LAK", + "LKR", + "LRD", + "LSL", + "MAD", + "MDL", + "MGA", + "MKD", + "MMK", + "MNT", + "MOP", + "MUR", + "MVR", + "MWK", + "MXN", + "MYR", + "MZN", + "NAD", + "NGN", + "NIO", + "NOK", + "NPR", + "NZD", + "OMR", + "PEN", + "PGK", + "PHP", + "PKR", + "PLN", + "PYG", + "QAR", + "RON", + "RSD", + "RUB", + "RWF", + "SAR", + "SCR", + "SEK", + "SGD", + "SLL", + "SOS", + "SSP", + "SVC", + "SZL", + "THB", + "TND", + "TRY", + "TTD", + "TWD", + "TZS", + "UAH", + "UGX", + "USD", + "UYU", + "UZS", + "VND", + "VUV", + "XAF", + "XCD", + "XOF", + "XPF", + "YER", + "ZAR", + "ZMW", + ) def init_client(self): if self.api_key: From 7047bfe9aa60ce6b0e290e4b6bf0db62cc00c5b1 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 19 Jun 2025 08:13:41 +0530 Subject: [PATCH 17/27] ci: trigger --- .../doctype/mpesa_settings/test_mpesa_settings.py | 3 +++ 1 file changed, 3 insertions(+) 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 ce5dc88a..cdfd0665 100644 --- a/payments/payment_gateways/doctype/mpesa_settings/test_mpesa_settings.py +++ b/payments/payment_gateways/doctype/mpesa_settings/test_mpesa_settings.py @@ -83,6 +83,7 @@ def test_processing_of_account_balance(self): integration_request.delete() def test_processing_of_callback_payload(self): + frappe.db.set_single_value("POS Settings", "invoice_type", "POS Invoice") mpesa_account = frappe.db.get_value( "Payment Gateway Account", {"payment_gateway": "Mpesa-Payment"}, "payment_account" ) @@ -144,6 +145,7 @@ def test_processing_of_callback_payload(self): pos_invoice.delete() def test_processing_of_multiple_callback_payload(self): + frappe.db.set_single_value("POS Settings", "invoice_type", "POS Invoice") mpesa_account = frappe.db.get_value( "Payment Gateway Account", {"payment_gateway": "Mpesa-Payment"}, "payment_account" ) @@ -214,6 +216,7 @@ def test_processing_of_multiple_callback_payload(self): pos_invoice.delete() def test_processing_of_only_one_succes_callback_payload(self): + frappe.db.set_single_value("POS Settings", "invoice_type", "POS Invoice") mpesa_account = frappe.db.get_value( "Payment Gateway Account", {"payment_gateway": "Mpesa-Payment"}, "payment_account" ) From c5c76aaeb731c696918e2dc30a820a8f8683c02d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 19 Jun 2025 09:11:28 +0530 Subject: [PATCH 18/27] chore: include setuptools --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 1eb59a56..470810c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,7 @@ dependencies = [ "braintree~=4.20.0", "pycryptodome>=3.18.0,<4.0.0", "gocardless-pro~=1.22.0", + "setuptools==80.9.0", ] [build-system] From f7abffbf2c47d21060f2a4ff5a6df301e32f3011 Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Thu, 19 Jun 2025 13:18:14 +0530 Subject: [PATCH 19/27] fix: make Stripe checkout page responsive --- payments/templates/pages/stripe_checkout.css | 16 ++++++++++++++++ payments/templates/pages/stripe_checkout.html | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/payments/templates/pages/stripe_checkout.css b/payments/templates/pages/stripe_checkout.css index 5aadc117..8842f1e7 100644 --- a/payments/templates/pages/stripe_checkout.css +++ b/payments/templates/pages/stripe_checkout.css @@ -99,3 +99,19 @@ .field:-ms-input-placeholder { color: #CFD7E0; } + +#payment-section { + min-height: 50vh; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + padding: 10rem; +} + +@media (max-width: 48rem) { + #payment-section { + padding: 0rem; + min-height: 70vh; + }, +} \ No newline at end of file diff --git a/payments/templates/pages/stripe_checkout.html b/payments/templates/pages/stripe_checkout.html index 9ce76a91..b8b56065 100644 --- a/payments/templates/pages/stripe_checkout.html +++ b/payments/templates/pages/stripe_checkout.html @@ -12,7 +12,7 @@ {%- block page_content -%} -
+
{% if image %} From 210961b06ce2d641ea5e738f33f28ff15e2e6d44 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Thu, 31 Jul 2025 21:25:51 +0530 Subject: [PATCH 20/27] fix(Razorpay): create order if not provided for payment link Closes #157 --- .../doctype/razorpay_settings/razorpay_settings.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py index 20815477..6cbed70a 100644 --- a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py +++ b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py @@ -325,6 +325,10 @@ def prepare_subscription_details(self, settings, **kwargs): return kwargs def get_payment_url(self, **kwargs): + if not kwargs.get("order_id"): + order = self.create_order(**kwargs) + kwargs.update({"order_id": order.get("id")}) + integration_request = create_request_log(kwargs, service_name="Razorpay") return get_url(f"./razorpay_checkout?token={integration_request.name}") From 93ff8de07ce29f90369b4b731e521cee0be38fd6 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 1 Aug 2025 13:51:04 +0530 Subject: [PATCH 21/27] fix: flaky mpesa tests --- .../doctype/mpesa_settings/mpesa_settings.py | 2 +- .../mpesa_settings/test_mpesa_settings.py | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/payments/payment_gateways/doctype/mpesa_settings/mpesa_settings.py b/payments/payment_gateways/doctype/mpesa_settings/mpesa_settings.py index 97e9aacc..ec0da3eb 100644 --- a/payments/payment_gateways/doctype/mpesa_settings/mpesa_settings.py +++ b/payments/payment_gateways/doctype/mpesa_settings/mpesa_settings.py @@ -44,8 +44,8 @@ def on_update(self): ) # required to fetch the bank account details from the payment gateway account - frappe.db.commit() # nosemgrep create_mode_of_payment("Mpesa-" + self.payment_gateway_name, payment_type="Phone") + frappe.db.commit() # nosemgrep def request_for_payment(self, **kwargs): args = frappe._dict(kwargs) 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 cdfd0665..d712f94b 100644 --- a/payments/payment_gateways/doctype/mpesa_settings/test_mpesa_settings.py +++ b/payments/payment_gateways/doctype/mpesa_settings/test_mpesa_settings.py @@ -39,10 +39,12 @@ def setUp(self): write_off_account="Write Off - WP", write_off_cost_center="Main - WP", ) - self.pos_profile = pos_profile.name - create_opening_entry(pos_profile, frappe.session.user) + self.pos_profile = pos_profile def tearDown(self): + frappe.db.rollback() + for x in frappe.db.get_all("POS Opening Entry"): + frappe.get_doc("POS Opening Entry", x.name).cancel().delete() frappe.db.sql("delete from `tabMpesa Settings`") frappe.db.sql("delete from `tabIntegration Request` where integration_request_service = 'Mpesa'") @@ -83,6 +85,7 @@ def test_processing_of_account_balance(self): integration_request.delete() def test_processing_of_callback_payload(self): + create_opening_entry(self.pos_profile, frappe.session.user) frappe.db.set_single_value("POS Settings", "invoice_type", "POS Invoice") mpesa_account = frappe.db.get_value( "Payment Gateway Account", {"payment_gateway": "Mpesa-Payment"}, "payment_account" @@ -97,7 +100,7 @@ def test_processing_of_callback_payload(self): cost_center="Main - WP", company="Wind Power LLC", income_account="Sales - WP", - pos_profile=self.pos_profile, + pos_profile=self.pos_profile.name, account_for_change_amount="Cash - WP", expense_account="Cost of Goods Sold - WP", do_not_submit=1, @@ -145,6 +148,7 @@ def test_processing_of_callback_payload(self): pos_invoice.delete() def test_processing_of_multiple_callback_payload(self): + create_opening_entry(self.pos_profile, frappe.session.user) frappe.db.set_single_value("POS Settings", "invoice_type", "POS Invoice") mpesa_account = frappe.db.get_value( "Payment Gateway Account", {"payment_gateway": "Mpesa-Payment"}, "payment_account" @@ -161,7 +165,7 @@ def test_processing_of_multiple_callback_payload(self): cost_center="Main - WP", company="Wind Power LLC", income_account="Sales - WP", - pos_profile=self.pos_profile, + pos_profile=self.pos_profile.name, account_for_change_amount="Cash - WP", expense_account="Cost of Goods Sold - WP", do_not_submit=1, @@ -215,7 +219,8 @@ def test_processing_of_multiple_callback_payload(self): pr.delete() pos_invoice.delete() - def test_processing_of_only_one_succes_callback_payload(self): + def test_processing_of_only_one_success_callback_payload(self): + create_opening_entry(self.pos_profile, frappe.session.user) frappe.db.set_single_value("POS Settings", "invoice_type", "POS Invoice") mpesa_account = frappe.db.get_value( "Payment Gateway Account", {"payment_gateway": "Mpesa-Payment"}, "payment_account" @@ -232,7 +237,7 @@ def test_processing_of_only_one_succes_callback_payload(self): cost_center="Main - WP", company="Wind Power LLC", income_account="Sales - WP", - pos_profile=self.pos_profile, + pos_profile=self.pos_profile.name, account_for_change_amount="Cash - WP", expense_account="Cost of Goods Sold - WP", do_not_submit=1, From b055e72d86b501a949f01424032309a66ad94607 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Mon, 15 Sep 2025 13:44:03 +0200 Subject: [PATCH 22/27] fix: image condition (#162) --- payments/overrides/payment_webform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/payments/overrides/payment_webform.py b/payments/overrides/payment_webform.py index c1a4b56d..09ec50a1 100644 --- a/payments/overrides/payment_webform.py +++ b/payments/overrides/payment_webform.py @@ -100,7 +100,7 @@ def accept(web_form, data, docname=None, for_payment=False): value = data.get(fieldname, None) if df and df.fieldtype in ("Attach", "Attach Image"): - if value and "data:" and "base64" in value: + if value and "data:" in value and "base64" in value: files.append((fieldname, value)) if not doc.name: doc.set(fieldname, "") From e3e1fa2cc81f7d0f5c0048a5c622ca3937937b52 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Mon, 15 Sep 2025 13:53:24 +0200 Subject: [PATCH 23/27] chore: bump actions/cache to v4 (#131) From 82114712ae03a37cc8f16db1f6a35bd4c63ac869 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Mon, 15 Sep 2025 14:11:14 +0200 Subject: [PATCH 24/27] refactor: delete custom fields (#161) --- payments/utils/utils.py | 53 ++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/payments/utils/utils.py b/payments/utils/utils.py index 40665ee8..8a9f763c 100644 --- a/payments/utils/utils.py +++ b/payments/utils/utils.py @@ -96,29 +96,38 @@ def make_custom_fields(): def delete_custom_fields(): - if frappe.get_meta("Web Form").has_field("payments_tab"): - click.secho("* Uninstalling Payment Custom Fields from Web Form") - - fieldnames = ( - "accept_payment", - "amount_based_on_field", - "amount_field", - "amount", - "currency", - "payer_email_based_on_field", - "payer_email_field", - "payer_name_based_on_field", - "payer_name_field", - "payment_gateway", - "payments_cb", - "payments_sb", - "payments_tab", - ) - - for fieldname in fieldnames: - frappe.db.delete("Custom Field", {"name": "Web Form-" + fieldname}) + if not frappe.get_meta("Web Form").has_field("payments_tab"): + return + + click.secho("* Uninstalling Payment Custom Fields from Web Form") + frappe.db.delete( + "Custom Field", + { + "dt": "Web Form", + "fieldname": ( + "in", + ( + "accept_payment", + "amount_based_on_field", + "amount_field", + "amount", + "currency", + "payer_email_based_on_field", + "payer_email_field", + "payer_name_based_on_field", + "payer_name_field", + "payment_button_help", + "payment_button_label", + "payment_gateway", + "payments_cb", + "payments_sb", + "payments_tab", + ), + ), + }, + ) - frappe.clear_cache(doctype="Web Form") + frappe.clear_cache(doctype="Web Form") def before_install(): From 14c82aee24f9ed1e8824636bc3438620b9ea1025 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 14:35:39 +0200 Subject: [PATCH 25/27] ci: bump github actions to supported versions (#169) (#171) (cherry picked from commit bbcde032a99b6ef5e1f383464f0bc52053a0dec8) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- .github/workflows/linter.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 159d3ca3..d756d5f3 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -20,10 +20,10 @@ jobs: if: github.event_name == 'pull_request' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 200 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 16 check-latest: true @@ -39,8 +39,8 @@ jobs: if: github.event_name == 'pull_request' steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: '3.10' - uses: pre-commit/action@v3.0.0 From 77499fecb4cea65d3ec215b06c60f4adfdee45a5 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Mon, 15 Sep 2025 14:41:13 +0200 Subject: [PATCH 26/27] refactor: inline immediately returned variable (#163) --- .../doctype/braintree_settings/braintree_settings.py | 5 +---- .../doctype/gocardless_settings/gocardless_settings.py | 8 ++------ .../doctype/mpesa_settings/mpesa_settings.py | 8 ++------ .../doctype/paytm_settings/paytm_settings.py | 5 +---- .../doctype/stripe_settings/stripe_settings.py | 3 +-- payments/templates/pages/stripe_checkout.py | 4 +--- 6 files changed, 8 insertions(+), 25 deletions(-) diff --git a/payments/payment_gateways/doctype/braintree_settings/braintree_settings.py b/payments/payment_gateways/doctype/braintree_settings/braintree_settings.py index c5c971bd..936139be 100644 --- a/payments/payment_gateways/doctype/braintree_settings/braintree_settings.py +++ b/payments/payment_gateways/doctype/braintree_settings/braintree_settings.py @@ -279,10 +279,7 @@ def create_charge_on_braintree(self): def get_gateway_controller(doc): payment_request = frappe.get_doc("Payment Request", doc) - gateway_controller = frappe.db.get_value( - "Payment Gateway", payment_request.payment_gateway, "gateway_controller" - ) - return gateway_controller + return frappe.db.get_value("Payment Gateway", payment_request.payment_gateway, "gateway_controller") def get_client_token(doc): diff --git a/payments/payment_gateways/doctype/gocardless_settings/gocardless_settings.py b/payments/payment_gateways/doctype/gocardless_settings/gocardless_settings.py index 4abb7e97..b101503d 100644 --- a/payments/payment_gateways/doctype/gocardless_settings/gocardless_settings.py +++ b/payments/payment_gateways/doctype/gocardless_settings/gocardless_settings.py @@ -199,14 +199,10 @@ def create_charge_on_gocardless(self): def get_gateway_controller(doc): payment_request = frappe.get_doc("Payment Request", doc) - gateway_controller = frappe.db.get_value( - "Payment Gateway", payment_request.payment_gateway, "gateway_controller" - ) - return gateway_controller + return frappe.db.get_value("Payment Gateway", payment_request.payment_gateway, "gateway_controller") def gocardless_initialization(doc): gateway_controller = get_gateway_controller(doc) settings = frappe.get_doc("GoCardless Settings", gateway_controller) - client = settings.initialize_client() - return client + return settings.initialize_client() diff --git a/payments/payment_gateways/doctype/mpesa_settings/mpesa_settings.py b/payments/payment_gateways/doctype/mpesa_settings/mpesa_settings.py index ec0da3eb..b0f8eb6e 100644 --- a/payments/payment_gateways/doctype/mpesa_settings/mpesa_settings.py +++ b/payments/payment_gateways/doctype/mpesa_settings/mpesa_settings.py @@ -143,7 +143,7 @@ def generate_stk_push(**kwargs): mobile_number = sanitize_mobile_number(args.sender) - response = connector.stk_push( + return connector.stk_push( business_shortcode=business_shortcode, amount=args.request_amount, passcode=mpesa_settings.get_password("online_passkey"), @@ -152,9 +152,6 @@ def generate_stk_push(**kwargs): phone_number=mobile_number, description="POS Payment", ) - - return response - except Exception: frappe.log_error("Mpesa Express Transaction Error") frappe.throw( @@ -268,7 +265,7 @@ def get_account_balance(request_payload): + "/api/method/payments.payment_gateways.doctype.mpesa_settings.mpesa_settings.process_balance_info" ) - response = connector.get_balance( + return connector.get_balance( mpesa_settings.initiator_name, mpesa_settings.security_credential, mpesa_settings.till_number, @@ -277,7 +274,6 @@ def get_account_balance(request_payload): callback_url, callback_url, ) - return response except Exception: frappe.log_error("Mpesa: Failed to get account balance") frappe.throw(_("Please check your configuration and try again"), title=_("Error")) diff --git a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py index db879255..41a49c63 100644 --- a/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py +++ b/payments/payment_gateways/doctype/paytm_settings/paytm_settings.py @@ -180,7 +180,4 @@ def finalize_request(order_id, transaction_response): def get_gateway_controller(doctype, docname): reference_doc = frappe.get_doc(doctype, docname) - gateway_controller = frappe.db.get_value( - "Payment Gateway", reference_doc.payment_gateway, "gateway_controller" - ) - return gateway_controller + return frappe.db.get_value("Payment Gateway", reference_doc.payment_gateway, "gateway_controller") diff --git a/payments/payment_gateways/doctype/stripe_settings/stripe_settings.py b/payments/payment_gateways/doctype/stripe_settings/stripe_settings.py index 88aa948e..7e8c5f46 100644 --- a/payments/payment_gateways/doctype/stripe_settings/stripe_settings.py +++ b/payments/payment_gateways/doctype/stripe_settings/stripe_settings.py @@ -284,5 +284,4 @@ def get_gateway_controller(doctype, docname, payment_gateway=None): if not payment_gateway: reference_doc = frappe.get_doc(doctype, docname) payment_gateway = reference_doc.payment_gateway - gateway_controller = frappe.db.get_value("Payment Gateway", payment_gateway, "gateway_controller") - return gateway_controller + return frappe.db.get_value("Payment Gateway", payment_gateway, "gateway_controller") diff --git a/payments/templates/pages/stripe_checkout.py b/payments/templates/pages/stripe_checkout.py index 8d8c1430..7f4a30e0 100644 --- a/payments/templates/pages/stripe_checkout.py +++ b/payments/templates/pages/stripe_checkout.py @@ -66,9 +66,7 @@ def get_api_key(doc, gateway_controller): def get_header_image(doc, gateway_controller): - header_image = frappe.db.get_value("Stripe Settings", gateway_controller, "header_img") - - return header_image + return frappe.db.get_value("Stripe Settings", gateway_controller, "header_img") @frappe.whitelist(allow_guest=True) From 0c09eb01480e01df6846e2f01b02a87a4067f002 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Mon, 15 Sep 2025 14:54:40 +0200 Subject: [PATCH 27/27] fix(Web Form): extend instead of overriding (#164) --- payments/hooks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/payments/hooks.py b/payments/hooks.py index a7a25d5c..3cc79b3a 100644 --- a/payments/hooks.py +++ b/payments/hooks.py @@ -92,9 +92,9 @@ # DocType Class # --------------- -# Override standard doctype classes +# Extend standard doctype classes -override_doctype_class = {"Web Form": "payments.overrides.payment_webform.PaymentWebForm"} +extend_doctype_class = {"Web Form": "payments.overrides.payment_webform.PaymentWebForm"} # Document Events # ---------------