From 6037f1e986e7ef8c57659e8a82f466e5621fb092 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Wed, 19 Feb 2025 14:12:48 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9D=D0=B0=D1=87=D0=B0=D0=BB=D1=8C=D0=BD?= =?UTF-8?q?=D0=BE=D0=B5=20=D0=BD=D0=B0=D0=BF=D0=BE=D0=BB=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __init__.py | 4 +++ __manifest__.py | 9 ++++++ controllers/__init__.py | 2 ++ controllers/main.py | 53 +++++++++++++++++++++++++++++++++ i18n/pos_self_order_adyen.pot | 21 +++++++++++++ i18n/ru.po | 23 +++++++++++++++ models/__init__.py | 3 ++ models/pos_payment_method.py | 55 +++++++++++++++++++++++++++++++++++ 8 files changed, 170 insertions(+) create mode 100644 __init__.py create mode 100644 __manifest__.py create mode 100644 controllers/__init__.py create mode 100644 controllers/main.py create mode 100644 i18n/pos_self_order_adyen.pot create mode 100644 i18n/ru.po create mode 100644 models/__init__.py create mode 100644 models/pos_payment_method.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..c3d410e --- /dev/null +++ b/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +from . import models +from . import controllers diff --git a/__manifest__.py b/__manifest__.py new file mode 100644 index 0000000..437520f --- /dev/null +++ b/__manifest__.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +{ + "name": "POS Self Order Adyen", + "summary": "Addon for the Self Order App that allows customers to pay by Adyen.", + "category": "Sales/Point Of Sale", + "depends": ["pos_adyen", "pos_self_order"], + "auto_install": True, + "license": "LGPL-3", +} diff --git a/controllers/__init__.py b/controllers/__init__.py new file mode 100644 index 0000000..757b12a --- /dev/null +++ b/controllers/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import main diff --git a/controllers/main.py b/controllers/main.py new file mode 100644 index 0000000..d0d7489 --- /dev/null +++ b/controllers/main.py @@ -0,0 +1,53 @@ +import logging +from odoo.addons.pos_adyen.controllers.main import PosAdyenController +from odoo import fields +from odoo.http import request + +_logger = logging.getLogger(__name__) + + +class PosSelfAdyenController(PosAdyenController): + + def _process_payment_response(self, data, adyen_pm_sudo): + self_order_id = None + try: + self_order_id = PosAdyenController._get_additional_data_from_unparsed(data['SaleToPOIResponse']['PaymentResponse']['Response']['AdditionalResponse'], 'metadata.self_order_id') + except KeyError: + self_order_id = None + + if not self_order_id: + return super()._process_payment_response(data, adyen_pm_sudo) + + order_sudo = request.env['pos.order'].sudo().search([('id', '=', self_order_id)], limit=1) + if not order_sudo: + _logger.warning('Received an Adyen event notification for the self order #%d that does not exist (anymore)', self_order_id) + return request.make_json_response('[accepted]') # https://docs.adyen.com/point-of-sale/design-your-integration/choose-your-architecture/cloud/#guarantee + + order = order_sudo.sudo(False).with_user(order_sudo.session_id.config_id.self_ordering_default_user_id).with_company(order_sudo.session_id.config_id.company_id) + + payment_result = data['SaleToPOIResponse']['PaymentResponse']['Response']['Result'] + + if payment_result == 'Success' and order.config_id.self_ordering_mode == 'kiosk': + payment_amount = data['SaleToPOIResponse']['PaymentResponse']['PaymentResult']['AmountsResp']['AuthorizedAmount'] + card_type = data['SaleToPOIResponse']['PaymentResponse']['PaymentResult']['PaymentInstrumentData']['CardData']['PaymentBrand'] + transaction_id = data['SaleToPOIResponse']['PaymentResponse']['SaleData']['SaleTransactionID']['TransactionID'] + order.add_payment({ + 'amount': payment_amount, + 'payment_date': fields.Datetime.now(), + 'payment_method_id': adyen_pm_sudo.id, + 'card_type': card_type, + 'cardholder_name': '', + 'transaction_id': transaction_id, + 'payment_status': payment_result, + 'ticket': '', + 'pos_order_id': order.id + }) + order.action_pos_order_paid() + order._send_order() + + if order.config_id.self_ordering_mode == 'kiosk': + order.env['bus.bus']._sendone(f'pos_config-{order.config_id.access_token}', 'PAYMENT_STATUS', { + 'payment_result': payment_result, + 'order': order._export_for_self_order(), + }) + return request.make_json_response('[accepted]') # https://docs.adyen.com/point-of-sale/design-your-integration/choose-your-architecture/cloud/#guarantee diff --git a/i18n/pos_self_order_adyen.pot b/i18n/pos_self_order_adyen.pot new file mode 100644 index 0000000..301cc3e --- /dev/null +++ b/i18n/pos_self_order_adyen.pot @@ -0,0 +1,21 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * pos_self_order_adyen +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2023-10-26 21:55+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: pos_self_order_adyen +#: model:ir.model,name:pos_self_order_adyen.model_pos_payment_method +msgid "Point of Sale Payment Methods" +msgstr "" diff --git a/i18n/ru.po b/i18n/ru.po new file mode 100644 index 0000000..07f31e5 --- /dev/null +++ b/i18n/ru.po @@ -0,0 +1,23 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * pos_self_order_adyen +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-10-26 21:55+0000\n" +"PO-Revision-Date: 2024-01-30 15:14+0400\n" +"Last-Translator: \n" +"Language-Team: Russian (https://app.transifex.com/odoo/teams/41243/ru/)\n" +"Language: ru\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n" + +#. module: pos_self_order_adyen +#: model:ir.model,name:pos_self_order_adyen.model_pos_payment_method +msgid "Point of Sale Payment Methods" +msgstr "Способы оплаты в торговых точках" diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..7f48bb5 --- /dev/null +++ b/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import pos_payment_method diff --git a/models/pos_payment_method.py b/models/pos_payment_method.py new file mode 100644 index 0000000..d759ad8 --- /dev/null +++ b/models/pos_payment_method.py @@ -0,0 +1,55 @@ +from datetime import datetime, timezone +import random +from odoo import models, api +from odoo.addons.pos_adyen.models.pos_payment_method import UNPREDICTABLE_ADYEN_DATA + + +class PosPaymentMethod(models.Model): + _inherit = "pos.payment.method" + + @api.model + def _get_valid_acquirer_data(self): + res = super()._get_valid_acquirer_data() + res['metadata.self_order_id'] = UNPREDICTABLE_ADYEN_DATA + return res + + def _payment_request_from_kiosk(self, order): + if self.use_payment_terminal != 'adyen': + return super()._payment_request_from_kiosk(order) + else: + pos_config = order.session_id.config_id + random_number = random.randrange(10**9, 10**10 - 1) + + # https://docs.adyen.com/point-of-sale/basic-tapi-integration/make-a-payment/#make-a-payment + data = { + 'SaleToPOIRequest': { + 'MessageHeader': { + 'ProtocolVersion': "3.0", + 'MessageClass': "Service", + 'MessageType': "Request", + 'MessageCategory': "Payment", + 'SaleID': f'{pos_config.display_name} (ID:{pos_config.id})', # Your unique ID for the POS system component to send this request from. + 'ServiceID': str(random_number), # Your unique ID for this request, consisting of 1-10 alphanumeric characters. + 'POIID': self.adyen_terminal_identifier, # The unique ID of the terminal to send this request to. + }, + 'PaymentRequest': { + 'SaleData': { + 'SaleTransactionID': { + 'TransactionID': order.pos_reference, # your reference to identify a payment. + 'TimeStamp': datetime.now(tz=timezone.utc).isoformat(timespec='seconds'), # date and time of the request in UTC format. + }, + 'SaleToAcquirerData': 'metadata.self_order_id=' + str(order.id), + }, + 'PaymentTransaction': { + 'AmountsReq': { + 'Currency': order.currency_id.name, # the transaction currency. + 'RequestedAmount': order.amount_total, # The final transaction amount. + }, + }, + }, + }, + } + + req = self.proxy_adyen_request(data) + + return req and (isinstance(req, bool) or not req.get('error'))