208 lines
9.7 KiB
Python
208 lines
9.7 KiB
Python
|
# coding: utf-8
|
||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||
|
import logging
|
||
|
import requests
|
||
|
|
||
|
from odoo import fields, models, api, tools, _
|
||
|
from odoo.exceptions import UserError, AccessError
|
||
|
|
||
|
_logger = logging.getLogger(__name__)
|
||
|
TIMEOUT = 10
|
||
|
|
||
|
|
||
|
class PosPaymentMethod(models.Model):
|
||
|
_inherit = 'pos.payment.method'
|
||
|
|
||
|
# Viva Wallet
|
||
|
viva_wallet_merchant_id = fields.Char(string="Merchant ID", help='Used when connecting to Viva Wallet: https://developer.vivawallet.com/getting-started/find-your-account-credentials/merchant-id-and-api-key/')
|
||
|
viva_wallet_api_key = fields.Char(string="API Key", help='Used when connecting to Viva Wallet: https://developer.vivawallet.com/getting-started/find-your-account-credentials/merchant-id-and-api-key/')
|
||
|
viva_wallet_client_id = fields.Char(string="Client ID", help='Used when connecting to Viva Wallet: https://developer.vivawallet.com/getting-started/find-your-account-credentials/pos-apis-credentials/#find-your-pos-apis-credentials')
|
||
|
viva_wallet_client_secret = fields.Char(string="Client secret")
|
||
|
viva_wallet_terminal_id = fields.Char(string="Terminal ID", help='[Terminal ID of the Viva Wallet terminal], for example: 16002169')
|
||
|
viva_wallet_bearer_token = fields.Char(default='Bearer Token')
|
||
|
viva_wallet_webhook_verification_key = fields.Char()
|
||
|
viva_wallet_latest_response = fields.Json() # used to buffer the latest asynchronous notification from Adyen.
|
||
|
viva_wallet_test_mode = fields.Boolean(string="Test mode", help="Run transactions in the test environment.")
|
||
|
viva_wallet_webhook_endpoint = fields.Char(compute='_compute_viva_wallet_webhook_endpoint', readonly=True)
|
||
|
|
||
|
|
||
|
def _viva_wallet_account_get_endpoint(self):
|
||
|
if self.viva_wallet_test_mode:
|
||
|
return 'https://demo-accounts.vivapayments.com'
|
||
|
return 'https://accounts.vivapayments.com'
|
||
|
|
||
|
def _viva_wallet_api_get_endpoint(self):
|
||
|
if self.viva_wallet_test_mode:
|
||
|
return 'https://demo-api.vivapayments.com'
|
||
|
return 'https://api.vivapayments.com'
|
||
|
|
||
|
def _viva_wallet_webhook_get_endpoint(self):
|
||
|
if self.viva_wallet_test_mode:
|
||
|
return 'https://demo.vivapayments.com'
|
||
|
return 'https://www.vivapayments.com'
|
||
|
|
||
|
def _compute_viva_wallet_webhook_endpoint(self):
|
||
|
web_base_url = self.get_base_url()
|
||
|
self.viva_wallet_webhook_endpoint = f"{web_base_url}/pos_viva_wallet/notification?company_id={self.company_id.id}&token={self.viva_wallet_webhook_verification_key}"
|
||
|
|
||
|
def _is_write_forbidden(self, fields):
|
||
|
# Allow the modification of these fields even if a pos_session is open
|
||
|
whitelisted_fields = {'viva_wallet_bearer_token', 'viva_wallet_webhook_verification_key', 'viva_wallet_latest_response'}
|
||
|
return bool(fields - whitelisted_fields and self.open_session_ids)
|
||
|
|
||
|
def _get_payment_terminal_selection(self):
|
||
|
return super()._get_payment_terminal_selection() + [('viva_wallet', 'Viva Wallet')]
|
||
|
|
||
|
def _bearer_token(self, session):
|
||
|
self.ensure_one()
|
||
|
if not self.env.user.has_group('point_of_sale.group_pos_user'):
|
||
|
raise AccessError(_("Do not have access to fetch token from Viva Wallet"))
|
||
|
|
||
|
data = {'grant_type': 'client_credentials'}
|
||
|
auth = requests.auth.HTTPBasicAuth(self.viva_wallet_client_id, self.viva_wallet_client_secret)
|
||
|
try:
|
||
|
resp = session.post(f"{self._viva_wallet_account_get_endpoint()}/connect/token", auth=auth, data=data, timeout=TIMEOUT)
|
||
|
except requests.exceptions.RequestException:
|
||
|
_logger.exception("Failed to call viva_wallet_bearer_token endpoint")
|
||
|
|
||
|
access_token = resp.json().get('access_token')
|
||
|
if access_token:
|
||
|
self.viva_wallet_bearer_token = access_token
|
||
|
return {'Authorization': f"Bearer {access_token}"}
|
||
|
else:
|
||
|
raise UserError(_('Not receive Bearer Token'))
|
||
|
|
||
|
def _get_verification_key(self, endpoint, viva_wallet_merchant_id, viva_wallet_api_key):
|
||
|
# Get a key to configure the webhook.
|
||
|
# this key need to be the response when we receive a notifiaction
|
||
|
# do not execute this query in test mode
|
||
|
if tools.config['test_enable']:
|
||
|
return 'viva_wallet_test'
|
||
|
|
||
|
auth = requests.auth.HTTPBasicAuth(viva_wallet_merchant_id, viva_wallet_api_key)
|
||
|
try:
|
||
|
resp = requests.get(f"{endpoint}/api/messages/config/token", auth=auth, timeout=TIMEOUT)
|
||
|
except requests.exceptions.RequestException:
|
||
|
_logger.exception('Failed to call https://%s/api/messages/config/token endpoint', endpoint)
|
||
|
return resp.json().get('Key')
|
||
|
|
||
|
def _call_viva_wallet(self, endpoint, action, data=None):
|
||
|
session = get_viva_wallet_session()
|
||
|
session.headers.update({'Authorization': f"Bearer {self.viva_wallet_bearer_token}"})
|
||
|
endpoint = f"{self._viva_wallet_api_get_endpoint()}/ecr/v1/{endpoint}"
|
||
|
try:
|
||
|
resp = session.request(action, endpoint, json=data, timeout=TIMEOUT)
|
||
|
except requests.exceptions.RequestException as e:
|
||
|
return {'error': _("There are some issues between us and Viva Wallet, try again later.%s)", e)}
|
||
|
|
||
|
if resp.text and resp.json().get('detail') == 'Could not validate credentials':
|
||
|
session.headers.update(self._bearer_token(session))
|
||
|
resp = session.request(action, endpoint, json=data, timeout=TIMEOUT)
|
||
|
|
||
|
if resp.status_code == 200:
|
||
|
if resp.text:
|
||
|
return resp.json()
|
||
|
return {'success': resp.status_code}
|
||
|
else:
|
||
|
return {'error': _("There are some issues between us and Viva Wallet, try again later. %s", resp.json().get('detail'))}
|
||
|
|
||
|
def _retrieve_session_id(self, data_webhook):
|
||
|
# Send a request to confirm the status of the sesions_id
|
||
|
# Need wait to the status of sesions_id is updated setted in session headers; code 202
|
||
|
|
||
|
session_id, pos_session_id = data_webhook.get('MerchantTrns', '').split("/") # Split to retrieve pos_sessions_id
|
||
|
endpoint = f"sessions/{session_id}"
|
||
|
data = self._call_viva_wallet(endpoint, 'get')
|
||
|
|
||
|
if data.get('success'):
|
||
|
data.update({'pos_session_id': pos_session_id, 'data_webhook': data_webhook})
|
||
|
self.viva_wallet_latest_response = data
|
||
|
self._send_notification(data)
|
||
|
else:
|
||
|
self._send_notification(
|
||
|
{'error': _(
|
||
|
"There are some issues between us and Viva Wallet, try again later. %s",
|
||
|
data.get('detail')
|
||
|
)}
|
||
|
)
|
||
|
|
||
|
def _send_notification(self, data):
|
||
|
# Send a notification to the point of sale channel to indicate that the transaction are finish
|
||
|
pos_session_sudo = self.env["pos.session"].browse(int(data.get('pos_session_id', False)))
|
||
|
if pos_session_sudo:
|
||
|
self.env['bus.bus']._sendone(pos_session_sudo._get_bus_channel_name(), 'VIVA_WALLET_LATEST_RESPONSE', pos_session_sudo.config_id.id)
|
||
|
|
||
|
def viva_wallet_send_payment_request(self, data):
|
||
|
if not self.env.user.has_group('point_of_sale.group_pos_user'):
|
||
|
raise AccessError(_("Only 'group_pos_user' are allowed to fetch token from Viva Wallet"))
|
||
|
|
||
|
endpoint = "transactions:sale"
|
||
|
return self._call_viva_wallet(endpoint, 'post', data)
|
||
|
|
||
|
def viva_wallet_send_payment_cancel(self, data):
|
||
|
if not self.env.user.has_group('point_of_sale.group_pos_user'):
|
||
|
raise AccessError(_("Only 'group_pos_user' are allowed to fetch token from Viva Wallet"))
|
||
|
|
||
|
session_id = data.get('sessionId')
|
||
|
cash_register_id = data.get('cashRegisterId')
|
||
|
endpoint = f"sessions/{session_id}?cashRegisterId={cash_register_id}"
|
||
|
return self._call_viva_wallet(endpoint, 'delete')
|
||
|
|
||
|
def write(self, vals):
|
||
|
record = super().write(vals)
|
||
|
|
||
|
if vals.get('viva_wallet_merchant_id') and vals.get('viva_wallet_api_key'):
|
||
|
self.viva_wallet_webhook_verification_key = self._get_verification_key(
|
||
|
self._viva_wallet_webhook_get_endpoint(),
|
||
|
self.viva_wallet_merchant_id,
|
||
|
self.viva_wallet_api_key
|
||
|
)
|
||
|
|
||
|
return record
|
||
|
|
||
|
|
||
|
def create(self, vals):
|
||
|
records = super().create(vals)
|
||
|
|
||
|
for record in records:
|
||
|
if record.viva_wallet_merchant_id and record.viva_wallet_api_key:
|
||
|
record.viva_wallet_webhook_verification_key = record._get_verification_key(
|
||
|
record._viva_wallet_webhook_get_endpoint(),
|
||
|
record.viva_wallet_merchant_id,
|
||
|
record.viva_wallet_api_key,
|
||
|
)
|
||
|
|
||
|
return records
|
||
|
|
||
|
def get_latest_viva_wallet_status(self):
|
||
|
if not self.env.user.has_group('point_of_sale.group_pos_user'):
|
||
|
raise AccessError(_("Only 'group_pos_user' are allowed to get latest transaction status"))
|
||
|
|
||
|
self.ensure_one()
|
||
|
latest_response = self.sudo().viva_wallet_latest_response
|
||
|
return latest_response
|
||
|
|
||
|
@api.constrains('use_payment_terminal')
|
||
|
def _check_viva_wallet_credentials(self):
|
||
|
for record in self:
|
||
|
if (record.use_payment_terminal == 'viva_wallet'
|
||
|
and not all(record[f] for f in [
|
||
|
'viva_wallet_merchant_id',
|
||
|
'viva_wallet_api_key',
|
||
|
'viva_wallet_client_id',
|
||
|
'viva_wallet_client_secret',
|
||
|
'viva_wallet_terminal_id']
|
||
|
)
|
||
|
):
|
||
|
raise UserError(_('It is essential to provide API key for the use of viva wallet'))
|
||
|
|
||
|
|
||
|
def get_viva_wallet_session():
|
||
|
session = requests.Session()
|
||
|
session.mount('https://', requests.adapters.HTTPAdapter(max_retries=requests.adapters.Retry(
|
||
|
total=6,
|
||
|
backoff_factor=2,
|
||
|
status_forcelist=[202, 500, 502, 503, 504],
|
||
|
)))
|
||
|
return session
|