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

[14.0] [BACKPORT] shopinvader_api_payment #15

Draft
wants to merge 36 commits into
base: 14.0-backport-fast-api
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
c111383
[ADD] shopinvader_api_payment first addons
marielejeune Dec 19, 2023
f946f05
[ADD] shopinvader_api_payment_provider_stripe
marielejeune Jan 9, 2024
5b5e77e
[IMP] parse redirect urls to add query params
marielejeune Jan 11, 2024
9ce522f
[IMP] Rename /payment/providers to /payment/methods
marielejeune Jan 11, 2024
c70aa40
[IMP] TransactionCreate schema specifications
marielejeune Jan 11, 2024
2722b4f
[IMP] Create opaque string 'payable' containing all the payment info
marielejeune Jan 11, 2024
1ffb65e
[IMP] shopinvader_api_payment_cart: confirm cart when paid
marielejeune Jan 12, 2024
cc1f8ec
[IMP] shopinvader_api_payment_cart: /payable route is authenticated
marielejeune Jan 12, 2024
3de5de4
[IMP] shopinvader_api_payment: add amount_formatted into payable info
marielejeune Jan 17, 2024
c5a9dfd
[IMP] shopinvader_api_payment_cart: add /current/payable route
marielejeune Jan 17, 2024
5c2e28a
Add methods to sign/encode and verify/decode a Payable
sbidoul Jan 20, 2024
b24d128
shopinvader_api_payment: don't use access token anymore
sbidoul Jan 20, 2024
c66dc77
shopinvader_api_payment: remove amount_formatted from payable
sbidoul Jan 20, 2024
bcf04f5
[FIX] shopinvader_api_payment: decoding the payable obj to get paymen…
marielejeune Jan 22, 2024
f29e930
[FIX] shopinvader_api_payment: missing amount_formatted in PaymentDat…
marielejeune Jan 22, 2024
42033de
[IMP] Shopinvader Payment: specify return models
marielejeune Jan 22, 2024
22a6fc1
[IMP] Shopinvader payment: routes docstrings
marielejeune Jan 22, 2024
a68b672
[FIX] shopinvader_api_payment(_cart): payable decoding
marielejeune Feb 12, 2024
37bc30f
[ADD] shopinvader_api_payment_provider_custom
marielejeune Feb 29, 2024
fd3cbe3
[IMP] shopinvader_api_payment_provider_sips: call super() instead of …
marielejeune Apr 8, 2024
80186f5
[MIG] shopinvader_api_payment, shopinvader_api_payment_cart, shopinva…
paradoxxxzero Apr 10, 2024
9f748dc
[ADD] shopinvader_api_payment_provider_systempay, shopinvader_api_pay…
paradoxxxzero Apr 10, 2024
0a5a5a0
[IMP] shopinvader_api_payment: Use a frontend redirect field in trans…
paradoxxxzero Apr 11, 2024
307dd77
[FIX] shopinvader_api_payment_provider_systempay, shopinvader_api_pay…
paradoxxxzero Apr 11, 2024
ef0688c
shopinvader_api_payment_provider_monetico: fix return url and fix web…
sebastienbeau Jul 31, 2024
c8db9fc
shopinvader_api_payment_provider_monetico: Fix reading data
sebastienbeau Jul 31, 2024
3b3edc5
[ADD] Shopinvader api payment paybox module
Sep 5, 2024
180a96b
[ADD] Shopinvader api payment paybox module
Sep 5, 2024
76ffb21
add missing files
bguillot Sep 5, 2024
f5cca67
[FIX] webhook and return with get protocole
Sep 9, 2024
6f1828b
small fixes to make it work
bguillot Sep 10, 2024
1d07b94
add method for router
bguillot Sep 18, 2024
7db6a78
[BACKPORT] shopinvader_api_payment_provider_custom
bguillot Sep 19, 2024
614920e
Merge pull request #17 from akretion/14.0-ADD-Shopinvader-api-payment…
paradoxxxzero Oct 11, 2024
705a258
[FIX] backport of custom (transfer) payment, confirm cart if transcat…
bguillot Oct 18, 2024
2389ad0
Merge pull request #18 from akretion/14.0-add-shopinvader_api_payment…
paradoxxxzero Oct 18, 2024
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 requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ extendable_pydantic>=1.2.0
fastapi
openupgradelib
pydantic>=2.0.0
pyjwt
python-slugify
slugify
unidecode
6 changes: 6 additions & 0 deletions setup/shopinvader_api_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,
)
6 changes: 6 additions & 0 deletions setup/shopinvader_api_payment_cart/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,
)
6 changes: 6 additions & 0 deletions setup/shopinvader_api_payment_provider_custom/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,
)
6 changes: 6 additions & 0 deletions setup/shopinvader_api_payment_provider_monetico/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,
)
6 changes: 6 additions & 0 deletions setup/shopinvader_api_payment_provider_paybox/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,
)
6 changes: 6 additions & 0 deletions setup/shopinvader_api_payment_provider_sips/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,
)
6 changes: 6 additions & 0 deletions setup/shopinvader_api_payment_provider_stripe/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,
)
6 changes: 6 additions & 0 deletions setup/shopinvader_api_payment_provider_systempay/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,
)
Empty file.
2 changes: 2 additions & 0 deletions shopinvader_api_payment/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import models
from . import routers
28 changes: 28 additions & 0 deletions shopinvader_api_payment/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Copyright 2024 ACSONE SA/NV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

