changes
This commit is contained in:
@@ -467,7 +467,9 @@ class PoyntController(http.Controller):
|
|||||||
def poynt_process_card(self, reference=None, poynt_order_id=None,
|
def poynt_process_card(self, reference=None, poynt_order_id=None,
|
||||||
card_number=None, exp_month=None, exp_year=None,
|
card_number=None, exp_month=None, exp_year=None,
|
||||||
cvv=None, cardholder_name=None, card_type=None,
|
cvv=None, cardholder_name=None, card_type=None,
|
||||||
**kwargs):
|
billing_address=None, billing_city=None,
|
||||||
|
billing_state=None, billing_zip=None,
|
||||||
|
billing_country=None, **kwargs):
|
||||||
"""Process a card payment through Poynt Cloud API.
|
"""Process a card payment through Poynt Cloud API.
|
||||||
|
|
||||||
The frontend sends card details which are passed to Poynt for
|
The frontend sends card details which are passed to Poynt for
|
||||||
@@ -503,6 +505,13 @@ class PoyntController(http.Controller):
|
|||||||
},
|
},
|
||||||
'verificationData': {
|
'verificationData': {
|
||||||
'cvData': cvv,
|
'cvData': cvv,
|
||||||
|
'cardHolderBillingAddress': {
|
||||||
|
'line1': billing_address or '',
|
||||||
|
'city': billing_city or '',
|
||||||
|
'territory': billing_state or '',
|
||||||
|
'postalCode': billing_zip or '',
|
||||||
|
'countryCode': billing_country or '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
'entryDetails': {
|
'entryDetails': {
|
||||||
'customerPresenceStatus': 'ECOMMERCE',
|
'customerPresenceStatus': 'ECOMMERCE',
|
||||||
@@ -510,13 +519,25 @@ class PoyntController(http.Controller):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
action = 'AUTHORIZE' if tx_sudo.provider_id.capture_manually else 'SALE'
|
provider = tx_sudo.provider_id.sudo()
|
||||||
|
action = 'AUTHORIZE' if provider.capture_manually else 'SALE'
|
||||||
minor_amount = poynt_utils.format_poynt_amount(
|
minor_amount = poynt_utils.format_poynt_amount(
|
||||||
tx_sudo.amount, tx_sudo.currency_id,
|
tx_sudo.amount, tx_sudo.currency_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'source': 'WEB',
|
||||||
|
'sourceApp': 'odoo.fusion_poynt',
|
||||||
|
'transactionInstruction': 'ONLINE_AUTH_REQUIRED',
|
||||||
|
}
|
||||||
|
if provider.poynt_business_id:
|
||||||
|
context['businessId'] = provider.poynt_business_id
|
||||||
|
if provider.poynt_store_id:
|
||||||
|
context['storeId'] = provider.poynt_store_id
|
||||||
|
|
||||||
txn_payload = {
|
txn_payload = {
|
||||||
'action': action,
|
'action': action,
|
||||||
|
'fundingSourceType': 'CREDIT_DEBIT',
|
||||||
'amounts': {
|
'amounts': {
|
||||||
'transactionAmount': minor_amount,
|
'transactionAmount': minor_amount,
|
||||||
'orderAmount': minor_amount,
|
'orderAmount': minor_amount,
|
||||||
@@ -525,11 +546,7 @@ class PoyntController(http.Controller):
|
|||||||
'currency': tx_sudo.currency_id.name,
|
'currency': tx_sudo.currency_id.name,
|
||||||
},
|
},
|
||||||
'fundingSource': funding_source,
|
'fundingSource': funding_source,
|
||||||
'context': {
|
'context': context,
|
||||||
'source': 'WEB',
|
|
||||||
'sourceApp': 'odoo.fusion_poynt',
|
|
||||||
'transactionInstruction': 'ONLINE_AUTH_REQUIRED',
|
|
||||||
},
|
|
||||||
'notes': reference,
|
'notes': reference,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -539,7 +556,7 @@ class PoyntController(http.Controller):
|
|||||||
'type': 'POYNT_ORDER',
|
'type': 'POYNT_ORDER',
|
||||||
}]
|
}]
|
||||||
|
|
||||||
result = tx_sudo.provider_id._poynt_make_request(
|
result = provider._poynt_make_request(
|
||||||
'POST', 'transactions', payload=txn_payload,
|
'POST', 'transactions', payload=txn_payload,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ class AccountMove(models.Model):
|
|||||||
string="Has Poynt Receipt",
|
string="Has Poynt Receipt",
|
||||||
compute='_compute_has_poynt_receipt',
|
compute='_compute_has_poynt_receipt',
|
||||||
)
|
)
|
||||||
|
poynt_transaction_count = fields.Integer(
|
||||||
|
string="Poynt Transactions",
|
||||||
|
compute='_compute_poynt_transaction_count',
|
||||||
|
)
|
||||||
|
|
||||||
@api.depends('reversal_move_ids')
|
@api.depends('reversal_move_ids')
|
||||||
def _compute_poynt_refund_count(self):
|
def _compute_poynt_refund_count(self):
|
||||||
@@ -38,6 +42,33 @@ class AccountMove(models.Model):
|
|||||||
for move in self:
|
for move in self:
|
||||||
move.has_poynt_receipt = bool(move._get_poynt_transaction_for_receipt())
|
move.has_poynt_receipt = bool(move._get_poynt_transaction_for_receipt())
|
||||||
|
|
||||||
|
def _compute_poynt_transaction_count(self):
|
||||||
|
for move in self:
|
||||||
|
move.poynt_transaction_count = self.env['payment.transaction'].sudo().search_count([
|
||||||
|
('invoice_ids', 'in', move.id),
|
||||||
|
('provider_code', '=', 'poynt'),
|
||||||
|
])
|
||||||
|
|
||||||
|
def action_view_poynt_transactions(self):
|
||||||
|
"""Open payment transactions linked to this invoice/credit note."""
|
||||||
|
self.ensure_one()
|
||||||
|
transactions = self.env['payment.transaction'].sudo().search([
|
||||||
|
('invoice_ids', 'in', self.id),
|
||||||
|
('provider_code', '=', 'poynt'),
|
||||||
|
])
|
||||||
|
action = {
|
||||||
|
'name': _("Poynt Transactions"),
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'res_model': 'payment.transaction',
|
||||||
|
'domain': [('id', 'in', transactions.ids)],
|
||||||
|
}
|
||||||
|
if len(transactions) == 1:
|
||||||
|
action['view_mode'] = 'form'
|
||||||
|
action['res_id'] = transactions.id
|
||||||
|
else:
|
||||||
|
action['view_mode'] = 'list,form'
|
||||||
|
return action
|
||||||
|
|
||||||
def action_view_poynt_refunds(self):
|
def action_view_poynt_refunds(self):
|
||||||
"""Open the credit notes linked to this invoice that were refunded via Poynt."""
|
"""Open the credit notes linked to this invoice that were refunded via Poynt."""
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ class PaymentTransaction(models.Model):
|
|||||||
|
|
||||||
For direct (online) payments we create a Poynt order upfront and return
|
For direct (online) payments we create a Poynt order upfront and return
|
||||||
identifiers plus the return URL so the frontend JS can complete the flow.
|
identifiers plus the return URL so the frontend JS can complete the flow.
|
||||||
|
The actual transaction is created later when the frontend sends card
|
||||||
|
details via the /payment/poynt/process_card route.
|
||||||
"""
|
"""
|
||||||
if self.provider_code != 'poynt':
|
if self.provider_code != 'poynt':
|
||||||
return super()._get_specific_processing_values(processing_values)
|
return super()._get_specific_processing_values(processing_values)
|
||||||
@@ -65,7 +67,7 @@ class PaymentTransaction(models.Model):
|
|||||||
if self.operation == 'online_token':
|
if self.operation == 'online_token':
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
poynt_data = self._poynt_create_order_and_authorize()
|
order_data = self._poynt_create_order()
|
||||||
|
|
||||||
provider = self._get_provider_sudo()
|
provider = self._get_provider_sudo()
|
||||||
base_url = provider.get_base_url()
|
base_url = provider.get_base_url()
|
||||||
@@ -75,8 +77,7 @@ class PaymentTransaction(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'poynt_order_id': poynt_data.get('order_id', ''),
|
'poynt_order_id': order_data.get('order_id', ''),
|
||||||
'poynt_transaction_id': poynt_data.get('transaction_id', ''),
|
|
||||||
'return_url': return_url,
|
'return_url': return_url,
|
||||||
'business_id': provider.poynt_business_id,
|
'business_id': provider.poynt_business_id,
|
||||||
'is_test': provider.state == 'test',
|
'is_test': provider.state == 'test',
|
||||||
@@ -108,6 +109,33 @@ class PaymentTransaction(models.Model):
|
|||||||
return
|
return
|
||||||
self._process('poynt', payment_data)
|
self._process('poynt', payment_data)
|
||||||
|
|
||||||
|
def _poynt_create_order(self):
|
||||||
|
"""Create a Poynt order without a transaction.
|
||||||
|
|
||||||
|
Used by the portal payment flow where card details are collected
|
||||||
|
on the frontend and the transaction is created separately via
|
||||||
|
the /payment/poynt/process_card route.
|
||||||
|
|
||||||
|
:return: Dict with order_id.
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
provider = self._get_provider_sudo()
|
||||||
|
order_payload = poynt_utils.build_order_payload(
|
||||||
|
self.reference, self.amount, self.currency_id,
|
||||||
|
business_id=provider.poynt_business_id,
|
||||||
|
store_id=provider.poynt_store_id or '',
|
||||||
|
)
|
||||||
|
order_result = provider._poynt_make_request(
|
||||||
|
'POST', 'orders', payload=order_payload,
|
||||||
|
)
|
||||||
|
order_id = order_result.get('id', '')
|
||||||
|
self.poynt_order_id = order_id
|
||||||
|
return {'order_id': order_id}
|
||||||
|
except ValidationError as e:
|
||||||
|
self._set_error(str(e))
|
||||||
|
return {}
|
||||||
|
|
||||||
def _poynt_create_order_and_authorize(self):
|
def _poynt_create_order_and_authorize(self):
|
||||||
"""Create a Poynt order and authorize the transaction.
|
"""Create a Poynt order and authorize the transaction.
|
||||||
|
|
||||||
@@ -383,6 +411,7 @@ class PaymentTransaction(models.Model):
|
|||||||
try:
|
try:
|
||||||
refund_payload = {
|
refund_payload = {
|
||||||
'action': 'REFUND',
|
'action': 'REFUND',
|
||||||
|
'fundingSourceType': 'CREDIT_DEBIT',
|
||||||
'parentId': parent_txn_id,
|
'parentId': parent_txn_id,
|
||||||
'fundingSource': {
|
'fundingSource': {
|
||||||
'type': 'CREDIT_DEBIT',
|
'type': 'CREDIT_DEBIT',
|
||||||
@@ -426,6 +455,7 @@ class PaymentTransaction(models.Model):
|
|||||||
try:
|
try:
|
||||||
capture_payload = {
|
capture_payload = {
|
||||||
'action': 'CAPTURE',
|
'action': 'CAPTURE',
|
||||||
|
'fundingSourceType': 'CREDIT_DEBIT',
|
||||||
'parentId': source_tx.provider_reference,
|
'parentId': source_tx.provider_reference,
|
||||||
'amounts': {
|
'amounts': {
|
||||||
'transactionAmount': minor_amount,
|
'transactionAmount': minor_amount,
|
||||||
@@ -817,6 +847,7 @@ class PaymentTransaction(models.Model):
|
|||||||
self._poynt_store_receipt_data(payment_data)
|
self._poynt_store_receipt_data(payment_data)
|
||||||
self._poynt_attach_receipt_pdf()
|
self._poynt_attach_receipt_pdf()
|
||||||
self._poynt_attach_poynt_receipt()
|
self._poynt_attach_poynt_receipt()
|
||||||
|
self._poynt_auto_send_invoice_and_receipt()
|
||||||
except Exception:
|
except Exception:
|
||||||
_logger.exception(
|
_logger.exception(
|
||||||
"Receipt generation failed for transaction %s", self.reference,
|
"Receipt generation failed for transaction %s", self.reference,
|
||||||
@@ -925,6 +956,64 @@ class PaymentTransaction(models.Model):
|
|||||||
'mimetype': 'text/html',
|
'mimetype': 'text/html',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def _poynt_auto_send_invoice_and_receipt(self):
|
||||||
|
"""Automatically email the invoice and payment receipt to the customer
|
||||||
|
after a successful payment.
|
||||||
|
|
||||||
|
1. Sends the invoice via the standard Odoo invoice email template.
|
||||||
|
2. Sends the Poynt payment receipt email with the PDF attached.
|
||||||
|
|
||||||
|
Best-effort: failures are logged but never block the payment flow.
|
||||||
|
"""
|
||||||
|
self.ensure_one()
|
||||||
|
invoice = self.invoice_ids[:1]
|
||||||
|
partner = self.partner_id
|
||||||
|
|
||||||
|
if not partner.email:
|
||||||
|
_logger.info(
|
||||||
|
"Skipping auto-send for %s: partner %s has no email.",
|
||||||
|
self.reference, partner.display_name,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 1. Send the invoice PDF
|
||||||
|
if invoice and invoice.state == 'posted':
|
||||||
|
try:
|
||||||
|
inv_template = self.env.ref(
|
||||||
|
'account.email_template_edi_invoice',
|
||||||
|
raise_if_not_found=False,
|
||||||
|
)
|
||||||
|
if inv_template:
|
||||||
|
inv_template.sudo().send_mail(
|
||||||
|
invoice.id, force_send=True,
|
||||||
|
)
|
||||||
|
invoice.sudo().write({'is_move_sent': True})
|
||||||
|
_logger.info(
|
||||||
|
"Auto-sent invoice %s to %s",
|
||||||
|
invoice.name, partner.email,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
_logger.exception(
|
||||||
|
"Failed to auto-send invoice %s", invoice.name,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 2. Send the payment receipt
|
||||||
|
try:
|
||||||
|
receipt_template = self.env.ref(
|
||||||
|
'fusion_poynt.mail_template_poynt_receipt',
|
||||||
|
raise_if_not_found=False,
|
||||||
|
)
|
||||||
|
if receipt_template:
|
||||||
|
receipt_template.sudo().send_mail(self.id, force_send=True)
|
||||||
|
_logger.info(
|
||||||
|
"Auto-sent payment receipt for %s to %s",
|
||||||
|
self.reference, partner.email,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
_logger.exception(
|
||||||
|
"Failed to auto-send receipt for %s", self.reference,
|
||||||
|
)
|
||||||
|
|
||||||
def _get_poynt_receipt_values(self):
|
def _get_poynt_receipt_values(self):
|
||||||
"""Parse the stored receipt JSON for use in QWeb templates.
|
"""Parse the stored receipt JSON for use in QWeb templates.
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from odoo import _, models
|
from odoo import _, api, fields, models
|
||||||
from odoo.exceptions import UserError
|
from odoo.exceptions import UserError
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
@@ -11,6 +11,37 @@ _logger = logging.getLogger(__name__)
|
|||||||
class SaleOrder(models.Model):
|
class SaleOrder(models.Model):
|
||||||
_inherit = 'sale.order'
|
_inherit = 'sale.order'
|
||||||
|
|
||||||
|
poynt_transaction_count = fields.Integer(
|
||||||
|
string="Poynt Transactions",
|
||||||
|
compute='_compute_poynt_transaction_count',
|
||||||
|
)
|
||||||
|
|
||||||
|
def _compute_poynt_transaction_count(self):
|
||||||
|
for order in self:
|
||||||
|
order.poynt_transaction_count = self.env['payment.transaction'].sudo().search_count([
|
||||||
|
('sale_order_ids', 'in', order.id),
|
||||||
|
('provider_code', '=', 'poynt'),
|
||||||
|
])
|
||||||
|
|
||||||
|
def action_view_poynt_transactions(self):
|
||||||
|
self.ensure_one()
|
||||||
|
transactions = self.env['payment.transaction'].sudo().search([
|
||||||
|
('sale_order_ids', 'in', self.id),
|
||||||
|
('provider_code', '=', 'poynt'),
|
||||||
|
])
|
||||||
|
action = {
|
||||||
|
'name': _("Poynt Transactions"),
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'res_model': 'payment.transaction',
|
||||||
|
'domain': [('id', 'in', transactions.ids)],
|
||||||
|
}
|
||||||
|
if len(transactions) == 1:
|
||||||
|
action['view_mode'] = 'form'
|
||||||
|
action['res_id'] = transactions.id
|
||||||
|
else:
|
||||||
|
action['view_mode'] = 'list,form'
|
||||||
|
return action
|
||||||
|
|
||||||
def action_poynt_collect_payment(self):
|
def action_poynt_collect_payment(self):
|
||||||
"""Create an invoice (if needed) and open the Poynt payment wizard.
|
"""Create an invoice (if needed) and open the Poynt payment wizard.
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,22 @@ patch(PaymentForm.prototype, {
|
|||||||
this._setupCardFormatting(poyntContainer);
|
this._setupCardFormatting(poyntContainer);
|
||||||
this._setupTerminalToggle(poyntContainer);
|
this._setupTerminalToggle(poyntContainer);
|
||||||
this._setupSurcharge(poyntContainer);
|
this._setupSurcharge(poyntContainer);
|
||||||
|
this._prefillBillingAddress(poyntContainer);
|
||||||
|
},
|
||||||
|
|
||||||
|
_prefillBillingAddress(container) {
|
||||||
|
const billing = this.poyntFormData.billing_details;
|
||||||
|
if (!billing || !billing.address) return;
|
||||||
|
const addr = billing.address;
|
||||||
|
const setVal = (id, val) => {
|
||||||
|
const el = container.querySelector(id);
|
||||||
|
if (el && val) el.value = val;
|
||||||
|
};
|
||||||
|
setVal('#poynt_billing_address', addr.line1);
|
||||||
|
setVal('#poynt_billing_city', addr.city);
|
||||||
|
setVal('#poynt_billing_state', addr.state);
|
||||||
|
setVal('#poynt_billing_zip', addr.postal_code);
|
||||||
|
setVal('#poynt_billing_country', addr.country);
|
||||||
},
|
},
|
||||||
|
|
||||||
_detectCardBrand(number) {
|
_detectCardBrand(number) {
|
||||||
@@ -317,6 +333,36 @@ patch(PaymentForm.prototype, {
|
|||||||
return checked ? checked.value : 'other';
|
return checked ? checked.value : 'other';
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_showProcessingOverlay(container) {
|
||||||
|
const overlay = container.querySelector('.o_poynt_processing_overlay');
|
||||||
|
if (overlay) {
|
||||||
|
// Hide all form field sections
|
||||||
|
Array.from(container.children).forEach(child => {
|
||||||
|
if (!child.classList.contains('o_poynt_processing_overlay')) {
|
||||||
|
child.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
overlay.style.display = 'block';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_hideProcessingOverlay(container) {
|
||||||
|
const overlay = container.querySelector('.o_poynt_processing_overlay');
|
||||||
|
if (overlay) {
|
||||||
|
overlay.style.display = 'none';
|
||||||
|
Array.from(container.children).forEach(child => {
|
||||||
|
if (!child.classList.contains('o_poynt_processing_overlay')) {
|
||||||
|
child.style.display = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateProcessingMessage(container, message) {
|
||||||
|
const msgEl = container.querySelector('.o_poynt_processing_message');
|
||||||
|
if (msgEl) msgEl.textContent = message;
|
||||||
|
},
|
||||||
|
|
||||||
async _processCardPayment(processingValues, inlineForm) {
|
async _processCardPayment(processingValues, inlineForm) {
|
||||||
const cardNumber = inlineForm.querySelector('#poynt_card_number').value.replace(/\D/g, '');
|
const cardNumber = inlineForm.querySelector('#poynt_card_number').value.replace(/\D/g, '');
|
||||||
const expiry = inlineForm.querySelector('#poynt_expiry').value;
|
const expiry = inlineForm.querySelector('#poynt_expiry').value;
|
||||||
@@ -328,6 +374,13 @@ patch(PaymentForm.prototype, {
|
|||||||
|
|
||||||
const [expMonth, expYear] = expiry.split('/').map(Number);
|
const [expMonth, expYear] = expiry.split('/').map(Number);
|
||||||
|
|
||||||
|
const formContainer = inlineForm.closest('.o_poynt_payment_form')
|
||||||
|
|| inlineForm.querySelector('.o_poynt_payment_form')
|
||||||
|
|| inlineForm;
|
||||||
|
|
||||||
|
// Show processing animation
|
||||||
|
this._showProcessingOverlay(formContainer);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await rpc('/payment/poynt/process_card', {
|
const result = await rpc('/payment/poynt/process_card', {
|
||||||
reference: processingValues.reference,
|
reference: processingValues.reference,
|
||||||
@@ -338,9 +391,15 @@ patch(PaymentForm.prototype, {
|
|||||||
cvv: cvv,
|
cvv: cvv,
|
||||||
cardholder_name: cardholder,
|
cardholder_name: cardholder,
|
||||||
card_type: cardType,
|
card_type: cardType,
|
||||||
|
billing_address: inlineForm.querySelector('#poynt_billing_address')?.value || '',
|
||||||
|
billing_city: inlineForm.querySelector('#poynt_billing_city')?.value || '',
|
||||||
|
billing_state: inlineForm.querySelector('#poynt_billing_state')?.value || '',
|
||||||
|
billing_zip: inlineForm.querySelector('#poynt_billing_zip')?.value || '',
|
||||||
|
billing_country: inlineForm.querySelector('#poynt_billing_country')?.value || '',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
|
this._hideProcessingOverlay(formContainer);
|
||||||
this._displayErrorDialog(
|
this._displayErrorDialog(
|
||||||
_t("Payment Failed"),
|
_t("Payment Failed"),
|
||||||
result.error,
|
result.error,
|
||||||
@@ -349,8 +408,10 @@ patch(PaymentForm.prototype, {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._updateProcessingMessage(formContainer, _t("Payment successful! Redirecting..."));
|
||||||
window.location.href = processingValues.return_url;
|
window.location.href = processingValues.return_url;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
this._hideProcessingOverlay(formContainer);
|
||||||
this._displayErrorDialog(
|
this._displayErrorDialog(
|
||||||
_t("Payment Processing Error"),
|
_t("Payment Processing Error"),
|
||||||
error.message || _t("An unexpected error occurred."),
|
error.message || _t("An unexpected error occurred."),
|
||||||
|
|||||||
@@ -269,6 +269,7 @@ def build_transaction_payload(
|
|||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
'action': action,
|
'action': action,
|
||||||
|
'fundingSourceType': 'CREDIT_DEBIT',
|
||||||
'amounts': {
|
'amounts': {
|
||||||
'transactionAmount': minor_amount,
|
'transactionAmount': minor_amount,
|
||||||
'orderAmount': minor_amount,
|
'orderAmount': minor_amount,
|
||||||
@@ -318,6 +319,7 @@ def build_token_charge_payload(
|
|||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
'action': action,
|
'action': action,
|
||||||
|
'fundingSourceType': 'CREDIT_DEBIT',
|
||||||
'context': context,
|
'context': context,
|
||||||
'amounts': {
|
'amounts': {
|
||||||
'transactionAmount': minor_amount,
|
'transactionAmount': minor_amount,
|
||||||
|
|||||||
@@ -8,8 +8,15 @@
|
|||||||
<field name="priority">60</field>
|
<field name="priority">60</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
|
|
||||||
<!-- Poynt Refund smart button on invoices -->
|
<!-- Poynt Transaction smart button -->
|
||||||
<xpath expr="//div[@name='button_box']" position="inside">
|
<xpath expr="//div[@name='button_box']" position="inside">
|
||||||
|
<button name="action_view_poynt_transactions"
|
||||||
|
type="object"
|
||||||
|
class="oe_stat_button"
|
||||||
|
icon="fa-credit-card"
|
||||||
|
invisible="poynt_transaction_count == 0">
|
||||||
|
<field name="poynt_transaction_count" widget="statinfo" string="Poynt Payments"/>
|
||||||
|
</button>
|
||||||
<button name="action_view_poynt_refunds"
|
<button name="action_view_poynt_refunds"
|
||||||
type="object"
|
type="object"
|
||||||
class="oe_stat_button"
|
class="oe_stat_button"
|
||||||
|
|||||||
@@ -62,6 +62,51 @@
|
|||||||
autocomplete="cc-name"/>
|
autocomplete="cc-name"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Billing address -->
|
||||||
|
<div class="mb-3 o_poynt_billing_section">
|
||||||
|
<label class="form-label fw-bold">Billing Address</label>
|
||||||
|
<div class="mb-2">
|
||||||
|
<input type="text" class="form-control"
|
||||||
|
id="poynt_billing_address"
|
||||||
|
name="billing_address"
|
||||||
|
placeholder="Street Address"
|
||||||
|
autocomplete="billing street-address"/>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-6">
|
||||||
|
<input type="text" class="form-control"
|
||||||
|
id="poynt_billing_city"
|
||||||
|
name="billing_city"
|
||||||
|
placeholder="City"
|
||||||
|
autocomplete="billing address-level2"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<input type="text" class="form-control"
|
||||||
|
id="poynt_billing_state"
|
||||||
|
name="billing_state"
|
||||||
|
placeholder="Province / State"
|
||||||
|
autocomplete="billing address-level1"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-6">
|
||||||
|
<input type="text" class="form-control"
|
||||||
|
id="poynt_billing_zip"
|
||||||
|
name="billing_zip"
|
||||||
|
placeholder="Postal Code"
|
||||||
|
autocomplete="billing postal-code"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<input type="text" class="form-control"
|
||||||
|
id="poynt_billing_country"
|
||||||
|
name="billing_country"
|
||||||
|
placeholder="Country Code (CA, US)"
|
||||||
|
maxlength="2"
|
||||||
|
autocomplete="billing country"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Card type selector (for terminal payments where card brand cannot be auto-detected) -->
|
<!-- Card type selector (for terminal payments where card brand cannot be auto-detected) -->
|
||||||
<div class="mb-3 o_poynt_card_type_section" style="display:none;">
|
<div class="mb-3 o_poynt_card_type_section" style="display:none;">
|
||||||
<label class="form-label">Card Type</label>
|
<label class="form-label">Card Type</label>
|
||||||
@@ -117,6 +162,17 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Processing overlay (hidden until payment is submitted) -->
|
||||||
|
<div class="o_poynt_processing_overlay" style="display:none;">
|
||||||
|
<div class="text-center p-4">
|
||||||
|
<div class="spinner-border text-primary mb-3" role="status">
|
||||||
|
<span class="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
<h5 class="o_poynt_processing_message">Processing your payment...</h5>
|
||||||
|
<p class="text-muted">Please do not close this page.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -28,10 +28,16 @@
|
|||||||
confirm="Are you sure you want to void this transaction? This reverses the payment before settlement and cannot be undone. Only works same-day before closeout (6 PM)."/>
|
confirm="Are you sure you want to void this transaction? This reverses the payment before settlement and cannot be undone. Only works same-day before closeout (6 PM)."/>
|
||||||
</xpath>
|
</xpath>
|
||||||
|
|
||||||
<!-- Add voided fields to the form sheet -->
|
<!-- Add Poynt fields and linked records after provider_reference -->
|
||||||
<xpath expr="//field[@name='provider_reference']" position="after">
|
<xpath expr="//field[@name='provider_reference']" position="after">
|
||||||
<field name="poynt_voided" invisible="1"/>
|
<field name="poynt_voided" invisible="1"/>
|
||||||
<field name="poynt_void_date" invisible="not poynt_voided"/>
|
<field name="poynt_void_date" invisible="not poynt_voided"/>
|
||||||
|
<field name="poynt_order_id" invisible="provider_code != 'poynt'" readonly="1"/>
|
||||||
|
<field name="poynt_transaction_id" invisible="provider_code != 'poynt'" readonly="1"/>
|
||||||
|
<field name="sale_order_ids" widget="many2many_tags" readonly="1"
|
||||||
|
string="Sale Orders"/>
|
||||||
|
<field name="invoice_ids" widget="many2many_tags" readonly="1"
|
||||||
|
string="Invoices"/>
|
||||||
</xpath>
|
</xpath>
|
||||||
|
|
||||||
</field>
|
</field>
|
||||||
|
|||||||
@@ -7,6 +7,18 @@
|
|||||||
<field name="inherit_id" ref="sale.view_order_form"/>
|
<field name="inherit_id" ref="sale.view_order_form"/>
|
||||||
<field name="priority">60</field>
|
<field name="priority">60</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
|
<!-- Transaction count smart button -->
|
||||||
|
<xpath expr="//div[@name='button_box']" position="inside">
|
||||||
|
<button name="action_view_poynt_transactions"
|
||||||
|
type="object"
|
||||||
|
class="oe_stat_button"
|
||||||
|
icon="fa-credit-card"
|
||||||
|
invisible="poynt_transaction_count == 0">
|
||||||
|
<field name="poynt_transaction_count" widget="statinfo" string="Poynt Payments"/>
|
||||||
|
</button>
|
||||||
|
</xpath>
|
||||||
|
|
||||||
|
<!-- Collect Payment action button -->
|
||||||
<xpath expr="//button[@id='create_invoice']" position="after">
|
<xpath expr="//button[@id='create_invoice']" position="after">
|
||||||
<button name="action_poynt_collect_payment"
|
<button name="action_poynt_collect_payment"
|
||||||
string="Collect Payment"
|
string="Collect Payment"
|
||||||
|
|||||||
@@ -409,6 +409,7 @@ class PoyntPaymentWizard(models.TransientModel):
|
|||||||
|
|
||||||
txn_payload = {
|
txn_payload = {
|
||||||
'action': action,
|
'action': action,
|
||||||
|
'fundingSourceType': 'CREDIT_DEBIT',
|
||||||
'amounts': {
|
'amounts': {
|
||||||
'transactionAmount': minor_amount,
|
'transactionAmount': minor_amount,
|
||||||
'orderAmount': minor_amount,
|
'orderAmount': minor_amount,
|
||||||
|
|||||||
@@ -314,6 +314,7 @@ class PoyntRefundWizard(models.TransientModel):
|
|||||||
|
|
||||||
refund_payload = {
|
refund_payload = {
|
||||||
'action': 'REFUND',
|
'action': 'REFUND',
|
||||||
|
'fundingSourceType': 'CREDIT_DEBIT',
|
||||||
'parentId': parent_txn_id,
|
'parentId': parent_txn_id,
|
||||||
'fundingSource': {
|
'fundingSource': {
|
||||||
'type': 'CREDIT_DEBIT',
|
'type': 'CREDIT_DEBIT',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
access_renewal_log_user,rental.renewal.log.user,model_rental_renewal_log,sales_team.group_sale_salesman,1,0,0,0
|
access_renewal_log_user,rental.renewal.log.user,model_rental_renewal_log,sales_team.group_sale_salesman,1,1,1,0
|
||||||
access_renewal_log_manager,rental.renewal.log.manager,model_rental_renewal_log,fusion_rental.group_rental_manager,1,1,1,1
|
access_renewal_log_manager,rental.renewal.log.manager,model_rental_renewal_log,fusion_rental.group_rental_manager,1,1,1,1
|
||||||
access_cancellation_request_user,rental.cancellation.request.user,model_rental_cancellation_request,sales_team.group_sale_salesman,1,1,1,0
|
access_cancellation_request_user,rental.cancellation.request.user,model_rental_cancellation_request,sales_team.group_sale_salesman,1,1,1,0
|
||||||
access_cancellation_request_manager,rental.cancellation.request.manager,model_rental_cancellation_request,fusion_rental.group_rental_manager,1,1,1,1
|
access_cancellation_request_manager,rental.cancellation.request.manager,model_rental_cancellation_request,fusion_rental.group_rental_manager,1,1,1,1
|
||||||
|
|||||||
|
Reference in New Issue
Block a user