diff --git a/controllers/main.py b/controllers/main.py
index 4f60476..86e9067 100644
--- a/controllers/main.py
+++ b/controllers/main.py
@@ -3,61 +3,48 @@
#
# TropiPay.
# soporte@tropipay.com
-#
#
#############################################################################
import logging
-import pprint
import json
-import requests
from odoo import http
from odoo.http import request
-import ast
_logger = logging.getLogger(__name__)
+
class PaymentTppController(http.Controller):
_return_url = '/payment/tpp/_return_url'
- _information_url = '/payment/tpp/_information_url'
+ _information_url = '/payment/tpp/_information_url'
@http.route(_return_url, type='http', auth='public',
methods=['GET'])
def tpp__checkout(self, **data):
- #_logger.info("Recibiendo de Tropipay los datos de retorno:\n%s",
- # pprint.pformat(data))
- # tx_sudo = request.env[
- # 'payment.transaction'].sudo()._get_tx_from_notification_data(
- # 'tpp', data)
- #tx_sudo._handle_notification_data('tpp', data)
return request.redirect('/payment/status')
-
-
+
@http.route(_information_url, type='json', auth='public',
- methods=['GET', 'POST'],csrf=False)
+ methods=['GET', 'POST'], csrf=False)
def tpp__checkout2(self, **data):
- #_logger.info("Recibiendo de Tropipay EN EL URL INFORMATION: los datos de retorno:\n%s", pprint.pformat(data))
- _logger.info("Cuerpo de la solicitud HTTP: %s", request.httprequest.data)
+ _logger.debug("Cuerpo de la solicitud HTTP: %s", request.httprequest.data)
data_dict = json.loads(request.httprequest.data) # convierte la cadena JSON a un diccionario
- status = data_dict['status'] # 'OK'
- #if status === OK
+ status = data_dict['status'] # 'OK'
if status == 'OK':
- data_dict = data_dict['data'] # {'id': 383663, 'reference': 'S00047'}
- data_id = data_dict['id'] # 383663
- data_reference = data_dict['reference'] # 'S00047'
- _logger.info("status, id, reference: %s %s %s",
+ data_dict = data_dict['data'] # {'id': 383663, 'reference': 'S00047'}
+ data_id = data_dict['id'] # 383663
+ data_reference = data_dict['reference'] # 'S00047'
+ _logger.debug(
+ "status, id, reference: %s %s %s",
status,
data_id,
data_reference)
tx_sudo = request.env['payment.transaction'].sudo()._get_tx_from_notification_data('tpp', request.httprequest.data)
tx_sudo._handle_notification_data('tpp', request.httprequest.data)
- #return request.redirect('/payment/status')
- return {'status': 'OK'}, 200
+ # return request.redirect('/payment/status')
+ return {'status': 'OK'}, 200
@http.route('/payment/tpp/failed', type='http', auth='user',
website=True, )
def payment_failed(self, redirect=None):
- # return request.render("tpp_payment_gateway.tpp_payment_gateway_failed_form")
- #return request.redirect('/payment/status')error-al-pagar
- return request.redirect('/error-al-pagar')
+ return request.redirect('/error-al-pagar')
diff --git a/data/payment_provider_data.xml b/data/payment_provider_data.xml
index b606b4f..32f32be 100644
--- a/data/payment_provider_data.xml
+++ b/data/payment_provider_data.xml
@@ -6,6 +6,10 @@
file="tpp_payment_gateway/static/description/tropipaylogo.png"/>
+
+ Serás redirigido a la pasarela de Tropipay después de finalizar tu compra.
]]>
+
\ No newline at end of file
diff --git a/models/__init__.py b/models/__init__.py
index 3625456..2d11c14 100644
--- a/models/__init__.py
+++ b/models/__init__.py
@@ -23,5 +23,4 @@
from . import payment_provider
from . import payment_transaction
-
-
+from . import res_partner
diff --git a/models/payment_provider.py b/models/payment_provider.py
index 7dfdbc6..26702a3 100644
--- a/models/payment_provider.py
+++ b/models/payment_provider.py
@@ -27,18 +27,35 @@ def _get_payment_method_information(self):
res['tpp'] = {'mode': 'unique', 'domain': [('type', '=', 'bank')]}
return res
+ @api.model
+ def _get_compatible_providers(self, *args, currency_id=None, **kwargs):
+ """Decide whether to show Tropipay as an option or not"""
+ providers = super()._get_compatible_providers(
+ *args, currency_id=currency_id, **kwargs
+ )
+ """
+ We want to show Tropipay only for companies whose main currency is "EUR".
+ In case we select a different currency during an order,
+ we just convert the amount life to "EUR" on transaction creation.
+ """
+ currency = self.main_currency_id
+ if currency and currency.name != "EUR":
+ providers = providers.filtered(lambda x: x.code != 'tpp')
+ return providers
+
def _tpp_get_api_url(self):
""" Return the API URL according to the provider state.
Note: self.ensure_one()
:return: The API URL
:rtype: str
"""
- self.ensure_one()
-
- if self.state == 'enabled':
- return 'https://www.tropipay.com/api/v2/access/token'
- else:
- return 'https://tropipay-dev.herokuapp.com/api/v2/access/token'
+ website_id = self.env['website'].get_current_website()
+ for sel in self.filtered(lambda x: x.website_id == website_id):
+ sel.ensure_one()
+ if sel.state == 'enabled':
+ return 'https://www.tropipay.com/api/v2/access/token'
+ else:
+ return 'https://tropipay-dev.herokuapp.com/api/v2/access/token'
def _tpp_get_endpoint_url(self):
""" Return the ENDPOINT URL according to the provider state.
@@ -46,9 +63,12 @@ def _tpp_get_endpoint_url(self):
:return: The API URL
:rtype: str
"""
- self.ensure_one()
+ website_id = self.env['website'].get_current_website()
+ for sel in self.filtered(lambda x: x.website_id == website_id):
+ sel.ensure_one()
+
+ if sel.state == 'enabled':
+ return 'https://www.tropipay.com/api/v2/paymentcards'
+ else:
+ return 'https://tropipay-dev.herokuapp.com/api/v2/paymentcards'
- if self.state == 'enabled':
- return 'https://www.tropipay.com/api/v2/paymentcards'
- else:
- return 'https://tropipay-dev.herokuapp.com/api/v2/paymentcards'
diff --git a/models/payment_transaction.py b/models/payment_transaction.py
index eef1456..abeab03 100644
--- a/models/payment_transaction.py
+++ b/models/payment_transaction.py
@@ -9,21 +9,15 @@
import hashlib
import logging
-import pprint
-
-from werkzeug import urls
-
-from odoo import _, api, fields, models
+from odoo import _, models, fields
from odoo.exceptions import ValidationError
+from odoo.tools import float_round
-from odoo.addons.payment import utils as payment_utils
-from odoo.http import request
# Import required libraries (make sure it is installed!)
import requests
import json
-import time
-import sys
from datetime import datetime
+import random
_logger = logging.getLogger(__name__)
@@ -40,15 +34,19 @@ def _get_specific_rendering_values(self, processing_values):
def execute_payment(self):
"""Fetching data and Executing Payment"""
endpoint_url = self.env['payment.provider'].search([('code', '=', 'tpp')])._tpp_get_endpoint_url()
- _logger.info("*****ENDPOINT ********************")
- _logger.info(endpoint_url)
- odoo_base_url = self.env['ir.config_parameter'].get_param('web.base.url')
+ _logger.debug("*****ENDPOINT ********************")
+ _logger.debug(endpoint_url)
+ odoo_base_url = \
+ self.env['website'].browse(self.env.context.get('website_id')).domain or \
+ self.env['ir.config_parameter'].get_param('web.base.url')
+ # TODO: sale_order is never used (!) --> delete ?
sale_order = self.env['payment.transaction'].search(
[('id', '=', self.id)]).sale_order_ids
order_line = self.env['payment.transaction'].search(
[('id', '=', self.id)]).sale_order_ids.order_line
+ # TODO: invoice_items is never used (!) --> delete ?
invoice_items = [
{
'ItemName': rec.product_id.name,
@@ -65,87 +63,89 @@ def execute_payment(self):
'Authorization': f'Bearer {token}'
}
amount = self.amount
- ahora=datetime.now()
+ ahora = datetime.now()
fecha = ahora.strftime("%Y-%m-%d")
- _logger.info("Mostrando country:\n%s",
- self.partner_id.country_id.code)
- _logger.info(f' La fecha que viene{fecha}')
- # Extraer first_name y last_name de partner_name
- name_parts = self.partner_name.split()
- first_name = name_parts[0] if name_parts else ''
- last_name = ' '.join(name_parts[1:]) if len(name_parts) > 1 else '.'
-
+ _logger.debug(
+ "Mostrando country:\n%s",
+ self.partner_id.country_id.code)
+ _logger.debug(f' La fecha que viene{fecha}')
payload = {
"reference": self.reference,
"concept": "Compra en la web",
"favorite": False,
"description": "Compra de productos en la tienda en linea",
- "amount": round(amount * 100, 2), # float(f"{self.amount}00"), #str(self.amount)+"00",
- "currency": self.currency_id.name,
+ # Amount in EUR, in cents.
+ "amount": int(float_round(self._convert_to_eur(amount, self.currency_id.name, "EUR"), precision_digits=2) * 100),
+ "currency": "EUR",
"singleUse": True,
"reasonId": 34,
"expirationDays": 1,
"lang": "es",
"urlSuccess": f"{odoo_base_url}/payment/tpp/_return_url",
"urlFailed": f"{odoo_base_url}/payment/tpp/failed",
- #urlNotification": "https://webhook.site/bc45e9cd-5bf0-432f-994e-4f86e762788f",
- #"urlNotification": f"{odoo_base_url}/payment/tpp/call-back",
"urlNotification": f"{odoo_base_url}/payment/tpp/_information_url",
"serviceDate": fecha,
"directPayment": True,
"client": {
- "name": first_name,
- "lastName": last_name,
+ "name": self.partner_id.get_partner_name(),
+ "lastName": self.partner_id.get_partner_surname(),
"address": self.partner_address,
- "phone": self.partner_phone,
+ "phone": self.partner_phone or self.partner_id.mobile,
"email": self.partner_email,
"countryIso": self.partner_id.country_id.code,
"termsAndConditions": "true",
- "postCode": self.partner_id.zip,
- "city": self.partner_id.city
+ "city": self.partner_id.city_id.name or self.partner_id.city,
+ "postCode": int(self.partner_id.zip_id.name or self.partner_id.zip),
}
}
- _logger.info(endpoint_url)
- _logger.info(payload)
+ self.partner_id.check_client_details_tropipay()
+ _logger.debug(endpoint_url)
+ _logger.debug(payload)
response = requests.post(endpoint_url, json=payload, headers=headers)
- _logger.info("La URL corta obtenid es")
- _logger.info(response)
- _logger.info(response.json())
+ _logger.debug("Tropipay response:" + str(response.text))
+ # If we receive an error we block that payment method but we can choose another one
+ if not str(response.status_code).startswith("2"):
+ _logger.error("Tropipay error:" + str(response.text))
+ raise ValidationError("")
+
+ _logger.debug("La URL corta obtenida es")
+ _logger.debug(response)
+ _logger.debug(response.json())
rendering_values = {
'api_url': response.json()["shortUrl"],
'payment_url': response.json()["paymentUrl"],
}
return rendering_values
-
def _get_tx_from_notification_data(self, provider_code, notification_data):
"""Getting payment status from tropipay"""
- #notification_data_str = notification_data.decode('utf-8')
# Deserialize the JSON string
tx = super()._get_tx_from_notification_data(provider_code, notification_data)
if provider_code != 'tpp' or len(tx) == 1:
return tx
notification_data_dict = json.loads(notification_data)
- _logger.info("asdfasf: %s", notification_data_dict)
+ _logger.debug("asdfasf: %s", notification_data_dict)
# Access the payment_status field
payment_status = notification_data_dict['data']['state']
- _logger.info("payment_status: %s", payment_status)
+ _logger.debug("payment_status: %s", payment_status)
# payment_status = notification_data['state'] #5 cuando el pago se realizo correctamente
- _logger.info("mi clientid: %s", self.env['payment.provider'].search([('code', '=', 'tpp')]).client_id)
- clientid = self.env['payment.provider'].search([('code', '=', 'tpp')]).client_id
- clientsecret = self.env['payment.provider'].search([('code', '=', 'tpp')]).client_secret
+ website_id = self.env['website'].get_current_website()
+ _logger.debug("mi clientid: %s", self.env['payment.provider'].search([('code', '=', 'tpp'), ('website_id', '=', website_id.id)]).client_id)
+ clientid = self.env['payment.provider'].search([('code', '=', 'tpp'), ('website_id', '=', website_id.id)]).client_id
+ clientsecret = self.env['payment.provider'].search([('code', '=', 'tpp'), ('website_id', '=', website_id.id)]).client_secret
bankOrderCode = notification_data_dict['data']['bankOrderCode']
originalCurrencyAmount = notification_data_dict['data']['originalCurrencyAmount']
# Concatenar los valores
- data = "{}{}{}{}".format(bankOrderCode,clientid,clientsecret,originalCurrencyAmount)
-
+ data = "{}{}{}{}".format(bankOrderCode, clientid, clientsecret, originalCurrencyAmount)
+ _logger.info("data: {}".format(data))
# Calcular la firma utilizando SHA256
signature = hashlib.sha256(data.encode()).hexdigest()
_logger.info("misignature: {}".format(signature))
- _logger.info("Firma remota: {}, Firma local: {}".format(notification_data_dict['data']['signaturev2'],signature))
+ _logger.info("Firma remota: {}, Firma local: {}".format(notification_data_dict['data']['signaturev2'], signature))
reference = notification_data_dict['data']['reference']
if signature != notification_data_dict['data']['signaturev2']:
+ _logger.info("TPP: Signature does not match")
raise ValidationError(
"tpp: " + _(
"Invalid Signature %s.",
@@ -163,8 +163,7 @@ def _get_tx_from_notification_data(self, provider_code, notification_data):
reference)
)
return tx
-
-
+
def _process_notification_data(self, notification_data):
super()._process_notification_data(notification_data)
if self.provider_code != 'tpp':
@@ -180,11 +179,22 @@ def _handle_notification_data(self, provider_code, notification_data):
tx._execute_callback()
return tx
+ def _convert_to_eur(self, amount, currency_orig, currency_dest="EUR"):
+ currency_orig = self.env['res.currency'].search([('name', '=', currency_orig)], limit=1)
+ currency_dest = self.env['res.currency'].search([('name', '=', currency_dest)], limit=1)
+ # Minimize probability to have outdated exchange rate, but don't call with every transaction.
+ if random.randint(1, 100) == 1:
+ self.env['res.config.settings'].search([], limit=1).update_currency_rates_manually()
+ res = currency_orig._convert(
+ amount, currency_dest, self.company_id, fields.Date.today()
+ )
+ return res
def login(self):
- base_api_url = self.env['payment.provider'].search([('code', '=', 'tpp')])._tpp_get_api_url()
- client_id = self.env['payment.provider'].search([('code', '=', 'tpp')]).client_id
- client_secret = self.env['payment.provider'].search([('code', '=', 'tpp')]).client_secret
+ website_id = self.env['website'].get_current_website()
+ base_api_url = self.env['payment.provider'].search([('code', '=', 'tpp'), ('website_id', '=', website_id.id)])._tpp_get_api_url()
+ client_id = self.env['payment.provider'].search([('code', '=', 'tpp'),('website_id', '=', website_id.id)]).client_id
+ client_secret = self.env['payment.provider'].search([('code', '=', 'tpp'),('website_id', '=', website_id.id)]).client_secret
scope = "ALLOW_EXTERNAL_CHARGE"
grandtype = "client_credentials"
response = requests.post(base_api_url, json={
@@ -194,10 +204,10 @@ def login(self):
"scope": scope
})
data = response.json()
- _logger.info("******LOS DATOS PARA VER SI SE AUTENTICO EN TROPIPAY*****")
- _logger.info(data)
- _logger.info(base_api_url)
- _logger.info(client_id)
- _logger.info(client_secret)
- _logger.info("******FIN DE LOS DATOS *****")
+ _logger.debug("******LOS DATOS PARA VER SI SE AUTENTICO EN TROPIPAY*****")
+ _logger.debug(data)
+ _logger.debug(base_api_url)
+ _logger.debug(client_id)
+ _logger.debug(client_secret)
+ _logger.debug("******FIN DE LOS DATOS *****")
return data
diff --git a/models/res_partner.py b/models/res_partner.py
new file mode 100644
index 0000000..56a8620
--- /dev/null
+++ b/models/res_partner.py
@@ -0,0 +1,64 @@
+# Copyright 2024 Alejandra García
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+from odoo import models, api, _
+from odoo.exceptions import ValidationError
+
+
+class ResCompany(models.Model):
+ _inherit = 'res.partner'
+
+ def split_display_name(self):
+ for record in self:
+ if record.display_name:
+ split_name = record.display_name.split()
+ return split_name
+
+ def get_partner_name(self):
+ split_name = self.split_display_name()
+ name = split_name[0] if len(split_name) > 1 else ''
+ return name
+
+ def get_partner_surname(self):
+ split_name = self.split_display_name()
+ surname = ' '.join(split_name[1:]) if len(split_name) > 1 else ''
+ return surname
+
+ @api.constrains('display_name', 'street', 'street2', 'phone', 'mobile', 'email', 'website_id', 'country_id', 'zip', 'city_id')
+ def _check_client_details_tropipay(self):
+ """Method to check necessary details from the client to make a payment through Tropipay when creating an user through backend."""
+ for record in self:
+ if not record.website_id:
+ continue
+
+ tropipay_payment_provider = self.env['payment.provider'].search([
+ ('code', '=', 'tpp'),
+ ('website_id', '=', record.website_id.id)
+ ], limit=1)
+
+ if tropipay_payment_provider.state in ['test', 'enabled']:
+ record.check_client_details_tropipay()
+
+ def check_client_details_tropipay(self):
+ """Method to check necessary details from the client to make a payment through Tropipay from website when tropipay is selected."""
+ for record in self:
+ missing_fields = []
+ field_checks = {
+ _("Name"): record.get_partner_name(),
+ _("Surname"): record.get_partner_surname(),
+ _("Address"): record.street or record.street2,
+ _("Phone"): record.phone or record.mobile,
+ _("Email"): record.email,
+ _("Country"): record.country_id.code,
+ _("Postcode"): record.zip,
+ _("City"): record.city_id.name or record.city,
+ }
+
+ missing_fields = [field for field, value in field_checks.items() if not value]
+
+ if missing_fields:
+ missing_fields_str = ', '.join(missing_fields)
+ if len(missing_fields) == 1:
+ error_message = _(f"The field '{missing_fields[0]}' is required.")
+ else:
+ error_message = _(f"The fields '{missing_fields_str}' are required.")
+ raise ValidationError(error_message)
diff --git a/views/payment_template.xml b/views/payment_template.xml
index 8ad443e..4d26d30 100644
--- a/views/payment_template.xml
+++ b/views/payment_template.xml
@@ -12,7 +12,7 @@
attrs="{'required': [('code', '=', 'tpp'), ('state', '!=', 'disabled')]}"/>
+ attrs="{'required': [('code', '=', 'tpp'), ('state', '!=', 'disabled')]}"/>