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_payments_stripe: Add recurring payments with stripe #2

Merged
merged 4 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
2 changes: 2 additions & 0 deletions recurring_payments_stripe/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import models
18 changes: 18 additions & 0 deletions recurring_payments_stripe/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
{
"name": "Recurring Payments with Stripe",
"version": "16.0.1.0.0",
"summary": """ Recurring Payments with Stripe """,
"author": "Binhex, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/contract",
"license": "AGPL-3",
"category": "Subscription Management",
"depends": ["subscription_oca", "payment_stripe", "payment"],
"data": [
"views/sale_subscription_views.xml",
"data/ir_cron.xml",
],
"application": True,
mjavint marked this conversation as resolved.
Show resolved Hide resolved
"installable": True,
"auto_install": False,
}
16 changes: 16 additions & 0 deletions recurring_payments_stripe/data/ir_cron.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<odoo>
<data noupdate="1">
<!-- Cron Job para Procesar Facturas Vencidas Diariamente -->
<record id="ir_cron_process_due_invoices" model="ir.cron">
<field name="name">Procesar Facturas Vencidas para Suscripciones
Recurrentes</field>
mjavint marked this conversation as resolved.
Show resolved Hide resolved
<field name="model_id" ref="recurring_payments_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>
</data>
</odoo>
3 changes: 3 additions & 0 deletions recurring_payments_stripe/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import sale_subscription
from . import account_move
169 changes: 169 additions & 0 deletions recurring_payments_stripe/models/account_move.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# -*- coding: utf-8 -*-
import logging
import stripe
mjavint marked this conversation as resolved.
Show resolved Hide resolved
from datetime import date

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

_logger = logging.getLogger(__name__)


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

# def stripe_pay_invoice(self):
# """Paga la factura en Stripe si `is_recurrent` está activado en la suscripción."""
# for invoice in self:
# subscription = invoice.invoice_date and self.env[
# "sale.subscription"
# ].search([("code", "=", invoice.invoice_date)], limit=1)
# if (
# subscription
# and subscription.is_recurrent
# and subscription.stripe_customer
# ):
# provider = self.env["payment.provider"].search(
# [("code", "=", "stripe")],
# )
# stripe.api_key = provider.stripe_secret_key
# token = self.env["payment.token"].search(
# [("provider_id", "=", provider.id)]
# )
# try:
# # Crea un PaymentIntent en Stripe para el monto de la factura
# payment_intent = stripe.PaymentIntent.create(
# # Stripe maneja montos en centavos
# amount=int(invoice.amount_total * 100),
# currency=invoice.currency_id.name.lower(),
# customer=token.provider_ref,
# payment_method=token.stripe_payment_method,
# payment_method_types=["card"],
# # Para pagos automáticos sin intervención del usuario
# off_session=True,
# # Confirmar el PaymentIntent inmediatamente
# confirm=True,
# metadata={"odoo_invoice_id": invoice.id},
# )

# # Confirmar el pago y actualizar el estado de la factura en Odoo
# if payment_intent["status"] == "succeeded":
# invoice.action_post()
# invoice.payment_state = "paid"
# else:
# raise Exception(
# f"Error en el pago de Stripe: {payment_intent['status']}"
# )

# except stripe.StripeError as e:
# raise UserError(f"Stripe error: {e.user_message or str(e)}")

# def action_post(self):
# """Sobreescribe el método `action_post` para procesar el pago automático si `is_recurrent` está activo."""
# res = super(AccountMove, self).action_post()
# for invoice in self:
# subscription = self.env["sale.subscription"].search(
# [("code", "=", invoice.invoice_date)], limit=1
# )
# if subscription and subscription.is_recurrent:
# invoice.stripe_pay_invoice()
# return res
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove

Suggested change
# def stripe_pay_invoice(self):
# """Paga la factura en Stripe si `is_recurrent` está activado en la suscripción."""
# for invoice in self:
# subscription = invoice.invoice_date and self.env[
# "sale.subscription"
# ].search([("code", "=", invoice.invoice_date)], limit=1)
# if (
# subscription
# and subscription.is_recurrent
# and subscription.stripe_customer
# ):
# provider = self.env["payment.provider"].search(
# [("code", "=", "stripe")],
# )
# stripe.api_key = provider.stripe_secret_key
# token = self.env["payment.token"].search(
# [("provider_id", "=", provider.id)]
# )
# try:
# # Crea un PaymentIntent en Stripe para el monto de la factura
# payment_intent = stripe.PaymentIntent.create(
# # Stripe maneja montos en centavos
# amount=int(invoice.amount_total * 100),
# currency=invoice.currency_id.name.lower(),
# customer=token.provider_ref,
# payment_method=token.stripe_payment_method,
# payment_method_types=["card"],
# # Para pagos automáticos sin intervención del usuario
# off_session=True,
# # Confirmar el PaymentIntent inmediatamente
# confirm=True,
# metadata={"odoo_invoice_id": invoice.id},
# )
# # Confirmar el pago y actualizar el estado de la factura en Odoo
# if payment_intent["status"] == "succeeded":
# invoice.action_post()
# invoice.payment_state = "paid"
# else:
# raise Exception(
# f"Error en el pago de Stripe: {payment_intent['status']}"
# )
# except stripe.StripeError as e:
# raise UserError(f"Stripe error: {e.user_message or str(e)}")
# def action_post(self):
# """Sobreescribe el método `action_post` para procesar el pago automático si `is_recurrent` está activo."""
# res = super(AccountMove, self).action_post()
# for invoice in self:
# subscription = self.env["sale.subscription"].search(
# [("code", "=", invoice.invoice_date)], limit=1
# )
# if subscription and subscription.is_recurrent:
# invoice.stripe_pay_invoice()
# return res


