import logging from odoo import _, fields, http from odoo.http import request from odoo.exceptions import UserError _logger = logging.getLogger(__name__) class FusionRentalController(http.Controller): # ================================================================= # Cancellation (from renewal reminder email) # ================================================================= @http.route( '/rental/cancel/', type='http', auth='public', website=True, methods=['GET', 'POST'], ) def rental_cancel(self, token, **kwargs): """Handle rental cancellation requests from email links.""" cancel_request = request.env['rental.cancellation.request'].sudo().search([ ('token', '=', token), ('state', '=', 'new'), ], limit=1) if not cancel_request: return request.render( 'fusion_rental.cancellation_invalid_page', {'error': _("This cancellation link is invalid or has already been used.")}, ) if request.httprequest.method == 'POST': reason = kwargs.get('reason', '') cancel_request.write({'reason': reason}) cancel_request.action_confirm() return request.render( 'fusion_rental.cancellation_success_page', { 'order': cancel_request.order_id, 'partner': cancel_request.partner_id, }, ) return request.render( 'fusion_rental.cancellation_form_page', { 'order': cancel_request.order_id, 'partner': cancel_request.partner_id, 'token': token, }, ) # ================================================================= # Rental Agreement Signing # ================================================================= @http.route( '/rental/agreement//', type='http', auth='public', website=True, methods=['GET'], ) def rental_agreement_page(self, order_id, token, **kwargs): """Render the rental agreement signing page.""" order = request.env['sale.order'].sudo().browse(order_id) if ( not order.exists() or order.rental_agreement_token != token or order.rental_agreement_signed ): return request.render( 'fusion_rental.cancellation_invalid_page', {'error': _("This agreement link is invalid or has already been signed.")}, ) return request.render( 'fusion_rental.agreement_signing_page', { 'order': order, 'partner': order.partner_id, 'token': token, 'pdf_preview_url': f'/rental/agreement/{order_id}/{token}/pdf', }, ) @http.route( '/rental/agreement///pdf', type='http', auth='public', methods=['GET'], ) def rental_agreement_pdf(self, order_id, token, **kwargs): """Serve the rental agreement PDF for preview (token-protected).""" order = request.env['sale.order'].sudo().browse(order_id) if not order.exists() or order.rental_agreement_token != token: return request.not_found() report_name = 'fusion_rental.report_rental_agreement' report = request.env['ir.actions.report'].sudo()._get_report_from_name(report_name) if not report: report = request.env['ir.actions.report'].sudo()._get_report_from_name( 'fusion_claims.report_rental_agreement' ) report_name = 'fusion_claims.report_rental_agreement' if not report: return request.not_found() pdf_content, _report_type = report.sudo()._render_qweb_pdf( report_name, [order.id] ) return request.make_response( pdf_content, headers=[ ('Content-Type', 'application/pdf'), ('Content-Disposition', f'inline; filename="Rental Agreement - {order.name}.pdf"'), ], ) @http.route( '/rental/agreement///sign', type='json', auth='public', methods=['POST'], ) def rental_agreement_sign(self, order_id, token, **kwargs): """Process the agreement signing: save signature and tokenize card.""" order = request.env['sale.order'].sudo().browse(order_id) if ( not order.exists() or order.rental_agreement_token != token or order.rental_agreement_signed ): return {'success': False, 'error': 'Invalid or expired agreement link.'} signer_name = kwargs.get('signer_name', '').strip() signature_data = kwargs.get('signature_data', '') card_number = kwargs.get('card_number', '').replace(' ', '') exp_month = kwargs.get('exp_month', '') exp_year = kwargs.get('exp_year', '') cvv = kwargs.get('cvv', '') cardholder_name = kwargs.get('cardholder_name', '').strip() if not signer_name: return {'success': False, 'error': 'Full name is required.'} if not signature_data: return {'success': False, 'error': 'Signature is required.'} if not card_number or len(card_number) < 13: return {'success': False, 'error': 'Valid card number is required.'} if not exp_month or not exp_year: return {'success': False, 'error': 'Card expiry is required.'} if not cvv: return {'success': False, 'error': 'CVV is required.'} sig_binary = signature_data if ',' in sig_binary: sig_binary = sig_binary.split(',')[1] try: payment_token = self._tokenize_card_via_poynt( order, card_number, exp_month, exp_year, cvv, cardholder_name, ) except (UserError, Exception) as e: _logger.error("Card tokenization failed for %s: %s", order.name, e) return {'success': False, 'error': str(e)} order.write({ 'rental_agreement_signed': True, 'rental_agreement_signature': sig_binary, 'rental_agreement_signer_name': signer_name, 'rental_agreement_signed_date': fields.Datetime.now(), 'rental_payment_token_id': payment_token.id if payment_token else False, }) order.message_post( body=_("Rental agreement signed by %s.", signer_name), ) return { 'success': True, 'message': 'Agreement signed successfully. Thank you!', } def _tokenize_card_via_poynt( self, order, card_number, exp_month, exp_year, cvv, cardholder_name, ): """Tokenize a card through the Poynt API and create a payment.token.""" provider = request.env['payment.provider'].sudo().search([ ('code', '=', 'poynt'), ('state', '!=', 'disabled'), ], limit=1) if not provider: raise UserError(_("Poynt payment provider is not configured.")) from odoo.addons.fusion_poynt import utils as poynt_utils funding_source = { 'type': 'CREDIT_DEBIT', 'card': { 'number': card_number, 'expirationMonth': int(exp_month), 'expirationYear': int(exp_year), 'cardHolderFullName': cardholder_name, }, 'verificationData': { 'cvData': cvv, }, 'entryDetails': { 'customerPresenceStatus': 'MOTO', 'entryMode': 'KEYED', }, } minor_amount = poynt_utils.format_poynt_amount(0.00, order.currency_id) txn_payload = { 'action': 'VERIFY', 'amounts': { 'transactionAmount': minor_amount, 'orderAmount': minor_amount, 'currency': order.currency_id.name, }, 'fundingSource': funding_source, 'context': { 'source': 'WEB', 'sourceApp': 'odoo.fusion_rental', 'businessId': provider.poynt_business_id, }, 'notes': f"Card tokenization for {order.name}", } result = provider._poynt_make_request('POST', 'transactions', payload=txn_payload) card_data = result.get('fundingSource', {}).get('card', {}) card_id = card_data.get('cardId', '') last_four = card_data.get('numberLast4', card_number[-4:]) card_type = card_data.get('type', 'UNKNOWN') payment_method = request.env['payment.method'].sudo().search( [('code', '=', 'card')], limit=1, ) if not payment_method: payment_method = request.env['payment.method'].sudo().search( [('code', 'in', ('visa', 'mastercard'))], limit=1, ) token = request.env['payment.token'].sudo().create({ 'provider_id': provider.id, 'payment_method_id': payment_method.id if payment_method else False, 'partner_id': order.partner_id.id, 'poynt_card_id': card_id, 'payment_details': f"{card_type} ending in {last_four}", }) return token # ================================================================= # Purchase Interest (from marketing email) # ================================================================= @http.route( '/rental/purchase-interest//', type='http', auth='public', website=True, methods=['GET'], ) def rental_purchase_interest(self, order_id, token, **kwargs): """Handle customer expressing purchase interest from marketing email.""" order = request.env['sale.order'].sudo().browse(order_id) if ( not order.exists() or order.rental_agreement_token != token ): return request.render( 'fusion_rental.cancellation_invalid_page', {'error': _("This link is invalid.")}, ) if not order.rental_purchase_interest: order.rental_purchase_interest = True order.activity_schedule( 'mail.mail_activity_data_todo', date_deadline=fields.Date.today(), summary=_("Customer interested in purchasing rental product"), note=_( "Customer %s expressed interest in purchasing the rental " "product from order %s. Please follow up.", order.partner_id.name, order.name, ), user_id=order.user_id.id or request.env.uid, ) order.message_post( body=_("Customer expressed purchase interest via marketing email."), ) return request.render( 'fusion_rental.purchase_interest_success_page', {'order': order, 'partner': order.partner_id}, )