# Part of Odoo. See LICENSE file for full copyright and licensing details. import logging from odoo import _, api, fields, models from odoo.exceptions import UserError, ValidationError from odoo.addons.fusion_poynt import utils as poynt_utils _logger = logging.getLogger(__name__) class PoyntTerminal(models.Model): _name = 'poynt.terminal' _description = 'Poynt Terminal Device' _order = 'name' name = fields.Char( string="Terminal Name", required=True, ) device_id = fields.Char( string="Device ID", help="The Poynt device identifier (urn:tid:...).", required=True, copy=False, ) serial_number = fields.Char( string="Serial Number", copy=False, ) provider_id = fields.Many2one( 'payment.provider', string="Payment Provider", required=True, ondelete='cascade', domain="[('code', '=', 'poynt')]", ) store_id_poynt = fields.Char( string="Poynt Store ID", help="The Poynt store UUID this terminal belongs to.", ) status = fields.Selection( selection=[ ('online', "Online"), ('offline', "Offline"), ('unknown', "Unknown"), ], string="Status", default='unknown', readonly=True, ) last_seen = fields.Datetime( string="Last Seen", readonly=True, ) active = fields.Boolean( default=True, ) _unique_device_provider = models.Constraint( 'UNIQUE(device_id, provider_id)', 'A terminal with this device ID already exists for this provider.', ) # === BUSINESS METHODS === # def action_refresh_status(self): """Refresh the terminal status from Poynt Cloud.""" for terminal in self: try: store_id = terminal.store_id_poynt or terminal.provider_id.poynt_store_id if store_id: endpoint = f'stores/{store_id}/storeDevices/{terminal.device_id}' else: endpoint = f'storeDevices/{terminal.device_id}' result = terminal.provider_id._poynt_make_request('GET', endpoint) poynt_status = result.get('status', 'UNKNOWN') if poynt_status == 'ACTIVATED': terminal.status = 'online' elif poynt_status in ('DEACTIVATED', 'INACTIVE'): terminal.status = 'offline' else: terminal.status = 'unknown' terminal.last_seen = fields.Datetime.now() except (ValidationError, UserError) as e: _logger.warning( "Failed to refresh status for terminal %s: %s", terminal.device_id, e, ) terminal.status = 'unknown' def action_send_payment_to_terminal(self, amount, currency, reference, order_id=None): """Push a payment request to the physical Poynt terminal. This sends a cloud message to the terminal device instructing it to start a payment collection for the given amount. :param float amount: The payment amount in major currency units. :param recordset currency: The currency record. :param str reference: The Odoo payment reference. :param str order_id: Optional Poynt order UUID to link. :return: The Poynt cloud message response. :rtype: dict :raises UserError: If the terminal is offline. """ self.ensure_one() if self.status == 'offline': raise UserError( _("Terminal '%(name)s' is offline. Please check the device.", name=self.name) ) minor_amount = poynt_utils.format_poynt_amount(amount, currency) payment_request = { 'amount': minor_amount, 'currency': currency.name, 'referenceId': reference, 'callbackUrl': self._get_terminal_callback_url(), 'skipReceiptScreen': False, 'debit': True, } if order_id: payment_request['orderId'] = order_id try: result = self.provider_id._poynt_make_request( 'POST', f'cloudMessages', payload={ 'deviceId': self.device_id, 'ttl': 300, 'serialNum': self.serial_number or '', 'data': { 'action': 'sale', 'purchaseAmount': minor_amount, 'tipAmount': 0, 'currency': currency.name, 'referenceId': reference, 'callbackUrl': self._get_terminal_callback_url(), }, }, ) _logger.info( "Payment request sent to terminal %s for %s %s (ref: %s)", self.device_id, amount, currency.name, reference, ) return result except (ValidationError, UserError) as e: _logger.error( "Failed to send payment to terminal %s: %s", self.device_id, e, ) raise def _get_terminal_callback_url(self): """Build the callback URL for terminal payment completion. :return: The full callback URL. :rtype: str """ base_url = self.provider_id.get_base_url() return f"{base_url}/payment/poynt/terminal/callback" def action_check_terminal_payment_status(self, reference): """Poll for the status of a terminal payment. :param str reference: The Odoo transaction reference. :return: Dict with status and transaction data if completed. :rtype: dict """ self.ensure_one() try: txn_result = self.provider_id._poynt_make_request( 'GET', 'transactions', params={ 'notes': reference, 'limit': 1, }, ) transactions = txn_result.get('transactions', []) if not transactions: return {'status': 'pending', 'message': 'Waiting for terminal response...'} txn = transactions[0] return { 'status': txn.get('status', 'UNKNOWN'), 'transaction_id': txn.get('id', ''), 'funding_source': txn.get('fundingSource', {}), 'amounts': txn.get('amounts', {}), } except (ValidationError, UserError): return {'status': 'error', 'message': 'Failed to check payment status.'}