From e7220d52116d517b31dfa62a90c495a49d6957b9 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 22 Apr 2021 22:28:41 +0800 Subject: [PATCH 1/6] [ADD] affipay: initial commit (untested) --- payment_affipay/__init__.py | 9 + payment_affipay/__manifest__.py | 32 ++ payment_affipay/controllers/__init__.py | 3 + payment_affipay/controllers/main.py | 37 ++ .../data/payment_acquirer_data.xml | 27 ++ payment_affipay/models/__init__.py | 3 + payment_affipay/models/payment.py | 393 ++++++++++++++++++ payment_affipay/static/description/icon.png | Bin 0 -> 10516 bytes .../static/src/img/affipay-logo.png | Bin 0 -> 10516 bytes .../views/payment_acquirer_views.xml | 24 ++ .../views/payment_affipay_templates.xml | 28 ++ 11 files changed, 556 insertions(+) create mode 100644 payment_affipay/__init__.py create mode 100644 payment_affipay/__manifest__.py create mode 100644 payment_affipay/controllers/__init__.py create mode 100644 payment_affipay/controllers/main.py create mode 100644 payment_affipay/data/payment_acquirer_data.xml create mode 100644 payment_affipay/models/__init__.py create mode 100644 payment_affipay/models/payment.py create mode 100755 payment_affipay/static/description/icon.png create mode 100755 payment_affipay/static/src/img/affipay-logo.png create mode 100644 payment_affipay/views/payment_acquirer_views.xml create mode 100644 payment_affipay/views/payment_affipay_templates.xml diff --git a/payment_affipay/__init__.py b/payment_affipay/__init__.py new file mode 100644 index 0000000..0a22ac1 --- /dev/null +++ b/payment_affipay/__init__.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- + +from . import models +from . import controllers +from odoo.addons.payment.models.payment_acquirer import create_missing_journal_for_acquirers +from odoo.addons.payment import reset_payment_provider + +def uninstall_hook(cr, registry): + reset_payment_provider(cr, registry, 'affipay') \ No newline at end of file diff --git a/payment_affipay/__manifest__.py b/payment_affipay/__manifest__.py new file mode 100644 index 0000000..20a65c0 --- /dev/null +++ b/payment_affipay/__manifest__.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Part of Ktrine. See LICENSE file for full copyright and licensing details. + +{ + 'name': 'Affipay Payment Acquirer Integration', + 'version': '12.0.0.0', + 'depends': [ + 'payment' + ], + 'external_dependencies': {}, + 'author': 'Eduwebgroup', + 'website': 'https://www.eduwebgroup.com', + 'summary': 'Affipay Payment Acquirer Integration', + 'description': """ + Affipay Payment Acquirer Integration + """, + 'category': 'Accounting/Payment Acquirers', + 'data': [ + 'views/payment_acquirer_views.xml', + 'views/payment_affipay_templates.xml', + 'data/payment_acquirer_data.xml', + ], + 'qweb': [], + 'css': [], + 'images': [], + 'demo': [], + 'installable': True, + 'auto_install': False, + 'application': True, + 'post_init_hook': 'create_missing_journal_for_acquirers', + 'uninstall_hook': 'uninstall_hook', +} diff --git a/payment_affipay/controllers/__init__.py b/payment_affipay/controllers/__init__.py new file mode 100644 index 0000000..65a8c12 --- /dev/null +++ b/payment_affipay/controllers/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import main diff --git a/payment_affipay/controllers/main.py b/payment_affipay/controllers/main.py new file mode 100644 index 0000000..4ea8043 --- /dev/null +++ b/payment_affipay/controllers/main.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +import logging + +from odoo import http, _ +from odoo.http import request + +_logger = logging.getLogger(__name__) + +class AffipayController(http.Controller): + @http.route(["/payment/affipay/s2s/create_json_3ds"], type="json", auth="public", csrf=False) + def affipay_s2s_create_json_3ds(self, verify_validity=False, **kwargs): + if not kwargs.get('partner_id'): + kwargs.update({ + "partner_id": request.env.user.partner_id.id + }) + try: + token = ( + request.env["payment.acquirer"] + .browse(int(kwargs.get("acquirer_id"))) + .s2s_process(kwargs) + ) + if not token: + return { + "result": False + } + return { + "short_name": token.short_name, + "3d_secure": False, + "verified": False, + "result": True, + "id": token.id, + } + except Exception as e: + return { + "result": False, + "error": str(e), + } diff --git a/payment_affipay/data/payment_acquirer_data.xml b/payment_affipay/data/payment_acquirer_data.xml new file mode 100644 index 0000000..a3972b0 --- /dev/null +++ b/payment_affipay/data/payment_acquirer_data.xml @@ -0,0 +1,27 @@ + + + + + + Affipay + Credit Card (powered by Affipay) + + +

