From b23e79dd4621db43be13f27ab4bac6b806fb0849 Mon Sep 17 00:00:00 2001 From: mrPauwHaan <120259765+mrPauwHaan@users.noreply.github.com> Date: Sat, 10 Feb 2024 12:19:16 +0800 Subject: [PATCH 1/9] Mollie integration (#3) * Update mollie_settings.py * Update mollie_settings.json * Update mollie_checkout.js * Update mollie_checkout.py * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update stripe_checkout.js * Update mollie_checkout.js * Update stripe_checkout.js * Update mollie_checkout.js * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_checkout.py * Update mollie_settings.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.html * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_checkout.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_checkout.js * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_settings.py * Update mollie_settings.py * Update mollie_checkout.js * Update mollie_checkout.py * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.html * Update mollie_checkout.js * Update mollie_checkout.html * Update mollie_checkout.html * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_settings.py * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.html * Update mollie_checkout.html * Update mollie_checkout.py * Update mollie_checkout.html * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.js * Update mollie_checkout.py * Update mollie_checkout.js * Update mollie_settings.py * Update mollie_settings.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_checkout.js * Update mollie_settings.py * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.html * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_checkout.js * Update mollie_checkout.html * Update mollie_settings.py * Update mollie_checkout.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_checkout.py * Update mollie_settings.py * Update mollie_checkout.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_checkout.py * Update mollie_checkout.js * Update mollie_settings.py * Update mollie_settings.py * Update mollie_checkout.js * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.js * Update mollie_settings.py * Update mollie_checkout.py * Update mollie_checkout.js * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.js * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_checkout.py * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_settings.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_settings.py * Update mollie_checkout.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_checkout.js * Update mollie_checkout.py * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.html * Update mollie_checkout.html * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.html * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py --- .../doctype/mollie_settings/__init__.py | 1 + .../mollie_settings/mollie_settings.js | 5 + .../mollie_settings/mollie_settings.json | 315 ++++++++++++++++++ .../mollie_settings/mollie_settings.py | 209 ++++++++++++ .../templates/includes/mollie_checkout.js | 40 +++ payments/templates/pages/mollie_checkout.css | 113 +++++++ payments/templates/pages/mollie_checkout.html | 40 +++ payments/templates/pages/mollie_checkout.py | 94 ++++++ pyproject.toml | 1 + 9 files changed, 818 insertions(+) create mode 100644 payments/payment_gateways/doctype/mollie_settings/__init__.py create mode 100644 payments/payment_gateways/doctype/mollie_settings/mollie_settings.js create mode 100644 payments/payment_gateways/doctype/mollie_settings/mollie_settings.json create mode 100644 payments/payment_gateways/doctype/mollie_settings/mollie_settings.py create mode 100644 payments/templates/includes/mollie_checkout.js create mode 100644 payments/templates/pages/mollie_checkout.css create mode 100644 payments/templates/pages/mollie_checkout.html create mode 100644 payments/templates/pages/mollie_checkout.py diff --git a/payments/payment_gateways/doctype/mollie_settings/__init__.py b/payments/payment_gateways/doctype/mollie_settings/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/payments/payment_gateways/doctype/mollie_settings/__init__.py @@ -0,0 +1 @@ + diff --git a/payments/payment_gateways/doctype/mollie_settings/mollie_settings.js b/payments/payment_gateways/doctype/mollie_settings/mollie_settings.js new file mode 100644 index 00000000..bb0899af --- /dev/null +++ b/payments/payment_gateways/doctype/mollie_settings/mollie_settings.js @@ -0,0 +1,5 @@ +frappe.ui.form.on('Mollie Settings', { + refresh: function(frm) { + + } +}); diff --git a/payments/payment_gateways/doctype/mollie_settings/mollie_settings.json b/payments/payment_gateways/doctype/mollie_settings/mollie_settings.json new file mode 100644 index 00000000..260817a9 --- /dev/null +++ b/payments/payment_gateways/doctype/mollie_settings/mollie_settings.json @@ -0,0 +1,315 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "field:gateway_name", + "beta": 0, + "creation": "2017-03-09 17:18:29.458397", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "gateway_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Payment Gateway Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "profile_id", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Profile ID", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_3", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "secret_key", + "fieldtype": "Password", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Secret Key", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_5", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "header_img", + "fieldtype": "Attach Image", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Header Image", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_7", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "redirect_url", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Redirect URL", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2023-11-25 13:32:14.429916", + "modified_by": "Administrator", + "module": "Payment Gateways", + "name": "Mollie Settings", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 0, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0 + } diff --git a/payments/payment_gateways/doctype/mollie_settings/mollie_settings.py b/payments/payment_gateways/doctype/mollie_settings/mollie_settings.py new file mode 100644 index 00000000..e10859b2 --- /dev/null +++ b/payments/payment_gateways/doctype/mollie_settings/mollie_settings.py @@ -0,0 +1,209 @@ +from urllib.parse import urlencode + +import frappe +from frappe import _ +from frappe.integrations.utils import create_request_log, make_get_request +from frappe.model.document import Document +from frappe.utils import call_hook_method, cint, flt, get_url +from payments.utils import create_payment_gateway +from mollie.api.client import Client +from mollie.api.error import Error + +mollie_client = Client() +mollie_error = Error() + +class MollieSettings(Document): + supported_currencies = [ + "AED", + "AUD", + "BGN", + "BRL", + "CAD", + "CHF", + "CZK", + "DKK", + "EUR", + "GBP", + "HKD", + "HUF", + "ILS", + "ISK", + "JPY", + "MXN", + "MYR", + "NOK", + "NZD", + "PHP", + "PLN", + "RON", + "RUB", + "SEK", + "SGD", + "THB", + "TWD", + "USD", + "ZAR", + ] + + def on_update(self): + create_payment_gateway( + "Mollie-" + self.gateway_name, + settings="Mollie Settings", + controller=self.gateway_name, + ) + call_hook_method("payment_gateway_enabled", gateway="Mollie-" + self.gateway_name) + if not self.flags.ignore_mandatory: + self.validate_mollie_credentials() + + def validate_mollie_credentials(self): + if self.profile_id and self.secret_key: + header = { + "Authorization": "Bearer {}".format( + self.get_password(fieldname="secret_key", raise_exception=False) + ) + } + try: + make_get_request(url="https://api.mollie.com/v2/payments", headers=header) + except Exception: + frappe.throw(_("Seems Publishable Key or Secret Key is wrong !!!")) + + def validate_transaction_currency(self, currency): + if currency not in self.supported_currencies: + frappe.throw( + _( + "Please select another payment method. Mollie does not support transactions in currency '{0}'" + ).format(currency) + ) + + def get_payment_url(self, **kwargs): + return get_url(f"mollie_checkout?{urlencode(kwargs)}") + + def create_request(self, data): + self.data = frappe._dict(data) + api = mollie_client.set_api_key(self.get_password(fieldname="secret_key", raise_exception=False)) + + try: + self.integration_request = create_request_log(self.data, service_name="Mollie") + return self.create_charge_on_mollie() + + except Exception: + frappe.log_error(frappe.get_traceback()) + return { + "redirect_to": frappe.redirect_to_message( + _("Server Error"), + _( + "It seems that there is an issue with the server's Mollie configuration. In case of failure, the amount will get refunded to your account." + ), + ), + "status": 401, + } + + def check_request(self, data, paymentID): + mollie_client.set_api_key(self.get_password(fieldname="secret_key", raise_exception=False)) + try: + payment = mollie_client.payments.get(paymentID) + paymentUrl = "Unavailable" + + if payment.is_paid(): + status = "Completed" + elif payment.is_pending(): + status = "Pending" + paymentUrl = payment['_links']['checkout']['href'] + elif payment.is_open(): + status = "Open" + paymentUrl = payment['_links']['checkout']['href'] + else: + status = "Cancelled" + + return {"paymentUrl": paymentUrl, "status": status} + + except Exception: + frappe.log_error(frappe.get_traceback()) + return f"API call failed" + + def create_charge_on_mollie(self): + try: + data_details = { + "amount": self.data.amount, + "title": f"Payment for {self.data.reference_doctype} {self.data.reference_docname}", + "description": f"Payment for {self.data.reference_doctype} {self.data.reference_docname}", + "reference_doctype": self.data.reference_doctype, + "reference_docname": self.data.reference_docname, + "payer_email": frappe.session.user, + "payer_name": frappe.utils.get_fullname(frappe.session.user), + "order_id": self.data.reference_docname, + "currency": self.data.currency, + "redirect_to": self.data.get("redirect_to"), + } + redirect_url = self.get_payment_url(**data_details) + + charge = mollie_client.payments.create( + { + 'amount': { + 'currency': self.data.currency, + 'value': "{:.2f}".format(float(self.data.amount)) + }, + "description": self.data.description, + 'redirectUrl': redirect_url, + } + ) + + frappe.db.set_value(self.data.reference_doctype, self.data.reference_docname, 'payment_id', charge.id) + + except Exception: + if mollie_error: + frappe.log_error(mollie_error) + + frappe.log_error(frappe.get_traceback()) + + data2 = self.finalize_request() + data2.update(paymentID=charge.id) + data2.update(paymentUrl=charge.checkout_url) + + return data2 + + 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 + + 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( + self.data.reference_doctype, self.data.reference_docname + ).run_method("on_payment_authorized", self.flags.status_changed_to) + except Exception: + frappe.log_error(frappe.get_traceback()) + + if custom_redirect_to: + redirect_to = custom_redirect_to + + redirect_url = "payment-success?doctype={}&docname={}".format( + self.data.reference_doctype, self.data.reference_docname + ) + + if self.redirect_url: + redirect_url = self.redirect_url + redirect_to = None + else: + redirect_url = "payment-failed" + + if redirect_to and "?" in redirect_url: + redirect_url += "&" + urlencode({"redirect_to": redirect_to}) + else: + redirect_url += "?" + urlencode({"redirect_to": redirect_to}) + + if redirect_message: + redirect_url += "&" + urlencode({"redirect_message": redirect_message}) + + return {"redirect_to": redirect_url, "status": status} + + +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 diff --git a/payments/templates/includes/mollie_checkout.js b/payments/templates/includes/mollie_checkout.js new file mode 100644 index 00000000..42b242f7 --- /dev/null +++ b/payments/templates/includes/mollie_checkout.js @@ -0,0 +1,40 @@ ++$(document).ready(function() { + var form = document.querySelector('#payment-form'); + var data = {{ frappe.form_dict | json }}; + var doctype = "{{ reference_doctype }}" + var docname = "{{ reference_docname }}" + document.getElementById("submit").innerHTML = "{{_("Loading...")}}"; + document.getElementById("status").value = "{{_("Loading...")}}"; + frappe.call({ + method: "payments.templates.pages.mollie_checkout.make_payment", + freeze: true, + headers: { + "X-Requested-With": "XMLHttpRequest" + }, + args: { + "data": JSON.stringify(data), + "reference_doctype": doctype, + "reference_docname": docname, + }, + callback: function(r){ + payment = r.message + document.getElementById("status").value = payment.status; + if (payment.paymentUrl == "Unavailable") { + document.getElementById("submit").innerHTML = "{{_("Ready")}}"; + } + else { + document.getElementById("submit").innerHTML = "{{_('Pay')}} {{amount}}"; + } + } + }) + + form.addEventListener('submit', e => { + e.preventDefault(); + if (payment.paymentUrl == "Unavailable") { + window.location.href = payment.redirect_to + } + else { + window.location.href = payment.paymentUrl + } + }) +}) diff --git a/payments/templates/pages/mollie_checkout.css b/payments/templates/pages/mollie_checkout.css new file mode 100644 index 00000000..aa7a1da4 --- /dev/null +++ b/payments/templates/pages/mollie_checkout.css @@ -0,0 +1,113 @@ +.MollieElement { + background-color: white; + height: 40px; + padding: 10px 12px; + border-radius: 4px; + border: 1px solid transparent; + box-shadow: 0 1px 3px 0 #e6ebf1; + -webkit-transition: box-shadow 150ms ease; + transition: box-shadow 150ms ease; +} + +.MollieElement--focus { + box-shadow: 0 1px 3px 0 #cfd7df; +} + +.MollieElement--invalid { + border-color: #fa755a; +} + +.MollieElement--webkit-autofill { + background-color: #fefde5; +} + +.mollie #payment-form { + margin-top: 80px; +} + +.mollie button { + float: right; + display: block; + background: #5e64ff; + color: white; + box-shadow: 0 7px 14px 0 rgba(49, 49, 93, 0.10), 0 3px 6px 0 rgba(0, 0, 0, 0.08); + border-radius: 4px; + border: 0; + margin-top: 20px; + font-size: 15px; + font-weight: 400; + max-width: 40%; + height: 40px; + line-height: 38px; + outline: none; +} + +.mollie button:hover, .mollie button:focus { + background: #2b33ff; + border-color: #0711ff; +} + +.mollie button:active { + background: #5e64ff; +} + +.mollie button:disabled { + background: #515e80; +} + +.mollie .group { + background: white; + box-shadow: 2px 7px 14px 2px rgba(49, 49, 93, 0.10), 0 3px 6px 0 rgba(0, 0, 0, 0.08); + border-radius: 4px; + margin-bottom: 20px; +} + +.mollie label { + position: relative; + color: #8898AA; + font-weight: 300; + height: 40px; + line-height: 40px; + margin-left: 20px; + display: block; +} + +.mollie .group label:not(:last-child) { + border-bottom: 1px solid #F0F5FA; +} + +.mollie label>span { + width: 20%; + text-align: right; + float: left; +} + +.current-card { + margin-left: 20px; +} + +.field { + background: transparent; + font-weight: 300; + border: 0; + color: #31325F; + outline: none; + padding-right: 10px; + padding-left: 10px; + cursor: text; + width: 70%; + height: 40px; + float: right; +} + +.field::-webkit-input-placeholder { + color: #CFD7E0; +} + +.field::-moz-placeholder { + color: #CFD7E0; +} + +.field:-ms-input-placeholder { + color: #CFD7E0; +} diff --git a/payments/templates/pages/mollie_checkout.html b/payments/templates/pages/mollie_checkout.html new file mode 100644 index 00000000..ee11c6a0 --- /dev/null +++ b/payments/templates/pages/mollie_checkout.html @@ -0,0 +1,40 @@ +{% extends "templates/web.html" %} + +{% block title %} Payment {% endblock %} + +{%- block header -%} +{% endblock %} + +{% block script %} + + +{% endblock %} + +{%- block page_content -%} + +
+
+ {% if image %} + + {% endif %} +

{{description}}

+
+
+
+
+
+
+ +
+
+
+ +
+
+
+ + +{% endblock %} diff --git a/payments/templates/pages/mollie_checkout.py b/payments/templates/pages/mollie_checkout.py new file mode 100644 index 00000000..808a6f62 --- /dev/null +++ b/payments/templates/pages/mollie_checkout.py @@ -0,0 +1,94 @@ +import json + +import frappe +from frappe import _ +from frappe.utils import cint, fmt_money + +from payments.payment_gateways.doctype.mollie_settings.mollie_settings import ( + get_gateway_controller, +) + +no_cache = 1 + +expected_keys = ( + "amount", + "title", + "description", + "reference_doctype", + "reference_docname", + "payer_name", + "payer_email", + "order_id", + "currency", +) + + +def get_context(context): + context.no_cache = 1 + + # all these keys exist in form_dict + if not (set(expected_keys) - set(list(frappe.form_dict))): + for key in expected_keys: + context[key] = frappe.form_dict[key] + + gateway_controller = get_gateway_controller(context.reference_doctype, context.reference_docname) + context.profile_id = get_api_key(context.reference_docname, gateway_controller) + context.image = get_header_image(context.reference_docname, gateway_controller) + + context["amount"] = fmt_money(amount=context["amount"], currency=context["currency"]) + + else: + frappe.log_error("Data to complete the payment is missing", frappe.form_dict) + frappe.redirect_to_message( + _("Some information is missing"), + _("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): + profile_id = frappe.db.get_value("Mollie Settings", gateway_controller, "profile_id") + if cint(frappe.form_dict.get("use_sandbox")): + profile_id = frappe.conf.sandbox_profile_id + + return profile_id + + +def get_header_image(doc, gateway_controller): + header_image = frappe.db.get_value("Mollie Settings", gateway_controller, "header_img") + + return header_image + + +@frappe.whitelist(allow_guest=True) +def make_payment(data, reference_doctype, reference_docname): + data = json.loads(data) + gateway_controller = get_gateway_controller(reference_doctype, reference_docname) + paymentID = frappe.db.get_value(reference_doctype, reference_docname, 'payment_id') + + if not paymentID: + data = frappe.get_doc("Mollie Settings", gateway_controller).create_request(data) + paymentID = data["paymentID"] + + status = frappe.get_doc("Mollie Settings", gateway_controller).check_request(data, paymentID) + data["paymentUrl"] = status["paymentUrl"] + + if status["status"] == "Cancelled": + data = frappe.get_doc("Mollie Settings", gateway_controller).create_request(data) + paymentID = data["paymentID"] + status = "Open" + data["status"] = status + data["paymentUrl"] = data["paymentUrl"] + else: + status = status["status"] + data["status"] = status + + try: + frappe.db.set_value(reference_doctype, reference_docname, 'payment_status', status) + except: + pass + + frappe.db.commit() + return data diff --git a/pyproject.toml b/pyproject.toml index 1dbe5220..7061b21d 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", + "mollie-api-python~=3.6.0", ] [build-system] From 9f102a33e79ae5997f11ba4140a3348be512e932 Mon Sep 17 00:00:00 2001 From: mrPauwHaan <120259765+mrPauwHaan@users.noreply.github.com> Date: Tue, 9 Jul 2024 13:00:50 +0200 Subject: [PATCH 2/9] Check payment_status field (#4) * Update mollie_checkout.py * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update stripe_checkout.js * Update mollie_checkout.js * Update stripe_checkout.js * Update mollie_checkout.js * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_checkout.py * Update mollie_settings.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.html * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_checkout.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_checkout.js * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_settings.py * Update mollie_settings.py * Update mollie_checkout.js * Update mollie_checkout.py * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.html * Update mollie_checkout.js * Update mollie_checkout.html * Update mollie_checkout.html * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_settings.py * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.html * Update mollie_checkout.html * Update mollie_checkout.py * Update mollie_checkout.html * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.js * Update mollie_checkout.py * Update mollie_checkout.js * Update mollie_settings.py * Update mollie_settings.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_checkout.js * Update mollie_settings.py * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.html * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_checkout.js * Update mollie_checkout.html * Update mollie_settings.py * Update mollie_checkout.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_checkout.py * Update mollie_settings.py * Update mollie_checkout.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_checkout.py * Update mollie_checkout.js * Update mollie_settings.py * Update mollie_settings.py * Update mollie_checkout.js * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.js * Update mollie_settings.py * Update mollie_checkout.py * Update mollie_checkout.js * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.js * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_checkout.py * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_settings.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_settings.py * Update mollie_checkout.py * Update mollie_settings.py * Update mollie_settings.py * Update mollie_checkout.js * Update mollie_checkout.py * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.html * Update mollie_checkout.html * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.html * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.js * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py * Update mollie_checkout.py if payment_status exists, first check for status field in doctype before checking the payment ID in the Mollie API --- payments/templates/pages/mollie_checkout.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/payments/templates/pages/mollie_checkout.py b/payments/templates/pages/mollie_checkout.py index 808a6f62..3a7cd323 100644 --- a/payments/templates/pages/mollie_checkout.py +++ b/payments/templates/pages/mollie_checkout.py @@ -71,8 +71,15 @@ def make_payment(data, reference_doctype, reference_docname): if not paymentID: data = frappe.get_doc("Mollie Settings", gateway_controller).create_request(data) paymentID = data["paymentID"] + + try: + status = frappe.db.get_value(reference_doctype, reference_docname, 'payment_status') + except: + pass + + if not status + status = frappe.get_doc("Mollie Settings", gateway_controller).check_request(data, paymentID) - status = frappe.get_doc("Mollie Settings", gateway_controller).check_request(data, paymentID) data["paymentUrl"] = status["paymentUrl"] if status["status"] == "Cancelled": @@ -91,4 +98,4 @@ def make_payment(data, reference_doctype, reference_docname): pass frappe.db.commit() - return data + return data \ No newline at end of file From a29f4642786e1a3ea8c789989e7e67fdba55235e Mon Sep 17 00:00:00 2001 From: mrPauwHaan <120259765+mrPauwHaan@users.noreply.github.com> Date: Tue, 9 Jul 2024 13:11:11 +0200 Subject: [PATCH 3/9] Update mollie_checkout.py --- payments/templates/pages/mollie_checkout.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/payments/templates/pages/mollie_checkout.py b/payments/templates/pages/mollie_checkout.py index 3a7cd323..b5d6f855 100644 --- a/payments/templates/pages/mollie_checkout.py +++ b/payments/templates/pages/mollie_checkout.py @@ -77,7 +77,7 @@ def make_payment(data, reference_doctype, reference_docname): except: pass - if not status + if not status: status = frappe.get_doc("Mollie Settings", gateway_controller).check_request(data, paymentID) data["paymentUrl"] = status["paymentUrl"] @@ -98,4 +98,4 @@ def make_payment(data, reference_doctype, reference_docname): pass frappe.db.commit() - return data \ No newline at end of file + return data From 63fc72f09dbcda2220ada6b99e4eb50ce96658a4 Mon Sep 17 00:00:00 2001 From: mrPauwHaan <120259765+mrPauwHaan@users.noreply.github.com> Date: Tue, 9 Jul 2024 13:26:43 +0200 Subject: [PATCH 4/9] Update mollie_checkout.py --- payments/templates/pages/mollie_checkout.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/payments/templates/pages/mollie_checkout.py b/payments/templates/pages/mollie_checkout.py index b5d6f855..99056800 100644 --- a/payments/templates/pages/mollie_checkout.py +++ b/payments/templates/pages/mollie_checkout.py @@ -71,17 +71,15 @@ def make_payment(data, reference_doctype, reference_docname): if not paymentID: data = frappe.get_doc("Mollie Settings", gateway_controller).create_request(data) paymentID = data["paymentID"] + + status = frappe.get_doc("Mollie Settings", gateway_controller).check_request(data, paymentID) + data["paymentUrl"] = status["paymentUrl"] try: - status = frappe.db.get_value(reference_doctype, reference_docname, 'payment_status') + status["status"] = frappe.db.get_value(reference_doctype, reference_docname, 'payment_status') except: pass - if not status: - status = frappe.get_doc("Mollie Settings", gateway_controller).check_request(data, paymentID) - - data["paymentUrl"] = status["paymentUrl"] - if status["status"] == "Cancelled": data = frappe.get_doc("Mollie Settings", gateway_controller).create_request(data) paymentID = data["paymentID"] From 5ac89d0d40340ecd18a269f73d8cb23fb5941df1 Mon Sep 17 00:00:00 2001 From: mrPauwHaan <120259765+mrPauwHaan@users.noreply.github.com> Date: Tue, 9 Jul 2024 13:39:56 +0200 Subject: [PATCH 5/9] Update mollie_checkout.py --- payments/templates/pages/mollie_checkout.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/payments/templates/pages/mollie_checkout.py b/payments/templates/pages/mollie_checkout.py index 99056800..5bfd5b52 100644 --- a/payments/templates/pages/mollie_checkout.py +++ b/payments/templates/pages/mollie_checkout.py @@ -76,7 +76,9 @@ def make_payment(data, reference_doctype, reference_docname): data["paymentUrl"] = status["paymentUrl"] try: - status["status"] = frappe.db.get_value(reference_doctype, reference_docname, 'payment_status') + status_field = frappe.db.get_value(reference_doctype, reference_docname, 'payment_status') + if status_field: + status["status"] = status_field except: pass From 05df389f31e2a4e74f08e749f89bbf4c8e729074 Mon Sep 17 00:00:00 2001 From: mrPauwHaan <120259765+mrPauwHaan@users.noreply.github.com> Date: Tue, 9 Jul 2024 13:51:44 +0200 Subject: [PATCH 6/9] Update mollie_checkout.js --- payments/templates/includes/mollie_checkout.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/payments/templates/includes/mollie_checkout.js b/payments/templates/includes/mollie_checkout.js index 42b242f7..74f037a8 100644 --- a/payments/templates/includes/mollie_checkout.js +++ b/payments/templates/includes/mollie_checkout.js @@ -19,7 +19,7 @@ callback: function(r){ payment = r.message document.getElementById("status").value = payment.status; - if (payment.paymentUrl == "Unavailable") { + if (payment.paymentUrl == "Completed" || payment.paymentUrl == "Cancelled") { document.getElementById("submit").innerHTML = "{{_("Ready")}}"; } else { From dc66d4685542200d17175e43996661555cb637a3 Mon Sep 17 00:00:00 2001 From: mrPauwHaan <120259765+mrPauwHaan@users.noreply.github.com> Date: Tue, 9 Jul 2024 13:52:53 +0200 Subject: [PATCH 7/9] Update mollie_checkout.js --- payments/templates/includes/mollie_checkout.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/payments/templates/includes/mollie_checkout.js b/payments/templates/includes/mollie_checkout.js index 74f037a8..a1d34a26 100644 --- a/payments/templates/includes/mollie_checkout.js +++ b/payments/templates/includes/mollie_checkout.js @@ -19,7 +19,7 @@ callback: function(r){ payment = r.message document.getElementById("status").value = payment.status; - if (payment.paymentUrl == "Completed" || payment.paymentUrl == "Cancelled") { + if (payment.status == "Completed" || payment.status == "Cancelled") { document.getElementById("submit").innerHTML = "{{_("Ready")}}"; } else { From 458f2fbaf3daa6817a568f2adbd47dd7235a5ce8 Mon Sep 17 00:00:00 2001 From: mrPauwHaan <120259765+mrPauwHaan@users.noreply.github.com> Date: Tue, 9 Jul 2024 14:17:02 +0200 Subject: [PATCH 8/9] Update mollie_checkout.py Only skip if status_field is 'completed' --- payments/templates/pages/mollie_checkout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/payments/templates/pages/mollie_checkout.py b/payments/templates/pages/mollie_checkout.py index 5bfd5b52..e00494b5 100644 --- a/payments/templates/pages/mollie_checkout.py +++ b/payments/templates/pages/mollie_checkout.py @@ -77,7 +77,7 @@ def make_payment(data, reference_doctype, reference_docname): try: status_field = frappe.db.get_value(reference_doctype, reference_docname, 'payment_status') - if status_field: + if status_field == "Completed": status["status"] = status_field except: pass From fb3ae055f5f924a7e9c5a19733f2c70d99593402 Mon Sep 17 00:00:00 2001 From: mrPauwHaan <120259765+mrPauwHaan@users.noreply.github.com> Date: Tue, 9 Jul 2024 14:26:20 +0200 Subject: [PATCH 9/9] Update mollie_checkout.js --- payments/templates/includes/mollie_checkout.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/payments/templates/includes/mollie_checkout.js b/payments/templates/includes/mollie_checkout.js index a1d34a26..113e5cf1 100644 --- a/payments/templates/includes/mollie_checkout.js +++ b/payments/templates/includes/mollie_checkout.js @@ -30,7 +30,7 @@ form.addEventListener('submit', e => { e.preventDefault(); - if (payment.paymentUrl == "Unavailable") { + if (payment.status == "Completed" || payment.status == "Cancelled") { window.location.href = payment.redirect_to } else {