Skip to content

Commit

Permalink
Changes to use mollie mandates instead of subscriptions for recurring…
Browse files Browse the repository at this point in the history
… payments

pre-commit fixes

Updated name of module

Updates
  • Loading branch information
ByteMeAsap authored and tarteo committed Aug 21, 2024
1 parent d084f09 commit 2f204d2
Show file tree
Hide file tree
Showing 16 changed files with 175 additions and 283 deletions.
3 changes: 1 addition & 2 deletions sale_recurring_payment/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
],
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record model="ir.cron" id="update_payment_provider_subscription_for_sale_subscription_cron">
<field name="name">Update Payment Provider Subscription for Subscriptions</field>
<record model="ir.cron" id="update_payment_provider_payments_for_sale_subscription_cron">
<field name="name">Update Payment Provider Payments for Subscriptions</field>
<field name="model_id" ref="subscription_oca.model_sale_subscription" />
<field name="state">code</field>
<field name="code">model.cron_update_payment_provider_subscriptions()</field>
<field name="code">model.cron_update_payment_provider_payments()</field>
<field name="user_id" ref="base.user_root" />
<field name="interval_number">1</field>
<field name="interval_type">days</field>
Expand Down
2 changes: 1 addition & 1 deletion sale_recurring_payment/models/__init__.py
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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,
)
Expand Down
100 changes: 42 additions & 58 deletions sale_recurring_payment/models/payment_transaction.py
Original file line number Diff line number Diff line change
@@ -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,
)

Expand All @@ -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:
Expand All @@ -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

Expand All @@ -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,
)
75 changes: 22 additions & 53 deletions sale_recurring_payment/models/sale_subscription.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand All @@ -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),
Expand All @@ -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):
Expand All @@ -109,42 +88,32 @@ 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:
for record in self:
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(
Expand All @@ -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,
Expand All @@ -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"),
),
Expand Down
Loading

0 comments on commit 2f204d2

Please sign in to comment.