Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[16.0] [ADD] recurring_payment_stripe module #1151

Open
wants to merge 40 commits into
base: 16.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
e97eaf9
[ADD] Add recurring payments with stripe
mjavint Nov 14, 2024
e47bd6c
[FIX] Fixed recurring payments
mjavint Nov 14, 2024
e72ee4b
[FIX] Resolved all suggestion
mjavint Nov 28, 2024
ea7a9b2
[ADD] Add precommit rules
mjavint Nov 28, 2024
fa09388
Merge pull request #2 from BinhexTeam/16.0-dev
mjavint Nov 28, 2024
6007122
[ADD] Add stripe library
mjavint Dec 3, 2024
d21d398
[ADD] Add stripe library
mjavint Dec 3, 2024
02b0b61
Merge pull request #4 from BinhexTeam/16.0-dev
mjavint Dec 3, 2024
ea94dbd
[FIX] Fixed precommit error
mjavint Dec 3, 2024
b87b473
[FIX] Fixed precommit error
mjavint Dec 3, 2024
e0d75a0
[FIX] Fixed precommit flake error
mjavint Dec 3, 2024
ec6d741
[FIX] Fixed translation error in account_move.py and spaces inside su…
mjavint Dec 6, 2024
d359985
[FIX] Fixed translation error in account_move.py and spaces inside su…
mjavint Dec 6, 2024
75ac6af
[FIX] Fixed payment transaction and design modular
mjavint Dec 27, 2024
ea45267
[FIX] Fixed payment transaction and design modular (#6)
mjavint Dec 27, 2024
deb9ee4
[FIX] Fixed test implementation
mjavint Dec 30, 2024
02fa75e
[FIX] Fixed test implementation
mjavint Dec 30, 2024
a386028
16.0 dev (#7)
mjavint Dec 30, 2024
2c48daa
[FIX] Fixed test implementation
mjavint Dec 30, 2024
988f4b7
Merge remote-tracking branch 'origin/16.0-add-recurring_payments_stri…
mjavint Dec 30, 2024
71175d7
16.0 dev (#8)
mjavint Dec 30, 2024
5024bfa
Merge remote-tracking branch 'origin/16.0-add-recurring_payments_stri…
mjavint Dec 30, 2024
94b7070
[FIX] Fixed test implementation
mjavint Dec 30, 2024
1f8580c
16.0 dev (#9)
mjavint Dec 30, 2024
2e59f9b
[FIX] Fixed test implementation
mjavint Dec 30, 2024
d466972
Merge remote-tracking branch 'origin/16.0-add-recurring_payments_stri…
mjavint Dec 30, 2024
eee35fd
16.0 dev (#10)
mjavint Dec 30, 2024
4616bf8
[FIX] Fixed precommit
mjavint Dec 30, 2024
a3016a5
Merge remote-tracking branch 'origin/16.0-add-recurring_payments_stri…
mjavint Dec 30, 2024
db73567
16.0 dev (#11)
mjavint Dec 30, 2024
917df24
[FIX] Fixed implementation
mjavint Dec 30, 2024
dec7546
Merge remote-tracking branch 'origin/16.0-add-recurring_payments_stri…
mjavint Dec 30, 2024
81d188b
16.0 dev (#12)
mjavint Dec 30, 2024
b438fcb
16.0 dev (#13)
mjavint Jan 6, 2025
64530b6
Merge remote-tracking branch 'origin/16.0-add-recurring_payments_stri…
mjavint Jan 6, 2025
6042cd0
[FIX] Fixed implementation tests
mjavint Jan 6, 2025
866ef26
Merge branch '16.0-dev' into 16.0-add-recurring_payments_stripe
mjavint Jan 6, 2025
5329d7c
[FIX] Fixed implementation tests branch
mjavint Jan 6, 2025
0b8a36e
# This is a combination of 12 commits.
mjavint Nov 14, 2024
4a83366
[FIX] Fixed and squash old commit
mjavint Jan 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ addon | version | maintainers | summary
[contract_variable_quantity](contract_variable_quantity/) | 16.0.1.1.0 | | Variable quantity in contract recurrent invoicing
[product_contract](product_contract/) | 16.0.1.0.0 | [![sbejaoui](https://github.com/sbejaoui.png?size=30px)](https://github.com/sbejaoui) | Recurring - Product Contract
[subscription_oca](subscription_oca/) | 16.0.1.0.0 | | Generate recurring invoices.
[recurring_payments_stripe](recurring_payments_stripe/) | 16.0.1.0.0 | | Pay automatically with Stripe

[//]: # (end addons)

Expand Down
2 changes: 1 addition & 1 deletion contract_sale/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Contract from Sale
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:00fb3cdc565442ffcd3351e97d0516ce6f4ceb55402981b183f7e717a1e23173
!! source digest: sha256:c2e49dd78cebc553bbe7e5bfa2c8658e29e120878e688512e9599b43144815dd
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png
Expand Down
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
13 changes: 8 additions & 5 deletions contract_sale/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@

/*
:Author: David Goodger ([email protected])
:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.

Default cascading style sheet for the HTML output of Docutils.
Despite the name, some widely supported CSS2 features are used.

See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
Expand Down Expand Up @@ -274,7 +275,7 @@
margin-left: 2em ;
margin-right: 2em }

pre.code .ln { color: grey; } /* line numbers */
pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
Expand All @@ -300,7 +301,7 @@
span.pre {
white-space: pre }

span.problematic {
span.problematic, pre.problematic {
color: red }

span.section-subtitle {
Expand Down Expand Up @@ -366,7 +367,7 @@ <h1 class="title">Contract from Sale</h1>
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:00fb3cdc565442ffcd3351e97d0516ce6f4ceb55402981b183f7e717a1e23173
!! source digest: sha256:c2e49dd78cebc553bbe7e5bfa2c8658e29e120878e688512e9599b43144815dd
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Production/Stable" src="https://img.shields.io/badge/maturity-Production%2FStable-green.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/contract/tree/16.0/contract_sale"><img alt="OCA/contract" src="https://img.shields.io/badge/github-OCA%2Fcontract-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/contract-16-0/contract-16-0-contract_sale"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/contract&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This module allows access to contracts for sale employees without account
Expand Down Expand Up @@ -428,7 +429,9 @@ <h2><a class="toc-backref" href="#toc-entry-5">Contributors</a></h2>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-6">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
</a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
Expand Down
78 changes: 78 additions & 0 deletions recurring_payment_stripe/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
=============================
Recurring Payment with Stripe
=============================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:7aacc5b46917b14e43404143f0ccd1519d1411d580330b8eb8a8ef608fe3a5a1
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fcontract-lightgray.png?logo=github
:target: https://github.com/OCA/contract/tree/16.0/recurring_payment_stripe
:alt: OCA/contract
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/contract-16-0/contract-16-0-recurring_payment_stripe
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/contract&target_branch=16.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|


**Table of contents**

.. contents::
:local:

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/contract/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/contract/issues/new?body=module:%20recurring_payment_stripe%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
~~~~~~~

* Binhex

Maintainers
~~~~~~~~~~~

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

.. |maintainer-mjavint| image:: https://github.com/mjavint.png?size=40px
:target: https://github.com/mjavint
:alt: mjavint

Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:

|maintainer-mjavint|

This module is part of the `OCA/contract <https://github.com/OCA/contract/tree/16.0/recurring_payment_stripe>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
1 change: 1 addition & 0 deletions recurring_payment_stripe/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
17 changes: 17 additions & 0 deletions recurring_payment_stripe/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "Recurring Payment with Stripe",
"version": "16.0.1.0.0",
"summary": """Recurring Payment with Stripe""",
"author": "Binhex, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/contract",
"license": "AGPL-3",
"category": "Subscription Management",
"depends": ["subscription_recurring_payment", "payment_stripe"],
"data": [
"data/ir_cron.xml",
],
"installable": True,
"auto_install": False,
"external_dependencies": {"python": ["stripe"]},
"maintainers": ["mjavint"],
}
13 changes: 13 additions & 0 deletions recurring_payment_stripe/data/ir_cron.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<odoo noupdate="1">
<!-- Cron Job para Procesar Facturas Vencidas Diariamente -->
<record id="ir_cron_process_due_invoices" model="ir.cron">
<field name="name">Process Overdue Invoices for Subscriptions</field>
<field name="model_id" ref="recurring_payment_stripe.model_account_move" />
<field name="state">code</field>
<field name="code">model.cron_process_due_invoices()</field>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="active">True</field>
</record>
</odoo>
2 changes: 2 additions & 0 deletions recurring_payment_stripe/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import account_move
from . import sale_subscription
149 changes: 149 additions & 0 deletions recurring_payment_stripe/models/account_move.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import logging

import stripe

from odoo import _, api, models
from odoo.exceptions import UserError

_logger = logging.getLogger(__name__)


class AccountMove(models.Model):
_inherit = "account.move"

def action_register_payment(self):
"""
Override `action_register_payment` to automatically process Stripe
payment on subscriptions.
"""
for invoice in self:
# Find the subscription associated with the invoice, if it exists
subscription = invoice.subscription_id

# Check if the subscription is recurring and has a payment method
if subscription and subscription.charge_automatically:
provider = subscription.provider_id
stripe.api_key = provider.stripe_secret_key
token = self._create_token(subscription)
try:
# Create the PaymentIntent and confirm it immediately
payment_intent = stripe.PaymentIntent.create(
# Stripe uses cents
amount=int(invoice.amount_total * 100),
currency=invoice.currency_id.name.lower(),
customer=token.provider_ref,
payment_method=token.stripe_payment_method,
automatic_payment_methods={"enabled": True},
# For automatic payments without user intervention
off_session=True,
# Confirm the PaymentIntent immediately
confirm=True,
metadata={"odoo_invoice_id": str(invoice.id)},
)

# Handling the result of the PaymentIntent
if payment_intent["status"] != "succeeded":
raise UserError(

Check warning on line 46 in recurring_payment_stripe/models/account_move.py

View check run for this annotation

Codecov / codecov/patch

recurring_payment_stripe/models/account_move.py#L46

Added line #L46 was not covered by tests
_("Payment failed with status: %s")
% payment_intent["status"]
)

# If the payment is successful, record the payment on
# the invoice
Payment = self.env["account.payment"].sudo()
payment_vals = {
"journal_id": self.env["account.journal"]
.search([("type", "=", "bank")], limit=1)
.id,
"amount": invoice.amount_total,
"payment_type": "inbound",
"partner_type": "customer",
"partner_id": invoice.partner_id.id,
"payment_method_id": self.env.ref(
"account.account_payment_method_manual_in"
).id,
"ref": f"Stripe - {payment_intent['id']}",
}
payment = Payment.create(payment_vals)
payment.action_post()
invoice.payment_state = "paid"

except stripe.StripeError as e:
raise UserError(f"Stripe error: {e}") from e

Check warning on line 72 in recurring_payment_stripe/models/account_move.py

View check run for this annotation

Codecov / codecov/patch

recurring_payment_stripe/models/account_move.py#L71-L72

Added lines #L71 - L72 were not covered by tests

else:
return super(AccountMove, self).action_register_payment()

Check warning on line 75 in recurring_payment_stripe/models/account_move.py

View check run for this annotation

Codecov / codecov/patch

recurring_payment_stripe/models/account_move.py#L75

Added line #L75 was not covered by tests

def _create_token(self, subscription):
provider = subscription.provider_id
# Search for an existing payment token for the given provider and
# partner
token = self.env["payment.token"].search(
[
("provider_id", "=", provider.id),
("partner_id", "=", subscription.partner_id.id),
],
limit=1,
)

# If no token exists, create a new one
if not token:
stripe.api_key = provider.stripe_secret_key

# Create a new Stripe customer
customer = stripe.Customer.create(
email=subscription.partner_id.email,
name=subscription.partner_id.name,
metadata={"odoo_subscription": str(subscription.name)},
)

# Create a new payment token in Odoo
new_token = self.env["payment.token"].create(
{
"provider_id": provider.id,
"partner_id": subscription.partner_id.id,
"provider_ref": customer.id,
"verified": True,
}
)

# Retrieve the default payment method for the customer,
# or create one if it doesn't exist
new_token.stripe_payment_method = (
stripe.PaymentMethod.list(
customer=customer.id,
type="card",
limit=1,
)
.data[0]
.id
if stripe.PaymentMethod.list(
customer=customer.id, type="card", limit=1
).data
else stripe.Customer.create_source(
customer.id,
source="tok_visa",
).id
)

# Assign the new token to the variable
token = new_token

return token

@api.model
def cron_process_due_invoices(self):
"""Process payment of overdue invoices for recurring subscriptions."""

for invoice in self:
# Find the subscription associated with the invoice
subscription = invoice.subscription_id

# Check if it's a recurring subscription with Stripe
if subscription and subscription.charge_automatically:
try:
# Register the payment
invoice.action_post()
invoice.action_register_payment()
except Exception as e:
_logger.error(f"Error Processing Due Invoices: {str(e)}")

Check warning on line 149 in recurring_payment_stripe/models/account_move.py

View check run for this annotation

Codecov / codecov/patch

recurring_payment_stripe/models/account_move.py#L148-L149

Added lines #L148 - L149 were not covered by tests
12 changes: 12 additions & 0 deletions recurring_payment_stripe/models/sale_subscription.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from odoo import fields, models


class SaleSubscription(models.Model):
_inherit = "sale.subscription"

charge_automatically = fields.Boolean(default=True)
provider_id = fields.Many2one(
string="Provider",
domain=[("code", "=", "stripe")],
comodel_name="payment.provider",
)
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Loading
Loading