From 2f204d24b212872b3e3994b18802100099c8452e Mon Sep 17 00:00:00 2001 From: Anjeel Haria Date: Thu, 4 Jul 2024 18:48:17 +0530 Subject: [PATCH] Changes to use mollie mandates instead of subscriptions for recurring payments pre-commit fixes Updated name of module Updates --- sale_recurring_payment/__manifest__.py | 3 +- ...ate_payment_provider_subscription_cron.xml | 14 -- ...update_payment_provider_payments_cron.xml} | 6 +- sale_recurring_payment/models/__init__.py | 2 +- ...ription.py => payment_provider_mandate.py} | 14 +- .../models/payment_transaction.py | 100 ++++++-------- .../models/sale_subscription.py | 75 +++-------- .../models/sale_subscription_line.py | 4 +- .../security/ir.model.access.csv | 6 +- sale_recurring_payment/tests/test_fields.py | 2 +- .../views/sale_subscription_view.xml | 2 +- sale_recurring_payment_mollie/__manifest__.py | 4 +- .../models/__init__.py | 1 - .../models/payment_provider.py | 2 +- .../models/payment_transaction.py | 126 ++++++++---------- .../models/sale_subscription.py | 97 +++++--------- 16 files changed, 175 insertions(+), 283 deletions(-) delete mode 100644 sale_recurring_payment/data/terminate_payment_provider_subscription_cron.xml rename sale_recurring_payment/data/{update_payment_provider_subscription_cron.xml => update_payment_provider_payments_cron.xml} (61%) rename sale_recurring_payment/models/{payment_provider_subscription.py => payment_provider_mandate.py} (57%) diff --git a/sale_recurring_payment/__manifest__.py b/sale_recurring_payment/__manifest__.py index 4205846..21b2eae 100644 --- a/sale_recurring_payment/__manifest__.py +++ b/sale_recurring_payment/__manifest__.py @@ -8,8 +8,7 @@ "depends": ["subscription_oca", "account_payment"], "data": [ "security/ir.model.access.csv", - "data/update_payment_provider_subscription_cron.xml", - "data/terminate_payment_provider_subscription_cron.xml", + "data/update_payment_provider_payments_cron.xml", "views/sale_subscription_view.xml", "views/payment_provider_view.xml", ], diff --git a/sale_recurring_payment/data/terminate_payment_provider_subscription_cron.xml b/sale_recurring_payment/data/terminate_payment_provider_subscription_cron.xml deleted file mode 100644 index f050857..0000000 --- a/sale_recurring_payment/data/terminate_payment_provider_subscription_cron.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - Terminate Payment Provider Subscription for Subscriptions - - code - model.cron_terminate_payment_provider_subscriptions() - - 1 - days - -1 - - - diff --git a/sale_recurring_payment/data/update_payment_provider_subscription_cron.xml b/sale_recurring_payment/data/update_payment_provider_payments_cron.xml similarity index 61% rename from sale_recurring_payment/data/update_payment_provider_subscription_cron.xml rename to sale_recurring_payment/data/update_payment_provider_payments_cron.xml index 10e438e..1dccc07 100644 --- a/sale_recurring_payment/data/update_payment_provider_subscription_cron.xml +++ b/sale_recurring_payment/data/update_payment_provider_payments_cron.xml @@ -1,10 +1,10 @@ - - Update Payment Provider Subscription for Subscriptions + + Update Payment Provider Payments for Subscriptions code - model.cron_update_payment_provider_subscriptions() + model.cron_update_payment_provider_payments() 1 days diff --git a/sale_recurring_payment/models/__init__.py b/sale_recurring_payment/models/__init__.py index 764962c..f3641ce 100644 --- a/sale_recurring_payment/models/__init__.py +++ b/sale_recurring_payment/models/__init__.py @@ -1,5 +1,5 @@ from . import sale_subscription from . import sale_subscription_line -from . import payment_provider_subscription +from . import payment_provider_mandate from . import payment_provider from . import payment_transaction diff --git a/sale_recurring_payment/models/payment_provider_subscription.py b/sale_recurring_payment/models/payment_provider_mandate.py similarity index 57% rename from sale_recurring_payment/models/payment_provider_subscription.py rename to sale_recurring_payment/models/payment_provider_mandate.py index 4444e32..560772a 100644 --- a/sale_recurring_payment/models/payment_provider_subscription.py +++ b/sale_recurring_payment/models/payment_provider_mandate.py @@ -1,24 +1,24 @@ from odoo import fields, models -class PaymentProviderSubscription(models.Model): - """The payment provider subscription is attached to a sale subscription and represents a - subscription that is created with payment providers to accept recurring +class PaymentProviderMandate(models.Model): + """The payment provider mandate is attached to a sale subscription and represents a + mandate that is created with payment providers to accept recurring payments for sale subscriptions """ - _name = "payment.provider.subscription" - _description = "Payment Provider Subscription" + _name = "payment.provider.mandate" + _description = "Payment Provider Mandate" _rec_name = "reference" reference = fields.Char( - help="The reference of the subscription", + help="The reference of the mandate", readonly=True, required=True, ) payment_transaction_ids = fields.One2many( "payment.transaction", - "payment_provider_subscription_id", + "payment_provider_mandate_id", string="Payment Transactions", readonly=True, ) diff --git a/sale_recurring_payment/models/payment_transaction.py b/sale_recurring_payment/models/payment_transaction.py index 1a0d3ca..4bc7644 100644 --- a/sale_recurring_payment/models/payment_transaction.py +++ b/sale_recurring_payment/models/payment_transaction.py @@ -1,12 +1,13 @@ from odoo import api, fields, models +from odoo.fields import Command class PaymentTransaction(models.Model): _inherit = "payment.transaction" - payment_provider_subscription_id = fields.Many2one( - "payment.provider.subscription", - string="Payment Provider Subscription", + payment_provider_mandate_id = fields.Many2one( + "payment.provider.mandate", + string="Payment Provider Mandate", readonly=True, ) @@ -24,13 +25,13 @@ def _process_notification_data(self, data): create_sub_for_invoice = ( self.invoice_ids and self.invoice_ids[0].subscription_id - and not self.invoice_ids[0].subscription_id.payment_provider_subscription_id + and not self.invoice_ids[0].subscription_id.payment_provider_mandate_id ) if not create_sub_for_sale_order and not create_sub_for_invoice: return res payment_data = self._provider_get_payment_data() - if not self._must_create_subscription(payment_data): + if not self._must_create_mandate(payment_data): return res if create_sub_for_sale_order: @@ -44,79 +45,62 @@ def _process_notification_data(self, data): invoice = self.invoice_ids[0] subscription = invoice.subscription_id # pylint: disable=assignment-from-none - subscription_for_payment_provider = ( - self._create_subscription_for_payment_provider(subscription, payment_data) + mandate_for_payment_provider = self._get_mandate_reference_for_payment_provider( + payment_data ) - payment_provider_subscription = self._create_payment_provider_subscription( - subscription_for_payment_provider + payment_provider_mandate = self._create_payment_provider_mandate( + mandate_for_payment_provider ) - subscription.payment_provider_subscription_id = payment_provider_subscription.id - self.payment_provider_subscription_id = payment_provider_subscription.id + subscription.payment_provider_mandate_id = payment_provider_mandate.id + self.payment_provider_mandate_id = payment_provider_mandate.id def _provider_get_payment_data(self): self.ensure_one() return {} - def _must_create_subscription(self, data): + def _must_create_mandate(self, data): # This method needs to be extended in each provider module self.ensure_one() return False - def _create_subscription_for_payment_provider(self, subscription, payment_data): - # This method needs to be extended in each provider module. - # We expect to receive a data structure containing the payment data; - # We expect to return a data structure (depending on the payment provider implementation) describing the payment provider subscription + def _get_mandate_reference_for_payment_provider(self, payment): + # This method needs to be extended in each provider module self.ensure_one() - return None + return False - def _create_payment_provider_subscription(self, subscription): - # This method needs to be extended in each provider module. - # We expect to receive the payment provider subscription; - # This method processes them in order to create an Odoo payment.provider.subscription - self.ensure_one() - return self.env["payment.provider.subscription"] + def _create_payment_provider_mandate(self, mandate_reference): + # We expect to receive the payment provider mandate reference; + # This method processes them in order to create an Odoo payment.provider.mandate + return self.env["payment.provider.mandate"].create( + {"reference": mandate_reference, "provider_id": self.provider_id.id} + ) - def _process_payment_provider_subscription_recurring_payment( - self, subscription, payment - ): + def _process_payment_provider_recurring_payment(self, subscription, invoice): # This method needs to be extended in each provider module. # This method should process payment transactions(recurring payments) for subscription invoices - payment_transaction = self._get_payment_transaction(subscription, payment) - done_payment_transaction = ( - payment_transaction.update_state_recurring_payment_transaction( - subscription.payment_provider_subscription_id.provider_id, payment - ) + payment_transaction = self._get_payment_transaction_by_mandate_id_for_invoice( + invoice.id, + subscription.payment_provider_mandate_id.id, ) - if done_payment_transaction: - unpaid_invoices = subscription.invoice_ids.filtered( - lambda i: i.payment_state == "not_paid" - ).sorted("invoice_date") - unpaid_invoice = unpaid_invoices and unpaid_invoices[0] - if not unpaid_invoice: - subscription.generate_invoice() - unpaid_invoice = subscription.invoice_ids.filtered( - lambda i: i.payment_state == "not_paid" - ).sorted("invoice_date")[0] - done_payment_transaction.invoice_ids = [(6, 0, unpaid_invoice.ids)] - done_payment_transaction._reconcile_after_done() - return None + return payment_transaction - def _get_payment_transaction(self, subscription, payment): + def create_provider_recurring_payment(self, subscription): # This method needs to be extended in each provider module. - # This method should search for payment transaction if not found should create one with provided details - return self.env["payment.transaction"] + # This method should create recurring payments at the provider end + # We expect to receive a data structure containing the payment data(depending on the payment provider implementation) + return None def _prepare_vals_for_recurring_payment_transaction_for_subscription( - self, provider_reference, amount, subscription, currency + self, invoice, subscription ): # This method should return the vals for creating payment transactions vals = { - "amount": amount, - "currency_id": currency.id or subscription.currency_id.id, - "provider_reference": provider_reference, + "amount": invoice.amount_residual, + "currency_id": subscription.currency_id.id, "partner_id": subscription.partner_id.id, - "payment_provider_subscription_id": subscription.payment_provider_subscription_id.id, - "provider_id": subscription.payment_provider_subscription_id.provider_id.id, + "payment_provider_mandate_id": subscription.payment_provider_mandate_id.id, + "provider_id": subscription.payment_provider_mandate_id.provider_id.id, + "invoice_ids": [Command.set([invoice.id])], } return vals @@ -126,14 +110,14 @@ def update_state_recurring_payment_transaction(self, provider, payment): # This method should update the state of payment transactions and return done payment transactions if any return self.env["payment.transaction"] - def _get_payment_transaction_by_provider_reference( - self, provider_reference, provider_id + def _get_payment_transaction_by_mandate_id_for_invoice( + self, invoice_id, payment_provider_mandate_id ): - # This method should search for payment transaction with provider reference provided + # This method should search for payment transaction with payment_provider_mandate_id and invoice provided return self.search( [ - ("provider_reference", "=", provider_reference), - ("provider_id", "=", provider_id), + ("payment_provider_mandate_id", "=", payment_provider_mandate_id), + ("invoice_ids", "in", invoice_id), ], limit=1, ) diff --git a/sale_recurring_payment/models/sale_subscription.py b/sale_recurring_payment/models/sale_subscription.py index 3d08280..9d7949e 100644 --- a/sale_recurring_payment/models/sale_subscription.py +++ b/sale_recurring_payment/models/sale_subscription.py @@ -13,17 +13,17 @@ class SaleSubscription(models.Model): _inherit = "sale.subscription" - payment_provider_subscription_id = fields.Many2one( - "payment.provider.subscription", - string="Payment Provider Subscription", + payment_provider_mandate_id = fields.Many2one( + "payment.provider.mandate", + string="Payment Provider Mandate", readonly=True, ) - is_payment_provider_subscription_terminated = fields.Boolean() + is_payment_provider_mandate_terminated = fields.Boolean() last_date_invoiced = fields.Date( - help="Date when last invoice was generated for the subscription", + help="Date when last invoice was generated for the mandate", ) paid_for_date = fields.Date( - help="The date until the subscription is paid for (invoice date + recurring rule)", + help="The date until the mandate is paid for (invoice date + recurring rule)", compute="_compute_paid_for_date", store=True, ) @@ -45,13 +45,13 @@ def _compute_paid_for_date(self): sub.paid_for_date = paid_for_date @api.model - def cron_update_payment_provider_subscriptions(self): + def cron_update_payment_provider_payments(self): date_ref = fields.Date.context_today(self) sale_subscriptions = self.search( [ ("template_id.invoicing_mode", "!=", "sale_and_invoice"), - ("payment_provider_subscription_id", "!=", False), - ("is_payment_provider_subscription_terminated", "=", False), + ("payment_provider_mandate_id", "!=", False), + ("is_payment_provider_mandate_terminated", "=", False), "|", ("recurring_next_date", "<=", date_ref), ("last_date_invoiced", "=", date_ref), @@ -65,37 +65,16 @@ def cron_update_payment_provider_subscriptions(self): ).with_company(company) for sale_subscription in sale_subscriptions_to_update: try: - sale_subscription.update_sale_subscription_payments_and_subscription_status( - date_ref - ) + sale_subscription.update_sale_subscription_payments(date_ref) except Exception as exception: sale_subscription._log_provider_exception( exception, "updating subscription" ) return True - def update_sale_subscription_payments_and_subscription_status(self, date_ref): + def update_sale_subscription_payments(self, date_ref): # This method needs to be extended in each provider module. - # This method updates the payments, their status and subscription status for sale subscriptions - return True - - @api.model - def cron_terminate_payment_provider_subscriptions(self): - date_ref = fields.Date.context_today(self) - sale_subscriptions = self.search( - [ - ("payment_provider_subscription_id", "!=", False), - ("is_payment_provider_subscription_terminated", "=", False), - ("date", "<=", date_ref), - ] - ) - for sale_subscription in sale_subscriptions: - try: - sale_subscription.terminate_payment_provider_subscription() - except Exception as exception: - sale_subscription._log_provider_exception( - exception, "terminating subscription" - ) + # This method updates the payments and their status for sale subscriptions return True def generate_invoice(self): @@ -109,25 +88,15 @@ def write(self, values): if ( record.stage_id and record.stage_id.type == "post" - and record.payment_provider_subscription_id - and record.is_payment_provider_subscription_terminated + and record.payment_provider_mandate_id + and record.is_payment_provider_mandate_terminated ): raise UserError( _( - "Terminated subscriptions with payment provider subscription also terminated cannot be " + "Terminated subscriptions with payment provider mandate also terminated cannot be " "updated. Please generate a new subscription" ) ) - if "sale_subscription_line_ids" in values: - # Don't allow changes on in-progress subs because atm it will not be reflected in the payment provider (e.g. Mollie) - if self.filtered( - lambda sub: sub.payment_provider_subscription_id and sub.in_progress - ): - raise UserError( - _( - "Cannot change in-progress subscriptions. Please close this one and create a new one." - ) - ) res = super().write(values) if "stage_id" in values: @@ -135,16 +104,16 @@ def write(self, values): if ( record.stage_id and record.stage_id.type == "post" - and record.payment_provider_subscription_id - and not record.is_payment_provider_subscription_terminated + and record.payment_provider_mandate_id + and not record.is_payment_provider_mandate_terminated ): - record.terminate_payment_provider_subscription() + record.terminate_payment_provider_mandate() return res @api.model - def terminate_payment_provider_subscription(self): + def terminate_payment_provider_mandate(self): # This method cancels/terminates the subscription - # This method needs to be extended in each provider module to end the subscriptions on provider end. + # This method needs to be extended in each provider module to end the mandates on provider end. vals = {"date": datetime.today(), "recurring_next_date": False} stage = self.stage_id closed_stage = self.env["sale.subscription.stage"].search( @@ -160,7 +129,7 @@ def _log_provider_exception(self, exception, process): _logger.warning( _("Payment Provider %(name)s: Error " "while %(process)s"), dict( - name=self.payment_provider_subscription_id.provider_id.name, + name=self.payment_provider_mandate_id.provider_id.name, process=process, ), exc_info=True, @@ -171,7 +140,7 @@ def _log_provider_exception(self, exception, process): " while {process} - {exception}. See server logs for " "more details." ).format( - name=self.payment_provider_subscription_id.provider_id.name, + name=self.payment_provider_mandate_id.provider_id.name, process=process, exception=escape(str(exception)) or _("N/A"), ), diff --git a/sale_recurring_payment/models/sale_subscription_line.py b/sale_recurring_payment/models/sale_subscription_line.py index 3640d95..edb9bf9 100644 --- a/sale_recurring_payment/models/sale_subscription_line.py +++ b/sale_recurring_payment/models/sale_subscription_line.py @@ -7,10 +7,10 @@ class SaleSubscriptionLine(models.Model): def create(self, vals): record = super().create(vals) - if self.sale_subscription_id.payment_provider_subscription_id: + if self.sale_subscription_id.payment_provider_mandate_id: raise ValidationError( _( - "New subscription line cannot be added for subscription with ongoing payment provider subscription" + "New subscription line cannot be added for subscription with ongoing payment provider mandate" ) ) return record diff --git a/sale_recurring_payment/security/ir.model.access.csv b/sale_recurring_payment/security/ir.model.access.csv index 81d1be0..0dba5d2 100644 --- a/sale_recurring_payment/security/ir.model.access.csv +++ b/sale_recurring_payment/security/ir.model.access.csv @@ -1,4 +1,4 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -payment_provider_subscription_all,payment.provider.subscription.all,model_payment_provider_subscription,,1,0,0,0 -payment_provider_subscription_system,payment.provider.subscription.system,model_payment_provider_subscription,base.group_system,1,1,1,1 -payment_provider_subscription_user,payment.provider.subscription.user,model_payment_provider_subscription,base.group_user,1,1,1,0 +payment_provider_mandate_all,payment.provider.mandate.all,model_payment_provider_mandate,,1,0,0,0 +payment_provider_mandate_system,payment.provider.mandate.system,model_payment_provider_mandate,base.group_system,1,1,1,1 +payment_provider_mandate_user,payment.provider.mandate.user,model_payment_provider_mandate,base.group_user,1,1,1,0 diff --git a/sale_recurring_payment/tests/test_fields.py b/sale_recurring_payment/tests/test_fields.py index 079b9b6..f8fa425 100644 --- a/sale_recurring_payment/tests/test_fields.py +++ b/sale_recurring_payment/tests/test_fields.py @@ -42,7 +42,7 @@ def test_paid_for_date(self): self.assertFalse(sub.paid_for_date) # Needs sale_recurring_payment_mollie e.g. installed to work, we can't do this here - # self.env["payment.transaction"]._process_payment_provider_subscription_recurring_payment( + # self.env["payment.transaction"]._process_payment_provider_mandate_recurring_payment( # sub # ) diff --git a/sale_recurring_payment/views/sale_subscription_view.xml b/sale_recurring_payment/views/sale_subscription_view.xml index 231230a..99db04b 100644 --- a/sale_recurring_payment/views/sale_subscription_view.xml +++ b/sale_recurring_payment/views/sale_subscription_view.xml @@ -5,7 +5,7 @@ - + diff --git a/sale_recurring_payment_mollie/__manifest__.py b/sale_recurring_payment_mollie/__manifest__.py index 5f54c7d..1bb3244 100644 --- a/sale_recurring_payment_mollie/__manifest__.py +++ b/sale_recurring_payment_mollie/__manifest__.py @@ -1,9 +1,9 @@ { - "name": "Website Sale - Recurring Payment Mollie", + "name": "Sale - Recurring Payment Mollie", "version": "16.0.0.1", "category": "eCommerce", "license": "LGPL-3", - "summary": "Website Sale - Recurring Payment Mollie", + "summary": "Sale - Recurring Payment Mollie", "website": "https://www.onestein.nl", "depends": ["payment_mollie_official", "sale_recurring_payment"], "data": [ diff --git a/sale_recurring_payment_mollie/models/__init__.py b/sale_recurring_payment_mollie/models/__init__.py index ffb5f2f..bf0bd89 100644 --- a/sale_recurring_payment_mollie/models/__init__.py +++ b/sale_recurring_payment_mollie/models/__init__.py @@ -1,4 +1,3 @@ -# from . import mollie_payment from . import sale_subscription from . import payment_provider from . import payment_transaction diff --git a/sale_recurring_payment_mollie/models/payment_provider.py b/sale_recurring_payment_mollie/models/payment_provider.py index beaff46..974789d 100644 --- a/sale_recurring_payment_mollie/models/payment_provider.py +++ b/sale_recurring_payment_mollie/models/payment_provider.py @@ -29,7 +29,7 @@ def _mollie_get_supported_methods( methods = super(PaymentAcquirerMollie, self)._mollie_get_supported_methods( order, invoice, amount, currency, partner_id ) - if order and order.group_subscription_lines(): + if (order and order.group_subscription_lines()) or (invoice and invoice.subscription_id): methods = methods.filtered( lambda m: m.method_code in ["creditcard", "ideal"] ) diff --git a/sale_recurring_payment_mollie/models/payment_transaction.py b/sale_recurring_payment_mollie/models/payment_transaction.py index c557a55..f8730b9 100644 --- a/sale_recurring_payment_mollie/models/payment_transaction.py +++ b/sale_recurring_payment_mollie/models/payment_transaction.py @@ -1,7 +1,6 @@ import logging from odoo import _, models -from odoo.exceptions import ValidationError _logger = logging.getLogger(__name__) @@ -29,11 +28,10 @@ def _get_transaction_customer_id(self): partner_obj.write({"mollie_customer_id": mollie_customer_id}) return mollie_customer_id - def _must_create_subscription(self, payment_data): + def _must_create_mandate(self, payment_data): # This method needs to be extended in each provider module if self.provider_code != "mollie": - return super()._must_create_subscription(payment_data) - + return super()._must_create_mandate(payment_data) payment_status = payment_data.get("status") if payment_status == "paid": return True @@ -42,7 +40,6 @@ def _must_create_subscription(self, payment_data): def _process_notification_data(self, data): if self.provider_code != "mollie": return super()._process_notification_data(data) - self._process_refund_transactions_status() if not self.state == "done": return super()._process_notification_data(data) @@ -83,7 +80,20 @@ def _mollie_prepare_payment_payload(self, api_type): "sequenceType": "first", } ) - + if self._context.get("recurring_mollie_payment"): + name = ( + self.sale_order_ids + and self.sale_order_ids.name + or self.invoice_ids + and self.invoice_ids.name + ) + payment_data.update( + { + "description": f"Payment for {name}", + "sequenceType": "recurring", + "mandateId": self._context.get("mandate_id"), + } + ) mollie_customer_id = self._get_transaction_customer_id() if api_type == "order": payment_data["payment"]["customerId"] = mollie_customer_id @@ -92,84 +102,60 @@ def _mollie_prepare_payment_payload(self, api_type): return payment_data, params - def _create_subscription_for_payment_provider(self, subscription, payment_data): + def _get_mandate_reference_for_payment_provider(self, payment): if self.provider_code != "mollie": - return super()._create_subscription_for_payment_provider(payment_data) - - mollie = self.env.ref("payment.payment_provider_mollie") - mollie_client = mollie._api_mollie_get_client() - amount = { - "currency": self.currency_id.name, - "value": "%.2f" % (self.amount + self.fees), - } - subscription_template = subscription.template_id - interval = "%s %s" % ( - subscription_template.recurring_interval, - subscription_template.recurring_rule_type, - ) - description = subscription.name - # webhook_url = urls.url_join(mollie.get_base_url(), MollieController._webhook_url) - mollie_customer_id = self._get_transaction_customer_id() - customer = mollie_client.customers.get(mollie_customer_id) - - data = { - "amount": amount or "", - "interval": interval or "", - "description": description or "", - # 'webhookUrl': webhook_url, - "startDate": subscription.recurring_next_date.strftime("%Y-%m-%d"), - "mandateId": payment_data and payment_data["mandateId"] or "", - } - try: - subscription = customer.subscriptions.create( - data - ) # Uncomment this when ready (issue is that localhost doesn't work with Mollie) - except Exception as e: - raise ValidationError(_(str(e))) from e - # subscription = {'resource': 'subscription', 'subscriptions_id': 'Test ID: %s' % (datetime.datetime.now())} - - return subscription + return super()._get_mandate_reference_for_payment_provider(payment) + return payment and payment["mandateId"] or "" def _provider_get_payment_data(self): if self.provider_code != "mollie": return super()._provider_get_payment_data() return self.provider_id._api_mollie_get_payment_data(self.provider_reference) - def _create_payment_provider_subscription(self, subscription): - res = super()._create_payment_provider_subscription(subscription) - if self.provider_code == "mollie": - res = self.env["payment.provider.subscription"].create( - {"reference": subscription["id"], "provider_id": self.provider_id.id} - ) - return res - - def _get_payment_transaction(self, subscription, payment): - payment_transaction = super()._get_payment_transaction(subscription, payment) - if subscription.payment_provider_subscription_id.provider_id.code == "mollie": - payment_transaction = self._get_payment_transaction_by_provider_reference( - payment["id"], - subscription.payment_provider_subscription_id.provider_id.id, - ) - if not payment_transaction: - provider_reference = payment["id"] - amount = payment.get("amount", {}).get("value", 0.0) - currency = ( - self.env["res.currency"].search( - [("name", "=", payment.get("amount", {}).get("currency", ""))], - limit=1, - ) - if payment.get("amount", {}).get("currency", "") - else subscription.currency_id - ) + def _process_payment_provider_recurring_payment(self, subscription, invoice): + payment_transaction = super()._process_payment_provider_recurring_payment( + subscription, invoice + ) + if not payment_transaction: + if subscription.payment_provider_mandate_id.provider_id.code == "mollie": payment_transaction = self.create( self._prepare_vals_for_recurring_payment_transaction_for_subscription( - provider_reference, amount, subscription, currency + invoice, subscription + ) + ) + payment = payment_transaction.create_provider_recurring_payment( + subscription + ) + payment_transaction.write({"provider_reference": payment["id"]}) + done_payment_transaction = ( + payment_transaction.update_state_recurring_payment_transaction( + subscription.payment_provider_mandate_id.provider_id, payment ) ) + if done_payment_transaction: + done_payment_transaction._reconcile_after_done() return payment_transaction + def create_provider_recurring_payment(self, subscription): + provider_payment = super().create_provider_recurring_payment(subscription) + if subscription.payment_provider_mandate_id.provider_id.code == "mollie": + mollie = self.env.ref("payment.payment_provider_mollie") + mollie_client = mollie._api_mollie_get_client() + mollie_payment_vals, params = self.with_context( + recurring_mollie_payment=True, + mandate_id=subscription.payment_provider_mandate_id.reference, + )._mollie_prepare_payment_payload("payment") + mollie_payment_vals.pop("redirectUrl") + mollie_payment_vals.pop("method") + mollie_payment_vals.pop("customerId") + customer = mollie_client.customers.get(self.partner_id.mollie_customer_id) + provider_payment = customer.payments.create(mollie_payment_vals) + return provider_payment + def update_state_recurring_payment_transaction(self, provider, payment): - payment_transaction = super()._get_payment_transaction(provider, payment) + payment_transaction = super().update_state_recurring_payment_transaction( + provider, payment + ) if provider.code == "mollie": payment_status = payment.get("status") if payment_status == "paid": diff --git a/sale_recurring_payment_mollie/models/sale_subscription.py b/sale_recurring_payment_mollie/models/sale_subscription.py index 9f669ef..ab00af0 100644 --- a/sale_recurring_payment_mollie/models/sale_subscription.py +++ b/sale_recurring_payment_mollie/models/sale_subscription.py @@ -1,7 +1,6 @@ import logging -from datetime import datetime -from odoo import _, api, models +from odoo import _, api, fields, models _logger = logging.getLogger(__name__) @@ -9,84 +8,54 @@ class SaleSubscription(models.Model): _inherit = "sale.subscription" - def update_sale_subscription_payments_and_subscription_status(self, date_ref): - # This method updates the subscription status/payments from mollie - if self.payment_provider_subscription_id.provider_id.code != "mollie": - return super().update_sale_subscription_payments_and_subscription_status( - date_ref - ) + def update_sale_subscription_payments(self, date_ref): + # This method updates the payments and their status for mollie + if self.payment_provider_mandate_id.provider_id.code != "mollie": + return super().update_sale_subscription_payments(date_ref) mollie = self.env.ref("payment.payment_provider_mollie") payment_transaction_obj = self.env["payment.transaction"] mollie_client = mollie._api_mollie_get_client() mollie_customer_id = self.partner_id.mollie_customer_id if mollie_customer_id: customer = mollie_client.customers.get(mollie_customer_id) - subscription = customer.subscriptions.get( - self.payment_provider_subscription_id.reference - ) - if subscription: - if subscription.get("STATUS_CANCELED", False) and subscription.get( - "STATUS_CANCELED", "" - ) == subscription.get("status", ""): - # As the subscription is cancelled, end the subscription - if "canceledAt" in subscription.keys(): - cancelled_date = datetime.strptime( - subscription.get("canceledAt")[0:19], "%Y-%m-%dT%H:%M:%S" - ).date() - else: - cancelled_date = date_ref - vals = { - "date": cancelled_date, - "recurring_next_date": False, - "is_payment_provider_subscription_terminated": True, - } - stage = self.stage_id - closed_stage = self.env["sale.subscription.stage"].search( - [("type", "=", "post")], limit=1 + mandate = customer.mandates.get(self.payment_provider_mandate_id.reference) + if mandate: + invoices = self.invoice_ids.filtered( + lambda i: i.invoice_date == date_ref + ) + if not invoices: + self.generate_invoice() + unpaid_invoice = self.invoice_ids.filtered( + lambda i: i.invoice_date == date_ref + and i.payment_state == "not_paid" ) - if stage != closed_stage: - vals["stage_id"]: closed_stage.id - self.write(vals) - msg = _( - "The mollie subscription for this subscription has been terminated on %(cancelled_date)s", - cancelled_date=cancelled_date, + else: + unpaid_invoice = invoices.filtered( + lambda i: i.payment_state == "not_paid" ) - self.sudo().message_post(body=msg) - subscription_payments = subscription.payments.list() - if subscription_payments and subscription_payments.get("_embedded"): - payment_list = subscription_payments["_embedded"].get( - "payments", [] + if unpaid_invoice: + payment_transaction_obj._process_payment_provider_recurring_payment( + self, unpaid_invoice ) - for payment in payment_list: - payment_transaction_obj._process_payment_provider_subscription_recurring_payment( - self, payment - ) return True @api.model - def terminate_payment_provider_subscription(self): - # This method terminates the subscription on mollie - if self.payment_provider_subscription_id.provider_id.code != "mollie": - return super().terminate_payment_provider_subscription() + def terminate_payment_provider_mandate(self): + # This method terminates the mandate on mollie + if self.payment_provider_mandate_id.provider_id.code != "mollie": + return super().terminate_payment_provider_mandate() else: - vals = super().terminate_payment_provider_subscription() + vals = super().terminate_payment_provider_mandate() mollie = self.env.ref("payment.payment_provider_mollie") mollie_client = mollie._api_mollie_get_client() customer = mollie_client.customers.get(self.partner_id.mollie_customer_id) - subscription = customer.subscriptions.delete( - self.payment_provider_subscription_id.reference + customer.mandates.delete(self.payment_provider_mandate_id.reference) + cancelled_date = fields.Date.context_today(self) + msg = _( + "The mollie mandate for this subscription has been terminated on %(cancelled_date)s", + cancelled_date=cancelled_date, ) - if subscription: - cancelled_date = False - if "canceledAt" in subscription.keys(): - cancelled_date = datetime.strptime( - subscription.get("canceledAt")[0:19], "%Y-%m-%dT%H:%M:%S" - ) - msg = _( - "The mollie subscription for this subscription has been terminated on %(cancelled_date)s", - cancelled_date=cancelled_date, - ) - self.sudo().message_post(body=msg) - vals["is_payment_provider_subscription_terminated"] = True + self.sudo().message_post(body=msg) + vals["is_payment_provider_mandate_terminated"] = True self.write(vals) return True