def action_register_payment(self):
mjavint marked this conversation as resolved.
Show resolved Hide resolved
"""Sobreescribe `action_register_payment` para procesar automáticamente el pago con Stripe en suscripciones."""
for invoice in self:
# Buscar la suscripción asociada a la factura, si existe
subscription = self.env["sale.subscription"].search(
[("code", "=", invoice.invoice_origin)], limit=1
)
mjavint marked this conversation as resolved.
Show resolved Hide resolved

# Verificar si la suscripción es recurrente y tiene método de pago en Stripe
if subscription and subscription.is_recurrent:
provider = self.env["payment.provider"].search(
[("code", "=", "stripe")],
)
stripe.api_key = provider.stripe_secret_key
token = self.env["payment.token"].search(
[("provider_id", "=", provider.id)]
)
try:
# Crear el PaymentIntent en Stripe y confirmarlo inmediatamente
payment_intent = stripe.PaymentIntent.create(
# Stripe usa centavos
amount=int(invoice.amount_total * 100),
currency=invoice.currency_id.name.lower(),
customer=token.provider_ref,
payment_method=token.stripe_payment_method,
# Para pagos automáticos sin intervención del usuario
off_session=True,
# Confirmar el PaymentIntent inmediatamente
confirm=True,
metadata={"odoo_invoice_id": invoice.id},
)

# Manejar el resultado del PaymentIntent
if payment_intent["status"] == "succeeded":
# Si el pago es exitoso, registrar el pago en la factura
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 PaymentIntent {payment_intent['id']}",
}
payment = Payment.create(payment_vals)
payment.action_post()
invoice.payment_state = "paid"
elif payment_intent["status"] == "requires_action":
raise UserError(
"El pago requiere autenticación adicional (3D Secure)."
)
else:
raise UserError(
f"Error en el pago de Stripe: {payment_intent['status']}"
)

except stripe.StripeError as e:
raise UserError(f"Error de Stripe: {e.user_message or str(e)}")

else:
# Si no es una suscripción recurrente o no tiene método de pago en Stripe, llama al método original
return super(AccountMove, self).action_register_payment()

@api.model
def cron_process_due_invoices(self):
"""Procesa el pago de facturas vencidas para suscripciones recurrentes."""
# Obtener la fecha de hoy
today = date.today()
mjavint marked this conversation as resolved.
Show resolved Hide resolved

# Buscar facturas de suscripciones recurrentes que vencen hoy y están abiertas
invoices = self.search(
[
("state", "=", "posted"),
("payment_state", "=", "not_paid"),
]
)

for invoice in invoices:
# Buscar la suscripción asociada a la factura
subscription = self.env["sale.subscription"].search(
[("code", "=", invoice.invoice_origin)], limit=1
)

# Verificar si es una suscripción recurrente con Stripe
if subscription and subscription.is_recurrent:
try:
# Intentar registrar el pago en la factura
invoice.action_register_payment()
except Exception as e:
# Capturar excepciones en caso de error en el registro de pago
_logger.error(
f"Error al procesar el pago de la factura {invoice.id}: {str(e)}"
)
mjavint marked this conversation as resolved.
Show resolved Hide resolved
38 changes: 38 additions & 0 deletions recurring_payments_stripe/models/sale_subscription.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
import logging
import stripe

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

_logger = logging.getLogger(__name__)
mjavint marked this conversation as resolved.
Show resolved Hide resolved


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

is_recurrent = fields.Boolean(string="Pago Automático Recurrente")
mjavint marked this conversation as resolved.
Show resolved Hide resolved
# ID del cliente en Stripe
stripe_customer = fields.Char(string="Stripe Customer ID")

def create_stripe_customer(self):
"""Crea o recupera el cliente de Stripe asociado a la suscripción."""
provider = self.env["payment.provider"].search(
[("code", "=", "stripe")],
)
stripe.api_key = provider.stripe_secret_key

if not self.stripe_customer:
customer = stripe.Customer.create(
email=self.env.user.email,
name=self.env.user.name,
metadata={"odoo_subscription": self.id},
)
self.stripe_customer = customer["id"]
return self.stripe_customer

@api.onchange("is_recurrent")
def _onchange_is_recurrent(self):
for record in self:
if record.is_recurrent:
record.create_stripe_customer()
18 changes: 18 additions & 0 deletions recurring_payments_stripe/views/sale_subscription_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>

<!-- View sale.subscription form -->
<record id="sale_subscription_form" model="ir.ui.view">
<field name="name">sale.subscription.form</field>
<field name="model">sale.subscription</field>
<field name="inherit_id" ref="subscription_oca.sale_subscription_form" />
<field name="arch" type="xml">
<field name="recurring_next_date" position="after">
<field name="is_recurrent" widget="boolean_toggle" />
<!-- <field name="stripe_customer" widget="CopyClipboardChar" /> -->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove

</field>

</field>
</record>

</odoo>