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')]}"/>