diff --git a/erpnext/accounts/doctype/payment_request/payment_request.json b/erpnext/accounts/doctype/payment_request/payment_request.json index 3c099718b8f4038104466d61a1bcea6000b8a44e..81b1c366d6c843edbbc289f9b3dcf93159957eb5 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.json +++ b/erpnext/accounts/doctype/payment_request/payment_request.json @@ -356,7 +356,7 @@ "table_fieldname": "references" } ], - "modified": "2023-11-27 17:48:31.449475", + "modified": "2024-01-21 00:37:06.988919", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Request", diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 187ad8f90d083cf898feab83234bf7d6b2469e12..b8fbe0621a37350c3999fbf42175e926470c5aa8 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -9,6 +9,7 @@ from frappe import _ from frappe.model.document import Document from frappe.utils import flt, get_url, nowdate from frappe.utils.background_jobs import enqueue +from payments.utils.utils import can_make_immediate_payment from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_accounting_dimensions, @@ -19,9 +20,6 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import ( ) from erpnext.accounts.party import get_party_account from erpnext.accounts.utils import get_account_currency -from erpnext.erpnext_integrations.doctype.integration_references.integration_references import ( - can_make_immediate_payment, -) from erpnext.utilities import payment_app_import_guard, webshop_app_import_guard @@ -346,19 +344,13 @@ class PaymentRequest(Document): return controller = _get_payment_gateway_controller(self.payment_gateway) - if frappe.db.exists("Integration References", dict(customer=self.customer)): - doc = frappe.get_doc("Integration References", dict(customer=self.customer)) - else: - doc = frappe.new_doc("Integration References") - - if controller.doctype == "Stripe Settings": - doc.stripe_customer_id = controller.get_customer_id(reference_no) - if not doc.stripe_customer_id: - return - doc.stripe_settings = controller.name - - doc.customer = self.customer - doc.save(ignore_permissions=True) + if customer_id := controller.run_method("get_customer_id", reference_no): + customer = frappe.get_cached_doc("Customer", self.customer) + customer.append( + "payment_gateways_references", + {"payment_gateway": self.payment_gateway, "customer_id": customer_id}, + ) + customer.save() def create_payment_entry(self, submit=True, reference_no=None): """create entry""" diff --git a/erpnext/erpnext_integrations/doctype/integration_references/integration_references.js b/erpnext/erpnext_integrations/doctype/integration_references/integration_references.js deleted file mode 100644 index 21687eaa57bb07a661bb49e562f3981b3c38c999..0000000000000000000000000000000000000000 --- a/erpnext/erpnext_integrations/doctype/integration_references/integration_references.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Integration References', { - // refresh: function(frm) { - - // } -}); diff --git a/erpnext/erpnext_integrations/doctype/integration_references/integration_references.json b/erpnext/erpnext_integrations/doctype/integration_references/integration_references.json deleted file mode 100644 index 9fd24f5282b308a6aba448a18da351743cf07837..0000000000000000000000000000000000000000 --- a/erpnext/erpnext_integrations/doctype/integration_references/integration_references.json +++ /dev/null @@ -1,106 +0,0 @@ -{ - "allow_rename": 1, - "creation": "2019-08-21 17:10:07.246025", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "customer", - "column_break_2", - "customer_name", - "stripe_section", - "stripe_customer_id", - "column_break_6", - "stripe_settings", - "gocardless_section", - "gocardless_customer_id", - "column_break_8", - "gocardless_settings" - ], - "fields": [ - { - "fieldname": "customer", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Customer", - "options": "Customer", - "reqd": 1, - "unique": 1 - }, - { - "fieldname": "column_break_2", - "fieldtype": "Column Break" - }, - { - "fetch_from": "customer.customer_name", - "fieldname": "customer_name", - "fieldtype": "Data", - "label": "Customer Name", - "read_only": 1 - }, - { - "fieldname": "stripe_section", - "fieldtype": "Section Break", - "label": "Stripe" - }, - { - "fieldname": "stripe_customer_id", - "fieldtype": "Data", - "label": "Customer ID" - }, - { - "fieldname": "gocardless_section", - "fieldtype": "Section Break", - "label": "GoCardless" - }, - { - "fieldname": "gocardless_customer_id", - "fieldtype": "Data", - "label": "Customer ID" - }, - { - "fieldname": "column_break_8", - "fieldtype": "Column Break" - }, - { - "fieldname": "gocardless_settings", - "fieldtype": "Link", - "label": "GoCardless Settings", - "options": "GoCardless Settings" - }, - { - "fieldname": "column_break_6", - "fieldtype": "Column Break" - }, - { - "fieldname": "stripe_settings", - "fieldtype": "Link", - "label": "Stripe Settings", - "options": "Stripe Settings" - } - ], - "modified": "2019-08-22 19:30:55.564180", - "modified_by": "Administrator", - "module": "ERPNext Integrations", - "name": "Integration References", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "customer_name", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/integration_references/integration_references.py b/erpnext/erpnext_integrations/doctype/integration_references/integration_references.py deleted file mode 100644 index ca568f98375fe826db37819e645ac76ab62fada5..0000000000000000000000000000000000000000 --- a/erpnext/erpnext_integrations/doctype/integration_references/integration_references.py +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (c) 2023, Dokos SAS and contributors -# For license information, please see license.txt - -import frappe -from frappe.model.document import Document - -from payments.payment_gateways.doctype.stripe_settings.api.customer import StripeCustomer - -class IntegrationReferences(Document): - pass - -def can_make_immediate_payment(payment_request, controller): - customer = payment_request.get_customer() - if not customer: - return - - if controller.doctype == "Stripe Settings": - stripe_customer_id = frappe.db.get_value( - "Integration References", - dict(customer=customer, stripe_settings=controller.name), - "stripe_customer_id", - ) - - if stripe_customer_id: - stripe_customer = StripeCustomer(controller).get(stripe_customer_id) - has_default_source = bool(stripe_customer.get("default_source")) - has_default_payment_method = bool(stripe_customer.get("invoice_settings", {}).get("default_payment_method")) - return has_default_source or has_default_payment_method - - elif controller.doctype == "GoCardless Settings": - return bool(controller.check_mandate_validity(payment_request.get_customer()).get("mandate")) - diff --git a/erpnext/erpnext_integrations/doctype/integration_references/test_integration_references.py b/erpnext/erpnext_integrations/doctype/integration_references/test_integration_references.py deleted file mode 100644 index dac40e43b74efd88d542f2cec3c7105c6e1fe3f7..0000000000000000000000000000000000000000 --- a/erpnext/erpnext_integrations/doctype/integration_references/test_integration_references.py +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - - -# import frappe -import unittest - - -class TestIntegrationReferences(unittest.TestCase): - pass diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 04db932bc290def319bc72efab7e23a44194f1c2..ac0be698b71312259b69c0f63d30af49fbf217b5 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -275,6 +275,7 @@ erpnext.patches.dokos.v3_0.update_remarks erpnext.patches.dokos.v3_0.single_accounting_journal_for_bank_transactions execute:frappe.rename_doc("Report", "Fichier des Ecritures Comptables [FEC]", "Fichier des Ecritures Comptables", force=True) erpnext.patches.dokos.v4_0.remove_payment_gateway_custom_fields +erpnext.patches.dokos.v4_0.migrate_integration_references_to_customer [post_model_sync] @@ -381,9 +382,7 @@ execute:frappe.delete_doc_if_exists("DocType", "Subscription Event") erpnext.patches.dokos.v4_0.set_mode_of_payment_in_payment_requests erpnext.patches.dokos.v4_0.add_customer_to_payment_request erpnext.accounts.doctype.mode_of_payment.patches.migrate_fees_and_cost_center_to_account_table -erpnext.accounts.doctype.subscription.patches.use_the_correct_state_key_for_sales_invoices execute:frappe.delete_doc_if_exists("DocType", "Integration References") -execute:frappe.delete_doc_if_exists("DocType", "Social Media Post") # @dokos diff --git a/erpnext/patches/dokos/v4_0/migrate_integration_references_to_customer.py b/erpnext/patches/dokos/v4_0/migrate_integration_references_to_customer.py new file mode 100644 index 0000000000000000000000000000000000000000..3bfbfd152a901bf83f1eeac82d66d3180cc6dc4e --- /dev/null +++ b/erpnext/patches/dokos/v4_0/migrate_integration_references_to_customer.py @@ -0,0 +1,44 @@ +import frappe + + +def execute(): + frappe.reload_doc("selling", "doctype", "customer") + frappe.reload_doc("selling", "doctype", "payment_gateways_references") + + for ir in frappe.get_all("Integration References"): + doc = frappe.db.get_value("Integration References", ir.name, "*", as_dict=True) + customer = frappe.get_doc("Customer", doc.customer) + + if not doc.stripe_customer_id and not doc.gocardless_customer_id: + return + + if doc.stripe_customer_id: + customer.append( + "payment_gateways_references", + { + "payment_gateway": frappe.db.get_value( + "Payment Gateway", + dict(gateway_settings="Stripe Settings", gateway_controller=doc.stripe_settings, disabled=0), + ), + "customer_id": doc.stripe_customer_id, + }, + ) + + if doc.gocardless_customer_id: + customer.append( + "payment_gateways_references", + { + "payment_gateway": frappe.db.get_value( + "Payment Gateway", + dict( + gateway_settings="GoCardless Settings", + gateway_controller=doc.gocardless_settings, + disabled=0, + ), + ), + "customer_id": doc.gocardless_customer_id, + }, + ) + + customer.flags.ignore_mandatory = True + customer.save() diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json index 5cb0a5605b15e151dc4ed7e8d119d3f5ad3a5ec9..b8e6226a2cd294e54f88732225c64610a56c115f 100644 --- a/erpnext/selling/doctype/customer/customer.json +++ b/erpnext/selling/doctype/customer/customer.json @@ -87,7 +87,9 @@ "disabled", "status", "portal_users_tab", - "portal_users" + "portal_users", + "payments_tab", + "payment_gateways_references" ], "fields": [ { @@ -594,6 +596,17 @@ "fieldtype": "Link", "label": "Default Mode of Payment", "options": "Mode of Payment" + }, + { + "fieldname": "payments_tab", + "fieldtype": "Tab Break", + "label": "Payments" + }, + { + "fieldname": "payment_gateways_references", + "fieldtype": "Table", + "label": "Payment Gateways References", + "options": "Payment Gateways References" } ], "icon": "fa fa-user", @@ -607,7 +620,7 @@ "link_fieldname": "party" } ], - "modified": "2024-03-07 16:37:31.272633", + "modified": "2024-04-23 12:04:01.191169", "modified_by": "Administrator", "module": "Selling", "name": "Customer", diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index c7e81260d1363674302b4320e209b95e95943917..58e21375c30376488e0df72d3725b13d803b7de6 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -38,6 +38,9 @@ class Customer(TransactionBase): from erpnext.selling.doctype.customer_credit_limit.customer_credit_limit import ( CustomerCreditLimit, ) + from erpnext.selling.doctype.payment_gateways_references.payment_gateways_references import ( + PaymentGatewaysReferences, + ) from erpnext.selling.doctype.sales_team.sales_team import SalesTeam from erpnext.utilities.doctype.portal_user.portal_user import PortalUser @@ -74,6 +77,7 @@ class Customer(TransactionBase): mode_of_payment: DF.Link | None naming_series: DF.Literal["CUST-.YYYY.-"] opportunity_name: DF.Link | None + payment_gateways_references: DF.Table[PaymentGatewaysReferences] payment_terms: DF.Link | None portal_users: DF.Table[PortalUser] primary_address: DF.Text | None @@ -853,3 +857,11 @@ def parse_full_name(full_name: str) -> tuple[str, str | None, str | None]: last_name = names[-1] if len(names) > 1 else None return first_name, middle_name, last_name + + +def get_customer_from_integration_id(payment_gateway, customer_id): + return frappe.db.get_value( + "Payment Gateways References", + dict(parenttype="Customer", payment_gateway=payment_gateway, customer_id=customer_id), + "parent", + ) diff --git a/erpnext/erpnext_integrations/doctype/integration_references/__init__.py b/erpnext/selling/doctype/payment_gateways_references/__init__.py similarity index 100% rename from erpnext/erpnext_integrations/doctype/integration_references/__init__.py rename to erpnext/selling/doctype/payment_gateways_references/__init__.py diff --git a/erpnext/selling/doctype/payment_gateways_references/payment_gateways_references.json b/erpnext/selling/doctype/payment_gateways_references/payment_gateways_references.json new file mode 100644 index 0000000000000000000000000000000000000000..7458d13b10237cddaa90df68ba264fefa88b9b49 --- /dev/null +++ b/erpnext/selling/doctype/payment_gateways_references/payment_gateways_references.json @@ -0,0 +1,55 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2024-04-23 12:00:18.764044", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "payment_gateway", + "customer_id", + "payment_method", + "payment_method_id" + ], + "fields": [ + { + "fieldname": "payment_gateway", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Payment Gateway", + "options": "Payment Gateway", + "reqd": 1 + }, + { + "fieldname": "customer_id", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Customer ID", + "reqd": 1 + }, + { + "fieldname": "payment_method", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Default Payment Method" + }, + { + "fieldname": "payment_method_id", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Default Payment Method ID" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2024-04-23 12:03:24.787858", + "modified_by": "Administrator", + "module": "Selling", + "name": "Payment Gateways References", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} diff --git a/erpnext/selling/doctype/payment_gateways_references/payment_gateways_references.py b/erpnext/selling/doctype/payment_gateways_references/payment_gateways_references.py new file mode 100644 index 0000000000000000000000000000000000000000..2706287684d6d66b5c05e8bc673f86e51e2cc9b0 --- /dev/null +++ b/erpnext/selling/doctype/payment_gateways_references/payment_gateways_references.py @@ -0,0 +1,26 @@ +# Copyright (c) 2024, Dokos SAS and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class PaymentGatewaysReferences(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + customer_id: DF.Data + parent: DF.Data + parentfield: DF.Data + parenttype: DF.Data + payment_gateway: DF.Link + payment_method: DF.Data | None + payment_method_id: DF.Data | None + # end: auto-generated types + + pass diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv index 4e176a5f48ff45effd8859503602284130bf5488..35de9dee7c2ddf22b9179d81350e64d4ccda4546 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -5444,7 +5444,6 @@ Insurer,Assureur, Integrated Tax,Tax intégrée, Integration Details,Détails d'intégration, Integration ID,ID d'intégration, -Integration References,Références d'intégration, Integrations Settings,Paramètres des intégrations, Inter Company Account,Compte inter-sociétés, Inter Company Invoice Reference,Référence de facture inter-sociétés, diff --git a/erpnext/www/me.py b/erpnext/www/me.py index af6b056a0fdaf2b2ab696c5c2f7928e95b48282e..dfbb1d627928fd9afeed5126d67f2db96928fc15 100644 --- a/erpnext/www/me.py +++ b/erpnext/www/me.py @@ -11,3 +11,88 @@ def get_context(context): get_payments_context(context) context.current_user = frappe.get_doc("User", frappe.session.user) + + active_tokens = frappe.get_all( + "OAuth Bearer Token", + filters=[["user", "=", frappe.session.user]], + fields=["client"], + distinct=True, + order_by="creation", + ) + context.third_party_apps = True if active_tokens else False + + customers, suppliers = get_customers_suppliers("Customer", frappe.session.user) + if customers: + customer = frappe.get_cached_doc("Customer", customers[0]) + payment_gateways = frappe.get_all( + "Payment Gateway", filters={"gateway_settings": "Stripe Settings", "disabled": 0}, pluck="name" + ) + if stripe_references := customer.get( + "payment_gateways_references", dict(payment_gateway=("in", payment_gateways)) + ): + gateway_controller = frappe.db.get_value( + "Payment Gateway", stripe_references[0].payment_gateway, "gateway_controller" + ) + context.enable_stripe = True + context.stripe_payment_methods = get_customer_payment_methods( + stripe_references, customer.name, gateway_controller + ) + context.publishable_key = get_api_key(gateway_controller) + + context.credits_balance = get_balance(customer.name) + + +def get_customer_payment_methods(controller, customer, stripe_customer_id): + try: + stripe_settings = frappe.get_doc("Stripe Settings", controller) + return StripePaymentMethod(stripe_settings).get_list(stripe_customer_id) + except Exception: + frappe.log_error( + _("[Portal] Stripe payment methods retrieval error for {0}").format(customer), + ) + + +@frappe.whitelist() +def remove_payment_card(id): + try: + customers, suppliers = get_customers_suppliers("Customer", frappe.session.user) + if customers: + customer = frappe.get_cached_doc("Customer", customers[0]) + payment_gateways = frappe.get_all( + "Payment Gateway", filters={"gateway_settings": "Stripe Settings", "disabled": 0}, pluck="name" + ) + if stripe_references := customer.get( + "payment_gateways_references", dict(payment_gateway=("in", payment_gateways)) + ): + gateway_controller = frappe.db.get_value( + "Payment Gateway", stripe_references[0].payment_gateway, "gateway_controller" + ) + stripe_settings = frappe.get_doc("Stripe Settings", gateway_controller) + return StripePaymentMethod(stripe_settings).detach(id) + + except Exception: + frappe.log_error(_("[Portal] Stripe payment source deletion error")) + + +@frappe.whitelist() +def add_new_payment_card(payment_method): + try: + customers, suppliers = get_customers_suppliers("Customer", frappe.session.user) + if customers: + customer = frappe.get_cached_doc("Customer", customers[0]) + payment_gateways = frappe.get_all( + "Payment Gateway", filters={"gateway_settings": "Stripe Settings", "disabled": 0}, pluck="name" + ) + if stripe_references := customer.get( + "payment_gateways_references", dict(payment_gateway=("in", payment_gateways)) + ): + gateway_controller = frappe.db.get_value( + "Payment Gateway", stripe_references[0].payment_gateway, "gateway_controller" + ) + stripe_settings = frappe.get_doc("Stripe Settings", gateway_controller) + return StripePaymentMethod(stripe_settings).attach( + payment_method, stripe_references[0].customer_id + ) + + except Exception: + frappe.log_error(_("[Portal] New stripe payment source registration error"))