+ A payment gateway to accept online payments via credit cards, debit cards and bank transfers. +

+
    +
  • Online Payment
  • +
+
+ + + affipay + s2s + test + test + +
+ +
+
diff --git a/payment_affipay/models/__init__.py b/payment_affipay/models/__init__.py new file mode 100644 index 0000000..a20bdd2 --- /dev/null +++ b/payment_affipay/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import payment \ No newline at end of file diff --git a/payment_affipay/models/payment.py b/payment_affipay/models/payment.py new file mode 100644 index 0000000..07c9a1d --- /dev/null +++ b/payment_affipay/models/payment.py @@ -0,0 +1,393 @@ +# -*- coding: utf-8 -*- + +import base64 +import datetime +from hashlib import sha256 +import logging +import json + +from odoo import models, fields, api, _ +from odoo.http import request +from odoo.tools import DEFAULT_SERVER_DATE_FORMAT +from odoo.exceptions import ValidationError +from odoo.addons.payment.models.payment_acquirer import _partner_split_name + +from werkzeug import urls +import requests + +_logger = logging.getLogger(__name__) + +def get_customer_info(partner_id, country_id=False): + if not country_id: + country_id = partner_id.country_id + + return { + "firstName": "" if partner_id.is_company else _partner_split_name(partner_id.name)[0], + "lastName": partner_id.name if partner_id.is_company else _partner_split_name(partner_id.name)[1], + "email": partner_id.email or "", + "address1": partner_id.street[:40] if partner_id.street else "", + "city": partner_id.city or "", + "country": country_id.code or "", + "ip": request.httprequest.remote_addr or "" + } + +def sha256_hash(text: str): + return sha256(text.encode("utf-8")).hexdigest() + + +def base64_encode(text: str): + return base64.b64encode(text.encode("utf-8")).decode("ascii") + + +class AffipayError(Exception): + def __init__(self, message, response: requests.Response): + super().__init__(message) + self.response = response + + +class PaymentAcquirer(models.Model): + ###################### + # Private attributes # + ###################### + _inherit = "payment.acquirer" + _affipay_oauth_credentials = "affipay_ecommerce_api:affipay_ecommerce_api_password" + + ################### + # Default methods # + ################### + + ###################### + # Fields declaration # + ###################### + provider = fields.Selection(selection_add=[("affipay", "Affipay")]) + affipay_username = fields.Char("Merchant Account ID", + required_if_provider="affipay", + groups="base.group_user", + help="The username and password are provided by Affipay." + ) + affipay_password = fields.Char("Merchant Password", + required_if_provider="affipay", + groups="base.group_user", + help="The username and password are provided by Affipay." + ) + + # * This field will be changed/refreshed using _refresh_access_token() if: + # * It is not set; or it is expired + affipay_access_token = fields.Char( + "Affipay Auth Access Token", default=None) + affipay_prod_auth_url = fields.Char("Production Auth URL", + groups="base.group_user", + help="The Production URL to use when authenticating.") + affipay_prod_ecommerce_url = fields.Char("Production eCommerce URL", + groups="base.group_user", + help="The Production URL to use when adding token or charging.") + affipay_sandbox_auth_url = fields.Char("Sandbox Auth URL", + groups="base.group_user", + help="The Sandbox URL to use when authenticating.") + affipay_sandbox_ecommerce_url = fields.Char("Sandbox eCommerce URL", + groups="base.group_user", + help="The Sandbox URL to use when adding token or charging.") + + ############################## + # Compute and search methods # + ############################## + + ############################ + # Constrains and onchanges # + ############################ + + ######################### + # CRUD method overrides # + ######################### + + ################## + # Action methods # + ################## + @api.model + def affipay_s2s_form_process(self, data): + return self.env["payment.token"].sudo().create({ + **data, + "name": "XXXXXXXXXXXX%s - %s" % (data.get("cc_number")[-4:], data.get("cc_holder_name")), + "acquirer_id": int(data.get("acquirer_id")), + "partner_id": int(data.get("partner_id")) + }) + + def affipay_s2s_form_validate(_, data): + return all( + [data.get(key) + for key in + ["cc_number", + "cc_brand", + "cc_holder_name", + "cc_expiry", + "cc_cvc" + ] + ]) + + #################### + # Business methods # + #################### + def _get_affipaypay_url(self, action): + self.ensure_one() + default_auth_url = "https://sandbox-tokener.blumonpay.net/oauth/token" + default_ecommerce_url = "https://sandbox-ecommerce.blumonpay.net/ecommerce/v2/charge" + urls = { + "prod": { + "oauth": self.affipay_prod_auth_url or default_auth_url, + "ecommerce": self.affipay_prod_ecommerce_url or default_ecommerce_url, + }, + "test": { + "oauth": self.affipay_sandbox_auth_url or default_auth_url, + "ecommerce": self.affipay_sandbox_ecommerce_url or default_ecommerce_url, + } + } + return urls[self.environment][action] + + + def _affipay_request(self, url, headers=False, data=False, params=False, json=False, method="POST"): + self.ensure_one() + try: + response = requests.request( + method, url, data=data, json=json, params=params, headers=headers) + response.raise_for_status() + json = response.json() + if json.get("error"): + raise AffipayError(json.get("error_description"), response) + return response + except requests.exceptions.ConnectionError as errc: + _logger.error(errc) + raise ValidationError("Affipay: The server is down.") + except requests.exceptions.HTTPError as err: + _logger.error(err) + response = err.response + if "application/json" in response.headers.get("Content-Type"): + json = response.json() + if json.get("error"): + raise AffipayError("Affipay: %s: %s" % ( + json.get("error"), json.get("error_description")), response) + raise ValidationError("An HTTP error has occurred: %s." % str(err)) + + def _affipay_oauth_request(self, url, **kwargs): + self.ensure_one() + url = urls.url_join(self._get_affipaypay_url("oauth"), url) + headers = kwargs.get("headers", { + "Authorization": "Basic %s" % base64_encode(self._affipay_oauth_credentials) + }) + try: + response = self._affipay_request(url, headers=headers, **kwargs) + return response.json() + except AffipayError as e: + res_json = e.response.json() + err = res_json.get("error", "Error") + err_description = res_json.get( + "error_description", "Something is wrong with the request") + raise ValidationError("%s: %s" % (err, err_description)) + + def _affipay_ecommerce_request(self, url, eager_refresh=False, retries=2, **kwargs): + self.ensure_one() + if eager_refresh or not self.affipay_access_token: + self._affipay_refresh_access_token() + base_url = self._get_affipaypay_url("ecommerce") + url = urls.url_join(base_url, url) + headers = kwargs.get("headers", { + "Authorization": "Bearer %s" % self.affipay_access_token, + "Content-Type": "application/json", + "Accept": "application/json" + }) + try: + response = self._affipay_request(url, headers=headers, **kwargs) + res_json = response.json() + return res_json + except AffipayError as e: + res_json = e.response.json() + if res_json.get("error") == "invalid_token": + _logger.warn( + "Affipay: Invalid access token. Refreshing access token.") + self._affipay_refresh_access_token() + + # * Retry request if received invalid token error + if retries > 0: + _logger.info( + "Affipay: Retrying request on %s. (%i retries left)" % (url, retries)) + retries -= 1 + return self._affipay_ecommerce_request(url, retries=retries, **kwargs) + err = res_json.get("error", "Error") + err_code = err.get("httpStatusCode", 400) + err_description = err.get( + "description", "Something is wrong with the request") + raise ValidationError("affipay Error: %s %s" % (err_code, err_description)) + + def _affipay_refresh_access_token(self): + self.ensure_one() + data = self._affipay_oauth_request("/oauth/token", data={ + "grant_type": "password", + "username": self.affipay_username, + "password": sha256_hash(self.affipay_password), + }) + if not data.get("access_token"): + _logger.error(json.dumps(data)) + raise ValidationError("No access token given.") + self.affipay_access_token = data.get("access_token") + return self.affipay_access_token + + def _affipay_build_token_request_json(self, vals): + partner_id = self.env["res.partner"].browse(vals.get("partner_id")) + exp_month, exp_year = vals.get("cc_expiry").split("/") + country_id = partner_id.country_id or self.env.company.country_id + + return { + "pan": vals.get("cc_number", "").replace(" ", ""), + "expMonth": exp_month.strip(), + "expYear": "20" + exp_year.strip(), + "holderName": vals.get("cc_holder_name"), + "customerInformation": get_customer_info(partner_id, country_id=country_id) + } + + def _get_feature_support(self): + """Get advanced feature support by provider. + + Each provider should add its technical in the corresponding + key for the following features: + * tokenize: support saving payment data in a payment.tokenize + object + """ + res = super(PaymentAcquirer, self)._get_feature_support() + res["tokenize"].append("affipay") + + return res + +class PaymentTransaction(models.Model): + ###################### + # Private attributes # + ###################### + _inherit = "payment.transaction" + + _affipay_supported_currencies_map = { + "MXN": 484, + } + + ################### + # Default methods # + ################### + + ###################### + # Fields declaration # + ###################### + + ############################## + # Compute and search methods # + ############################## + + ############################ + # Constrains and onchanges # + ############################ + + ######################### + # CRUD method overrides # + ######################### + + ################## + # Action methods # + ################## + def affipay_s2s_do_transaction(self, **kwargs): + self.ensure_one() + if self.currency_id.name not in self._affipay_supported_currencies_map.keys(): + raise ValidationError( + "Currency %s not supported." % self.currency_id.name) + payment_token = self.payment_token_id + acquirer = self.acquirer_id + try: + ecommerce_data = acquirer._affipay_ecommerce_request("/ecommerce/v2/charge", json={ + "amount": float(self.amount) if acquirer.environment == "prod" else min(float(self.amount), 20.0), + "currency": self._affipay_supported_currencies_map[self.currency_id.name], + "noPresentCardData": { + "cardToken": payment_token.acquirer_ref, + } + }) + except AffipayError as e: + ecommerce_data = e.response.json() + return self._affipay_s2s_validate_tree(ecommerce_data) + + #################### + # Business methods # + #################### + def _affipay_s2s_validate_tree(self, tree, call_type="charge"): + self.ensure_one() + if self.state not in ["draft", "pending"]: + _logger.info( + "Affipay: trying to validate an already validated transaction (ref %s)", self.reference) + return True + if tree.get("status") and tree.get("dataResponse", {}).get("description") == "APROBADA": + self.write({ + "date": datetime.date.today().strftime(DEFAULT_SERVER_DATE_FORMAT), + "acquirer_reference": tree.get("id") + }) + if call_type == "charge": + # E-Commerce charge success + if self.partner_id and not self.payment_token_id and \ + (self.type == "form_save" or self.acquirer_id.save_token == "always"): + self.payment_token_id = self.acquirer_id.affipay_s2s_form_process( + dict(request.params)).id + if self.payment_token_id: + self.payment_token_id.verified = True + self._set_transaction_done() + self.execute_callback() + return True + error = tree.get("error", {}) + error_code = error.get("code", "ERR") + error_description = error.get( + "description", "Affipay API gave an error response.") + _logger.error(json.dumps(tree)) + self._set_transaction_error( + "Affipay API Error: %s - %s" % (error_code, error_description)) + return False + +class PaymentToken(models.Model): + ###################### + # Private attributes # + ###################### + _inherit = "payment.token" + + ################### + # Default methods # + ################### + + ###################### + # Fields declaration # + ###################### + + ############################## + # Compute and search methods # + ############################## + + ############################ + # Constrains and onchanges # + ############################ + + ######################### + # CRUD method overrides # + ######################### + @api.model + def affipay_create(self, vals): + if vals.get("cc_number"): + payment_acquirer = self.env["payment.acquirer"].browse(vals.get("acquirer_id")) + response = payment_acquirer._affipay_ecommerce_request("/cardToken/add", + json=payment_acquirer._affipay_build_token_request_json(vals)) + if response.get("status") and response.get("dataResponse"): + vals.update({ + "acquirer_ref": response.get("dataResponse")["id"], + "name": vals.get("name"), + }) + return vals + else: + raise ValidationError("Error: %s" % response.get( + "error", {}).get("description", "")) + return {} + + ################## + # Action methods # + ################## + + #################### + # Business methods # + #################### \ No newline at end of file diff --git a/payment_affipay/static/description/icon.png b/payment_affipay/static/description/icon.png new file mode 100755 index 0000000000000000000000000000000000000000..edfb5f5ba7cba905db4ba51a3215afe0679fbdb9 GIT binary patch literal 10516 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 literal 0 HcmV?d00001 diff --git a/payment_affipay/static/src/img/affipay-logo.png b/payment_affipay/static/src/img/affipay-logo.png new file mode 100755 index 0000000000000000000000000000000000000000..edfb5f5ba7cba905db4ba51a3215afe0679fbdb9 GIT binary patch literal 10516 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 literal 0 HcmV?d00001 diff --git a/payment_affipay/views/payment_acquirer_views.xml b/payment_affipay/views/payment_acquirer_views.xml new file mode 100644 index 0000000..58f465a --- /dev/null +++ b/payment_affipay/views/payment_acquirer_views.xml @@ -0,0 +1,24 @@ + + + + + payment.acquirer.view.form.inherit + payment.acquirer + + + + + + + + + + + + + + + + + + diff --git a/payment_affipay/views/payment_affipay_templates.xml b/payment_affipay/views/payment_affipay_templates.xml new file mode 100644 index 0000000..4869766 --- /dev/null +++ b/payment_affipay/views/payment_affipay_templates.xml @@ -0,0 +1,28 @@ + + + + + + From c2ba798ac5e47006f19fd8a20b68281592b04f2c Mon Sep 17 00:00:00 2001 From: root Date: Sat, 24 Apr 2021 10:06:28 +0800 Subject: [PATCH 2/6] [IMP] payment_affipay: testing --- payment_affipay/models/payment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/payment_affipay/models/payment.py b/payment_affipay/models/payment.py index 07c9a1d..b9bdfea 100644 --- a/payment_affipay/models/payment.py +++ b/payment_affipay/models/payment.py @@ -50,7 +50,7 @@ class PaymentAcquirer(models.Model): # Private attributes # ###################### _inherit = "payment.acquirer" - _affipay_oauth_credentials = "affipay_ecommerce_api:affipay_ecommerce_api_password" + _affipay_oauth_credentials = "blumon_pay_ecommerce_api:blumon_pay_ecommerce_api_password" ################### # Default methods # From 76cf8c30380987c2b72ba8db58c69a3e6205a29a Mon Sep 17 00:00:00 2001 From: root Date: Sat, 24 Apr 2021 18:37:15 +0800 Subject: [PATCH 3/6] [FIX] payment_affipay: removing self.env.company and replaced it with self.company_id --- payment_affipay/models/payment.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/payment_affipay/models/payment.py b/payment_affipay/models/payment.py index b9bdfea..8f9ee73 100644 --- a/payment_affipay/models/payment.py +++ b/payment_affipay/models/payment.py @@ -233,8 +233,7 @@ def _affipay_refresh_access_token(self): def _affipay_build_token_request_json(self, vals): partner_id = self.env["res.partner"].browse(vals.get("partner_id")) exp_month, exp_year = vals.get("cc_expiry").split("/") - country_id = partner_id.country_id or self.env.company.country_id - + country_id = partner_id.country_id or self.company_id.country_id return { "pan": vals.get("cc_number", "").replace(" ", ""), "expMonth": exp_month.strip(), From 0105b945c3ee6649644d2ba3aa06dd74bce47243 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 10 May 2021 22:12:52 +0800 Subject: [PATCH 4/6] [ADD] l10n_mx_edi_extends: initial commit --- l10n_mx_edi_extends/__init__.py | 3 ++ l10n_mx_edi_extends/__manifest__.py | 28 ++++++++++++ l10n_mx_edi_extends/models/__init__.py | 4 ++ .../models/payment_acquirer.py | 40 +++++++++++++++++ .../models/payment_transaction.py | 43 +++++++++++++++++++ .../views/payment_acquirer_views.xml | 15 +++++++ 6 files changed, 133 insertions(+) create mode 100644 l10n_mx_edi_extends/__init__.py create mode 100644 l10n_mx_edi_extends/__manifest__.py create mode 100644 l10n_mx_edi_extends/models/__init__.py create mode 100644 l10n_mx_edi_extends/models/payment_acquirer.py create mode 100644 l10n_mx_edi_extends/models/payment_transaction.py create mode 100644 l10n_mx_edi_extends/views/payment_acquirer_views.xml diff --git a/l10n_mx_edi_extends/__init__.py b/l10n_mx_edi_extends/__init__.py new file mode 100644 index 0000000..5305644 --- /dev/null +++ b/l10n_mx_edi_extends/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import models \ No newline at end of file diff --git a/l10n_mx_edi_extends/__manifest__.py b/l10n_mx_edi_extends/__manifest__.py new file mode 100644 index 0000000..8e5ec6f --- /dev/null +++ b/l10n_mx_edi_extends/__manifest__.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Part of Ktrine. See LICENSE file for full copyright and licensing details. + +{ + 'name': 'EDI Payment Extension', + 'version': '12.0.0.0', + 'depends': [ + 'l10n_mx_edi' + ], + 'external_dependencies': {}, + 'author': 'Eduwebgroup', + 'website': 'https://www.eduwebgroup.com', + 'summary': 'EDI Payment Extension', + 'description': """ + EDI Payment Extension + """, + 'category': 'Hidden', + 'data': [ + 'views/payment_acquirer_views.xml', + ], + 'qweb': [], + 'css': [], + 'images': [], + 'demo': [], + 'installable': True, + 'auto_install': False, + 'application': True, +} diff --git a/l10n_mx_edi_extends/models/__init__.py b/l10n_mx_edi_extends/models/__init__.py new file mode 100644 index 0000000..8bf266c --- /dev/null +++ b/l10n_mx_edi_extends/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +from . import payment_acquirer +from . import payment_transaction \ No newline at end of file diff --git a/l10n_mx_edi_extends/models/payment_acquirer.py b/l10n_mx_edi_extends/models/payment_acquirer.py new file mode 100644 index 0000000..773226a --- /dev/null +++ b/l10n_mx_edi_extends/models/payment_acquirer.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- + +from odoo import models, fields, api, _ + + +class PaymentAcquirer(models.Model): + ###################### + # Private attributes # + ###################### + _inherit = "payment.acquirer" + ################### + # Default methods # + ################### + + ###################### + # Fields declaration # + ###################### + l10n_mx_edi_payment_method_id = fields.Many2one(string="Payment Way", + comodel_name="l10n_mx_edi.payment.method") + + ############################## + # Compute and search methods # + ############################## + + ############################ + # Constrains and onchanges # + ############################ + + ######################### + # CRUD method overrides # + ######################### + + ################## + # Action methods # + ################## + + #################### + # Business methods # + #################### + \ No newline at end of file diff --git a/l10n_mx_edi_extends/models/payment_transaction.py b/l10n_mx_edi_extends/models/payment_transaction.py new file mode 100644 index 0000000..1348793 --- /dev/null +++ b/l10n_mx_edi_extends/models/payment_transaction.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- + +from odoo import models, fields, api, _ + + +class PaymentTransaction(models.Model): + ###################### + # Private attributes # + ###################### + _inherit = "payment.transaction" + ################### + # Default methods # + ################### + + ###################### + # Fields declaration # + ###################### + + ############################## + # Compute and search methods # + ############################## + + ############################ + # Constrains and onchanges # + ############################ + + ######################### + # CRUD method overrides # + ######################### + + ################## + # Action methods # + ################## + + #################### + # Business methods # + #################### + @api.multi + def _prepare_account_payment_vals(self): + self.ensure_one() + res = super(PaymentTransaction, self)._prepare_account_payment_vals() + res["l10n_mx_edi_payment_method_id"] = self.acquirer_id.l10n_mx_edi_payment_method_id.id if self.acquirer_id.l10n_mx_edi_payment_method_id else False, + return res \ No newline at end of file diff --git a/l10n_mx_edi_extends/views/payment_acquirer_views.xml b/l10n_mx_edi_extends/views/payment_acquirer_views.xml new file mode 100644 index 0000000..5170093 --- /dev/null +++ b/l10n_mx_edi_extends/views/payment_acquirer_views.xml @@ -0,0 +1,15 @@ + + + + + payment.acquirer.view.form.inherit + payment.acquirer + + + + + + + + + From 837fce1537ae6d01624bc9245b2126159a5c11a7 Mon Sep 17 00:00:00 2001 From: LuisMalave2001 Date: Fri, 21 May 2021 14:09:02 +0000 Subject: [PATCH 5/6] [IMP] payment_affipay: Message appear --- payment_affipay/models/payment.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/payment_affipay/models/payment.py b/payment_affipay/models/payment.py index 8f9ee73..41860f2 100644 --- a/payment_affipay/models/payment.py +++ b/payment_affipay/models/payment.py @@ -211,11 +211,12 @@ def _affipay_ecommerce_request(self, url, eager_refresh=False, retries=2, **kwar "Affipay: Retrying request on %s. (%i retries left)" % (url, retries)) retries -= 1 return self._affipay_ecommerce_request(url, retries=retries, **kwargs) + raise e err = res_json.get("error", "Error") err_code = err.get("httpStatusCode", 400) err_description = err.get( "description", "Something is wrong with the request") - raise ValidationError("affipay Error: %s %s" % (err_code, err_description)) + raise AffipayError("affipay Error: %s %s" % (err_code, err_description)) def _affipay_refresh_access_token(self): self.ensure_one() @@ -226,7 +227,7 @@ def _affipay_refresh_access_token(self): }) if not data.get("access_token"): _logger.error(json.dumps(data)) - raise ValidationError("No access token given.") + raise AffipayError("No access token given.") self.affipay_access_token = data.get("access_token") return self.affipay_access_token From 504e2a33230896c99126f6a81488c8060359057d Mon Sep 17 00:00:00 2001 From: LuisMalave2001 Date: Fri, 21 May 2021 14:09:34 +0000 Subject: [PATCH 6/6] [IMP] payment_affipay: Message appear --- payment_affipay/views/payment_affipay_templates.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/payment_affipay/views/payment_affipay_templates.xml b/payment_affipay/views/payment_affipay_templates.xml index 4869766..f105aa6 100644 --- a/payment_affipay/views/payment_affipay_templates.xml +++ b/payment_affipay/views/payment_affipay_templates.xml @@ -20,7 +20,7 @@
- +