Skip to content

Commit

Permalink
[ADD] added new module for v14 to manage mollie terminal with Odoo pos
Browse files Browse the repository at this point in the history
  • Loading branch information
odoo-mvds committed Jun 5, 2023
1 parent 318c924 commit bc8d2e7
Show file tree
Hide file tree
Showing 36 changed files with 1,075 additions and 0 deletions.
3 changes: 3 additions & 0 deletions mollie_pos_terminal/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from . import models
from . import wizard
from . import controllers
28 changes: 28 additions & 0 deletions mollie_pos_terminal/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
'name': 'Mollie Pos Terminal',
'version': '14.0.0.0',
'description': '',
'summary': 'Connect your pos with mollie terminal',
'author': 'Mollie',
'maintainer': 'Applix',
'license': 'LGPL-3',
'category': '',
'depends': [
'point_of_sale',
],
'data': [
'security/ir.model.access.csv',
'views/assets.xml',
'views/mollie_pos_terminal_views.xml',
'views/mollie_pos_terminal_payments_views.xml',
'views/res_config_settings_views.xml',
'views/pos_payment_method_views.xml',
'wizard/mollie_sync_terminal.xml',
],
'qweb': [
'static/src/views/*.xml',
],
'images': [
'static/description/cover.png',
],
}
2 changes: 2 additions & 0 deletions mollie_pos_terminal/controllers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# coding: utf-8
from . import main
17 changes: 17 additions & 0 deletions mollie_pos_terminal/controllers/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# coding: utf-8
import logging
import json
from odoo import fields, http
from odoo.http import request

_logger = logging.getLogger(__name__)


class PosMollieController(http.Controller):

@http.route('/pos_mollie/webhook', type='http', methods=['POST'], auth='public', csrf=False)
def webhook(self, **post):
if not post.get('id'):
return
request.env['mollie.pos.terminal.payments']._mollie_process_webhook(post)
return ""
5 changes: 5 additions & 0 deletions mollie_pos_terminal/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from . import res_company
from . import res_config_settings
from . import pos_payment_method
from . import mollie_pos_terminal
from . import mollie_pos_terminal_payments
135 changes: 135 additions & 0 deletions mollie_pos_terminal/models/mollie_pos_terminal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import logging
import requests
from werkzeug import urls

from odoo import fields, models, _
from odoo.exceptions import ValidationError

_logger = logging.getLogger(__name__)


class MolliePosTerminal(models.Model):
_name = 'mollie.pos.terminal'
_description = 'Mollie Pos Terminal'

name = fields.Char()
terminal_id = fields.Char('Terminal ID')
profile_id = fields.Char('Profile ID')
serial_number = fields.Char('Serial Number')
status = fields.Selection([
('pending', 'Pending'),
('active', 'Active'),
('inactive', 'Inactive')
])
currency_id = fields.Many2one('res.currency', string='Currency')
company_id = fields.Many2one('res.company', required=True, default=lambda self: self.env.company)

def _sync_mollie_terminals(self):
existing_terminals = self.search([])
terminals_data = self._api_get_terminals() # TODO: manage pager for 250+ terminals

if not terminals_data.get('count'):
return

for terminal in terminals_data['_embedded']['terminals']:
terminal_found = existing_terminals.filtered(lambda x: x.terminal_id == terminal['id'])
currency = self.env['res.currency'].search([('name', '=', terminal['currency'])])

if not currency:
raise ValidationError(_('Currency ') + terminal['currency'] + _(' is not active. Please activate it first.'))

terminal_data = {
'name': terminal['description'],
'terminal_id': terminal['id'],
'profile_id': terminal['profileId'],
'serial_number': terminal['serialNumber'],
'status': terminal['status'],
'currency_id': currency.id
}
if terminal_found:
terminal_found.write(terminal_data)
else:
self.create(terminal_data)

# =================
# API CALLS METHODS
# =================

def _api_get_terminals(self):
""" Fetch terminals data from mollie api """
return self._mollie_api_call('/terminals', method='GET')

def _api_make_payment_request(self, data):
payment_payload = self._prepare_payment_payload(data)
result = self._mollie_api_call('/payments', data=payment_payload, method='POST', silent=True)
self.env['mollie.pos.terminal.payments']._create_mollie_payment_request(result, {**data, 'terminal_id': self.id})
return result

def _api_cancel_mollie_payment(self, transaction_id):
return self.sudo()._mollie_api_call(f'/payments/{transaction_id}', method='DELETE', silent=True)

def _api_get_mollie_payment_status(self, transaction_id):
return self.sudo()._mollie_api_call(f'/payments/{transaction_id}', method='GET', silent=True)

def _prepare_payment_payload(self, data):
base_url = self.get_base_url()
webhook_url = urls.url_join(base_url, '/pos_mollie/webhook/')
return {
"amount": {
"currency": data['curruncy'],
"value": f"{data['amount']:.2f}"
},
"description": data['description'],
"webhookUrl": webhook_url,
"redirectUrl": webhook_url,
"method": "pointofsale",
"terminalId": self.terminal_id,
"metadata": {
"mollie_uid": data['mollie_uid'],
"order_id": data['order_id'],
}
}

# =====================
# GENERIC TOOLS METHODS
# =====================

def _mollie_api_call(self, endpoint, data=None, method='POST', silent=False):
company = self.company_id or self.env.company

headers = {
'content-type': 'application/json',
"Authorization": f'Bearer {company.mollie_terminal_api_key}',
}

endpoint = f'/v2/{endpoint.strip("/")}'
url = urls.url_join('https://api.mollie.com/', endpoint)

_logger.info('Mollie POS Terminal CALL on: %s', url)