{
"name": "Shopinvader Api Payment",
"summary": """
Shopinvader services to be able to pay (invoices, carts,...)""",
"version": "14.0.1.0.0",
"license": "AGPL-3",
"author": "ACSONE SA/NV",
"website": "https://github.com/shopinvader/odoo-shopinvader",
"depends": [
"fastapi",
"payment",
"payment_sips",
"pydantic",
"extendable",
"extendable_fastapi",
],
"external_dependencies": {
"python": [
"fastapi",
"pydantic>=2.0.0",
"extendable-pydantic>=1.2.0",
"pyjwt",
]
},
}
1 change: 1 addition & 0 deletions shopinvader_api_payment/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import payment_transaction
10 changes: 10 additions & 0 deletions shopinvader_api_payment/models/payment_transaction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from odoo import fields, models


class PaymentTransaction(models.Model):
_inherit = "payment.transaction"

shopinvader_frontend_redirect_url = fields.Char(
string="Shopinvader Frontend Redirect URL",
help="URL where the frontend should be redirected after the payment processing",
)
2 changes: 2 additions & 0 deletions shopinvader_api_payment/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
* Marie Lejeune <[email protected]>
* Stéphane Bidoul <[email protected]>
1 change: 1 addition & 0 deletions shopinvader_api_payment/routers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .payment import payment_router
244 changes: 244 additions & 0 deletions shopinvader_api_payment/routers/payment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
# Copyright Odoo SA (https://odoo.com)
# Copyright 2024 ACSONE SA (https://acsone.eu).
# @author Stéphane Bidoul <[email protected]>
# License LGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

import logging
from typing import Annotated, Any
from urllib.parse import urljoin

from fastapi import APIRouter, Depends, HTTPException, Request

from odoo import api, models
from odoo.tools.misc import format_amount

from odoo.addons.fastapi.dependencies import odoo_env
from odoo.addons.payment.models.payment_acquirer import (
PaymentAcquirer as PaymentProvider,
PaymentTransaction,
)

from ..schemas import (
PaymentDataWithMethods,
PaymentProvider as PaymentProviderSchema,
TransactionCreate,
TransactionProcessingValues,
)
from .utils import Payable

_logger = logging.getLogger(__name__)

payment_router = APIRouter(tags=["payment"])


@payment_router.get("/payment/methods")
def pay(
payable: str,
odoo_env: Annotated[api.Environment, Depends(odoo_env)],
) -> PaymentDataWithMethods:
"""Available payment providers for the given encoded payment data.

This route is public, so it is possible to pay anonymously provided that the
parameters are obtained securely by another mean. An authenticated user can
obtain the parameters with corresponding routes on the related payable
objects (/cart/current/payable for e.g.).
"""
try:
payable_obj = Payable.decode(odoo_env, payable)
except Exception as e:
_logger.info("Could not decode payable")
raise HTTPException(403) from e
# This method is similar to Odoo's PaymentPortal.payment_pay
providers_sudo = (
odoo_env["payment.acquirer"]
.sudo()
._get_available_payment_input(
company=odoo_env["res.company"].browse(payable_obj.company_id),
partner=odoo_env["res.partner"].browse(payable_obj.partner_id),
)
)["acquirers"]
return PaymentDataWithMethods(
payable=payable,
payable_reference=payable_obj.payable_reference,
amount=payable_obj.amount,
currency_code=odoo_env["res.currency"].browse(payable_obj.currency_id).name,
amount_formatted=format_amount(
odoo_env,
payable_obj.amount,
odoo_env["sale.order"].sudo().browse(payable_obj.payable_id).currency_id,
),
providers=[
PaymentProviderSchema.from_payment_provider(provider)
for provider in providers_sudo
],
)


