Skip to content

Commit

Permalink
[FIX] Fixed payment transaction and design modular (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
mjavint authored Dec 27, 2024
1 parent d359985 commit ea45267
Show file tree
Hide file tree
Showing 15 changed files with 791 additions and 36 deletions.
3 changes: 1 addition & 2 deletions recurring_payment_stripe/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@
"website": "https://github.com/OCA/contract",
"license": "AGPL-3",
"category": "Subscription Management",
"depends": ["subscription_oca", "payment_stripe", "payment"],
"depends": ["subscription_recurring_payment", "payment_stripe"],
"data": [
"views/sale_subscription_views.xml",
"data/ir_cron.xml",
],
"installable": True,
Expand Down
2 changes: 1 addition & 1 deletion recurring_payment_stripe/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from . import sale_subscription
from . import account_move
from . import sale_subscription
65 changes: 57 additions & 8 deletions recurring_payment_stripe/models/account_move.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,27 @@ def action_register_payment(self):
payment on subscriptions.
"""
for invoice in self:
res = super(AccountMove, self).action_register_payment()
# 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.env["payment.token"].search(
[("provider_id", "=", provider.id)]
)
token = self._create_token(subscription)
try:
# Create the PaymentIntent and confirm it immediately
payment_intent = stripe.PaymentIntent.create(
# Stripe usa centavos
# 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,
# Para pagos automáticos sin intervención del usuario
automatic_payment_methods={"enabled": True},
# For automatic payments without user intervention
off_session=True,
# Confirmar el PaymentIntent inmediatamente
# Confirm the PaymentIntent immediately
confirm=True,
metadata={"odoo_invoice_id": str(invoice.id)},
)
Expand All @@ -57,7 +57,7 @@ def action_register_payment(self):
"payment_method_id": self.env.ref(
"account.account_payment_method_manual_in"
).id,
"ref": f"Stripe PaymentIntent {payment_intent['id']}",
"ref": f"Stripe - {payment_intent['id']}",
}
payment = Payment.create(payment_vals)
payment.action_post()
Expand All @@ -75,7 +75,56 @@ def action_register_payment(self):
raise UserError(f"Stripe error: {e}") from e

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

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):
Expand Down
27 changes: 2 additions & 25 deletions recurring_payment_stripe/models/sale_subscription.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,12 @@
import stripe

from odoo import api, fields, models
from odoo import fields, models


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

charge_automatically = fields.Boolean()
stripe_customer = fields.Char("Stripe Customer ID")
charge_automatically = fields.Boolean(default=True)
provider_id = fields.Many2one(
string="Provider",
domain=[("code", "=", "stripe")],
comodel_name="payment.provider",
)

def create_stripe_customer(self):
provider = self.provider_id
if provider:
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": str(self.id)},
)
self.stripe_customer = customer["id"]
return self.stripe_customer

@api.onchange("charge_automatically")
def _onchange_charge_automatically(self):
for record in self:
if record.charge_automatically:
record.create_stripe_customer()
1 change: 1 addition & 0 deletions recurring_payment_stripe/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import test_payment_stripe_recurring
203 changes: 203 additions & 0 deletions recurring_payment_stripe/tests/test_payment_stripe_recurring.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import uuid

import stripe

from odoo import fields
from odoo.tests.common import TransactionCase


class TestPaymentStripeRecurring(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.provider = cls.env["payment.provider"].create(
{
"name": "Stripe",
"code": "stripe",
"stripe_secret_key": "sk_test_4eC39HqLyjWDarjtT1zdp7dc",
}
)
cls.sale_journal = cls.env["account.journal"].search(
[
("type", "=", "sale"),
("company_id", "=", cls.env.ref("base.main_company").id),
]
)[0]
cls.pricelist1 = cls.env["product.pricelist"].create(
{
"name": "pricelist for contract test",
}
)
cls.partner = cls.env["res.partner"].create(
{
"name": "Test Partner",
"email": "[email protected]",
"property_product_pricelist": cls.pricelist1.id,
}
)
cls.tax_10pc_incl = cls.env["account.tax"].create(
{
"name": "10% Tax incl",
"amount_type": "percent",
"amount": 10,
"price_include": True,
}
)
cls.country = cls.env["res.country"].search([], limit=1)
cls.fiscal = cls.env["account.fiscal.position"].create(
{
"name": "Regime National",
"auto_apply": True,
"country_id": cls.country.id,
"vat_required": True,
"sequence": 10,
}
)
cls.product_1 = cls.env.ref("product.product_product_1")
cls.product_1.subscribable = True
cls.product_1.taxes_id = [(6, 0, cls.tax_10pc_incl.ids)]
cls.product_2 = cls.env.ref("product.product_product_2")
cls.product_2.subscribable = True
cls.pricelist2 = cls.env["product.pricelist"].create(
{
"name": "pricelist for contract test 2",
"discount_policy": "with_discount",
}
)
cls.tmpl2 = cls.create_sub_template(
{
"recurring_rule_boundary": "limited",
"recurring_rule_type": "days",
}
)
cls.tag = cls.env["sale.subscription.tag"].create(
{
"name": "Test Tag",
}
)
cls.stage = cls.env["sale.subscription.stage"].create(
{
"name": "Test Sub Stage",
}
)
cls.sub8 = cls.create_sub(
{
"partner_id": cls.partner.id,
"template_id": cls.tmpl2.id,
"pricelist_id": cls.pricelist2.id,
"date_start": fields.Date.today(),
"in_progress": True,
"journal_id": cls.sale_journal.id,
"company_id": 1,
"tag_ids": [(6, 0, [cls.tag.id])],
"stage_id": cls.stage.id,
"fiscal_position_id": cls.fiscal.id,
"charge_automatically": True,
}
)

cls.invoice = cls.env["account.move"].create(
{
"partner_id": cls.partner.id,
"move_type": "out_invoice",
"invoice_line_ids": [
(
0,
0,
{
"name": "Test Product",
"quantity": 1,
"price_unit": 100.0,
},
)
],
"subscription_id": cls.sub8.id,
}
)

@classmethod
def create_sub_template(cls, vals):
code = str(uuid.uuid4().hex)
default_vals = {
"name": "Test Template " + code,
"code": code,
"description": "Some sort of subscription terms",
"product_ids": [(6, 0, [cls.product_1.id, cls.product_2.id])],
}
default_vals.update(vals)
rec = cls.env["sale.subscription.template"].create(default_vals)
return rec

@classmethod
def create_sub(cls, vals):
default_vals = {
"company_id": 1,
"partner_id": cls.partner.id,
"template_id": cls.tmpl2.id,
"tag_ids": [(6, 0, [cls.tag.id])],
"stage_id": cls.stage.id,
"pricelist_id": cls.pricelist1.id,
"fiscal_position_id": cls.fiscal.id,
"charge_automatically": True,
}
default_vals.update(vals)
rec = cls.env["sale.subscription"].create(default_vals)
return rec

def test_action_register_payment(self):
# Set Stripe API key for testing
stripe.api_key = self.provider.stripe_secret_key

# Create a new Stripe customer
customer = stripe.Customer.create(
email=self.partner.email,
name=self.partner.name,
)

# Create a new payment method for the customer
payment_method = stripe.PaymentMethod.create(
type="card",
card={
"number": "4242424242424242",
"exp_month": 1,
"exp_year": 2025,
"cvc": "123",
},
)

# Attach the payment method to the customer
stripe.PaymentMethod.attach(
payment_method.id,
customer=customer.id,
)

token = self.invoice._create_token(subscription=self.sub8)
self.assertTrue(token, "Payment token was not created")

method_line = self.env["account.payment.method.line"].search(
[("name", "=", self.provider.name)], limit=1
)
self.assertTrue(method_line, "Payment method line was not found")
method = method_line.payment_method_id
self.assertTrue(method, "Payment method was not found")

# Check if the PaymentIntent was created
payment_intent = stripe.PaymentIntent.create(
amount=int(self.invoice.amount_total * 100),
currency=self.invoice.currency_id.name.lower(),
customer=token.provider_ref,
payment_method=token.stripe_payment_method,
off_session=True,
confirm=True,
metadata={"odoo_invoice_id": str(self.invoice.name)},
)
self.assertEqual(
payment_intent["status"],
"succeeded",
"PaymentIntent was not successful",
)
self.invoice.action_register_payment()
self.assertTrue(
self.invoice.payment_state == "paid",
"Invoice was not paid",
)
6 changes: 6 additions & 0 deletions setup/subscription_recurring_payment/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import setuptools

setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)
Loading

0 comments on commit ea45267

Please sign in to comment.