try:
response = requests.request(method, url, json=data, headers=headers, timeout=60)
response.raise_for_status()
except requests.exceptions.HTTPError:
error_details = response.json()
_logger.exception("MOLLIE-POS-ERROR \n %s", error_details)
if silent:
return error_details
else:
raise ValidationError("MOLLIE: \n %s" % error_details)
except requests.exceptions.RequestException as e:
_logger.exception("unable to communicate with Mollie: %s \n %s", url, e)
if silent:
return {'error': "Some thing went wrong"}
else:
raise ValidationError("Mollie: " + _("Some thing went wrong."))
return response.json()

def show_form_and_tree(self):
action = self.env['ir.actions.actions']._for_xml_id('mollie_pos_terminal.mollie_pos_terminal_payments_action')
action.update({
'domain': [('terminal_id', '=', self.id)],
'views': [(self.env.ref('mollie_pos_terminal.mollie_pos_terminal_payments_view_tree').id, 'tree'), (self.env.ref('mollie_pos_terminal.mollie_pos_terminal_payments_view_form').id, 'form')],
'res_id': self.id,
})
return action
75 changes: 75 additions & 0 deletions mollie_pos_terminal/models/mollie_pos_terminal_payments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import json
import logging
import requests
from werkzeug import urls

from odoo import fields, models, api, _
from odoo.exceptions import ValidationError

_logger = logging.getLogger(__name__)


class MolliePosTerminal(models.Model):
_name = 'mollie.pos.terminal.payments'
_description = 'Mollie Pos Terminal'

name = fields.Char("Transaction ID")
mollie_uid = fields.Char("Mollie UID")
terminal_id = fields.Many2one('mollie.pos.terminal')
mollie_latest_response = fields.Text('Response', default="{}")
status = fields.Selection([
('open', 'Open'),
('paid', 'Paid'),
('failed', 'Failed'),
('expired', 'Expired'),
('canceled', 'Canceled'),
('pending', 'Pending'),
], default='open')

def _create_mollie_payment_request(self, response, data):
if response and response.get('status') == 'open':
self.create({
'name': response.get('id'),
'mollie_uid': data.get('mollie_uid'),
'terminal_id': data.get('terminal_id'),
'mollie_latest_response': json.dumps(response or {}),
'status': response.get('status')
})

@api.model
def get_mollie_payment_status(self, transaction_id=None, mollie_uid=None):
domain = []
if transaction_id:
domain.append(('name', '=', transaction_id))
elif mollie_uid:
domain.append(('mollie_uid', '=', mollie_uid))
else:
return {}
mollie_payment = self.search(domain, limit=1)
if mollie_payment:
return json.loads(mollie_payment.mollie_latest_response or '{}')
return {}

@api.model
def mollie_cancel_payment_request(self, transaction_id=None, mollie_uid=None):
domain = []
if transaction_id:
domain.append(('name', '=', transaction_id))
elif mollie_uid:
domain.append(('mollie_uid', '=', mollie_uid))
else:
return {}
mollie_payment = self.search(domain, limit=1)
if mollie_payment and mollie_payment.status == 'open':
return mollie_payment.terminal_id._api_cancel_mollie_payment(mollie_payment.name)
return {}

def _mollie_process_webhook(self, webhook_data):
mollie_payment = self.sudo().search([('name', '=', webhook_data.get('id'))], limit=1)
if mollie_payment:
payment_status = mollie_payment.terminal_id._api_get_mollie_payment_status(webhook_data.get('id'))
if payment_status and payment_status.get('status'):
mollie_payment.write({
'mollie_latest_response': json.dumps(payment_status or {}),
'status': payment_status.get('status')
})
13 changes: 13 additions & 0 deletions mollie_pos_terminal/models/pos_payment_method.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from odoo import fields, models


class PosPaymentMethod(models.Model):
_inherit = 'pos.payment.method'

mollie_pos_terminal_id = fields.Many2one('mollie.pos.terminal', string='Mollie Pos Terminal', domain="[('status', '=', 'active')]")

def _get_payment_terminal_selection(self):
return super(PosPaymentMethod, self)._get_payment_terminal_selection() + [('mollie', 'Mollie')]

def mollie_payment_request(self, data):
return self.mollie_pos_terminal_id._api_make_payment_request(data)
7 changes: 7 additions & 0 deletions mollie_pos_terminal/models/res_company.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from odoo import models, fields, _


class Company(models.Model):
_inherit = 'res.company'

mollie_terminal_api_key = fields.Char(string="Mollie Terminal Api Key")
7 changes: 7 additions & 0 deletions mollie_pos_terminal/models/res_config_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from odoo import models, fields


class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'

mollie_terminal_api_key = fields.Char(related='company_id.mollie_terminal_api_key', string='Mollie Terminal Api Key', readonly=False)
5 changes: 5 additions & 0 deletions mollie_pos_terminal/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_mollie_pos_terminal,access_mollie_pos_terminal,model_mollie_pos_terminal,point_of_sale.group_pos_manager,1,1,1,1
access_mollie_wiz_sync_terminal,access_mollie_wiz_sync_terminal,model_sync_mollie_terminal,point_of_sale.group_pos_manager,1,1,1,1
access_mollie_pos_terminal_payments_user,access_mollie_pos_terminal_payments_user,model_mollie_pos_terminal_payments,point_of_sale.group_pos_user,1,1,1,0
access_mollie_pos_terminal_payments_manager,access_mollie_pos_terminal_payments_manager,model_mollie_pos_terminal_payments,point_of_sale.group_pos_manager,1,1,1,1
Binary file added mollie_pos_terminal/static/description/cover.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mollie_pos_terminal/static/description/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit bc8d2e7

Please sign in to comment.