@payment_router.post("/payment/transactions")
def transaction(
data: TransactionCreate,
request: Request,
odoo_env: Annotated[api.Environment, Depends(odoo_env)],
) -> TransactionProcessingValues:
"""Create a payment transaction.

Input is data obtained from /payment/providers, with the provider selected by the
user. This route is public, so it is possible to pay anonymously.

This route will automatically redirect to the return route linked to
the specified provider. The user will finally land on data.frontend_redirect_url
"""
try:
payable_obj = Payable.decode(odoo_env, data.payable)
except Exception as e:
_logger.info("Could not decode payable")
raise HTTPException(403) from e
# similar to Odoo's /payment/transaction route
if data.flow == "redirect":
providers_sudo = (
odoo_env["payment.acquirer"]
.sudo()
._get_available_payment_input(
company=odoo_env["res.company"].browse(payable_obj.company_id),
partner=odoo_env["res.partner"].browse(payable_obj.partner_id),
)
)["acquirers"]
if not data.provider_id or data.provider_id not in providers_sudo.ids:
_logger.info(
"Invalid provider %s for partner %s",
data.provider_id,
payable_obj.partner_id,
)
raise HTTPException(403)
provider_sudo = odoo_env["payment.acquirer"].sudo().browse(data.provider_id)

# Create the transaction
tx_sudo = odoo_env[
"shopinvader_api_payment.payment_router.helper"
]._create_transaction(data, provider_sudo, request, odoo_env)
tx_sudo._log_payment_transaction_sent()

transaction_processing_values = odoo_env[
"shopinvader_api_payment.payment_router.helper"
]._get_tx_processing_values(
tx_sudo,
payable=data.payable,
frontend_redirect_url=data.frontend_redirect_url,
)
return transaction_processing_values
else:
raise NotImplementedError("Only redirect flow is supported")


class ShopinvaderApiPaymentRouterHelper(models.AbstractModel):
_name = "shopinvader_api_payment.payment_router.helper"
_description = "ShopInvader API Payment Router Helper"

def _get_additional_transaction_create_values(
self,
data: TransactionCreate,
odoo_env: Annotated[api.Environment, Depends(odoo_env)],
) -> dict:
# Intended to be extended for invoices, carts...
additional_transaction_create_values = {}
return additional_transaction_create_values

def _get_tx_create_values(
self,
data: TransactionCreate,
provider_sudo: PaymentProvider,
odoo_env: Annotated[api.Environment, Depends(odoo_env)],
) -> dict:
try:
payable_obj = Payable.decode(odoo_env, data.payable)
except Exception as e:
_logger.info("Could not decode payable")
raise HTTPException(403) from e
additional_transaction_create_values = (
self._get_additional_transaction_create_values(data, odoo_env)
)

is_validation = False # future
# compute transaction reference from payable reference
tx_reference = (
odoo_env["payment.transaction"]
.sudo()
._compute_reference(
# provider_code=provider_sudo.provider, # Not in v14.0
prefix=payable_obj.payable_reference,
values={
"acquirer_id": provider_sudo.id,
"partner_id": payable_obj.partner_id,
},
# TODO are custom_create_values and kwargs really needed
# **(custom_create_values or {}),
# **kwargs
)
)

return {
"acquirer_id": data.provider_id,
"reference": tx_reference,
"amount": payable_obj.amount,
"currency_id": payable_obj.currency_id,
"partner_id": payable_obj.partner_id,
# 'token_id': token_id,
# "operation": f"online_{data.flow}" if not is_validation else "validation",
# "tokenize": False,
"type": "form" if not is_validation else "validation",
"shopinvader_frontend_redirect_url": data.frontend_redirect_url,
**additional_transaction_create_values,
}

def _create_transaction(
self,
data: TransactionCreate,
provider_sudo: PaymentProvider,
request: Request,
odoo_env: Annotated[api.Environment, Depends(odoo_env)],
) -> dict:
transaction_values = odoo_env[
"shopinvader_api_payment.payment_router.helper"
]._get_tx_create_values(data, provider_sudo, odoo_env)
tx_sudo = (
odoo_env["payment.transaction"]
.sudo()
.with_context(
shopinvader_api_payment=True,
shopinvader_api_payment_frontend_redirect_url=data.frontend_redirect_url,
shopinvader_api_payment_base_url=urljoin(
str(request.url), "providers/"
),
)
.create(transaction_values)
)
return tx_sudo

def _get_tx_processing_values(
self, tx_sudo: PaymentTransaction, **kwargs: Any
) -> TransactionProcessingValues:
"""
Extract the creation of the response to allow to extend it.
"""
# There's no _get_processing_values in v14.0, so we fill it manually
redirect_form_html = tx_sudo.acquirer_id.render(
reference=tx_sudo.reference,
amount=tx_sudo.amount,
currency_id=tx_sudo.currency_id.id,
partner_id=tx_sudo.partner_id.id,
values={
"partner_id": tx_sudo.partner_id.id,
"type": "form",
},
)

return TransactionProcessingValues(
flow="redirect",
provider_id=tx_sudo.acquirer_id.id,
provider_code=tx_sudo.acquirer_id.provider,
reference=tx_sudo.reference,
amount=tx_sudo.amount,
currency_id=tx_sudo.currency_id.id,
partner_id=tx_sudo.partner_id.id,
redirect_form_html=redirect_form_html,
)
Loading