feat: hide authorizer for rental orders, auto-set sale type
Rental orders no longer show the "Authorizer Required?" question or the Authorizer field. The sale type is automatically set to 'Rentals' when creating or confirming a rental order. Validation logic also skips authorizer checks for rental sale type. Made-with: Cursor
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
'description': " ",
|
||||
'depends': ['payment', 'account_payment', 'sale'],
|
||||
'data': [
|
||||
'security/security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
|
||||
'report/poynt_receipt_report.xml',
|
||||
|
||||
@@ -253,6 +253,53 @@ class PaymentProvider(models.Model):
|
||||
|
||||
return result
|
||||
|
||||
# === BUSINESS METHODS - TOKENIZE / CHARGE === #
|
||||
|
||||
def _poynt_tokenize_nonce(self, nonce):
|
||||
"""Exchange a Poynt Collect nonce for a long-lived payment token JWT.
|
||||
|
||||
:param str nonce: The one-time nonce from Poynt Collect JS.
|
||||
:return: The tokenize response containing card details, cardId,
|
||||
paymentToken (JWT), and AVS/CVV verification results.
|
||||
:rtype: dict
|
||||
:raises ValidationError: If the tokenize call fails.
|
||||
"""
|
||||
self.ensure_one()
|
||||
return self._poynt_make_request(
|
||||
'POST',
|
||||
'cards/tokenize',
|
||||
payload={'nonce': nonce},
|
||||
)
|
||||
|
||||
def _poynt_charge_token(self, payment_jwt, amount, currency,
|
||||
action='SALE', reference=''):
|
||||
"""Charge a stored payment token JWT via the tokenize/charge endpoint.
|
||||
|
||||
:param str payment_jwt: The payment token JWT from _poynt_tokenize_nonce.
|
||||
:param float amount: The charge amount in major currency units.
|
||||
:param recordset currency: The currency record.
|
||||
:param str action: SALE or AUTHORIZE (default SALE).
|
||||
:param str reference: Optional reference note for the transaction.
|
||||
:return: The transaction result dict from Poynt.
|
||||
:rtype: dict
|
||||
:raises ValidationError: If the charge fails.
|
||||
"""
|
||||
self.ensure_one()
|
||||
payload = poynt_utils.build_token_charge_payload(
|
||||
action=action,
|
||||
amount=amount,
|
||||
currency=currency,
|
||||
payment_jwt=payment_jwt,
|
||||
business_id=self.poynt_business_id,
|
||||
store_id=self.poynt_store_id or '',
|
||||
reference=reference,
|
||||
)
|
||||
return self._poynt_make_request(
|
||||
'POST',
|
||||
'cards/tokenize/charge',
|
||||
payload=payload,
|
||||
)
|
||||
|
||||
# === BUSINESS METHODS - INLINE FORM === #
|
||||
|
||||
def _poynt_get_inline_form_values(self, amount, currency, partner_id, is_validation,
|
||||
|
||||
@@ -16,6 +16,13 @@ class PaymentToken(models.Model):
|
||||
help="The unique card identifier stored on the Poynt platform.",
|
||||
readonly=True,
|
||||
)
|
||||
poynt_payment_token = fields.Char(
|
||||
string="Poynt Payment Token (JWT)",
|
||||
help="Long-lived JWT issued by Poynt /cards/tokenize, used for "
|
||||
"recurring charges via /cards/tokenize/charge.",
|
||||
readonly=True,
|
||||
groups='base.group_system',
|
||||
)
|
||||
|
||||
def _poynt_validate_stored_card(self):
|
||||
"""Validate that the stored card is still usable on Poynt.
|
||||
@@ -35,7 +42,7 @@ class PaymentToken(models.Model):
|
||||
)
|
||||
|
||||
try:
|
||||
result = self.provider_id._poynt_make_request(
|
||||
result = self.provider_id.sudo()._poynt_make_request(
|
||||
'GET',
|
||||
f'cards/{self.poynt_card_id}',
|
||||
)
|
||||
|
||||
@@ -48,6 +48,9 @@ class PaymentTransaction(models.Model):
|
||||
copy=False,
|
||||
)
|
||||
|
||||
def _get_provider_sudo(self):
|
||||
return self.provider_id.sudo()
|
||||
|
||||
# === BUSINESS METHODS - PAYMENT FLOW === #
|
||||
|
||||
def _get_specific_processing_values(self, processing_values):
|
||||
@@ -64,7 +67,8 @@ class PaymentTransaction(models.Model):
|
||||
|
||||
poynt_data = self._poynt_create_order_and_authorize()
|
||||
|
||||
base_url = self.provider_id.get_base_url()
|
||||
provider = self._get_provider_sudo()
|
||||
base_url = provider.get_base_url()
|
||||
return_url = url_join(
|
||||
base_url,
|
||||
f'{PoyntController._return_url}?{url_encode({"reference": self.reference})}',
|
||||
@@ -74,8 +78,8 @@ class PaymentTransaction(models.Model):
|
||||
'poynt_order_id': poynt_data.get('order_id', ''),
|
||||
'poynt_transaction_id': poynt_data.get('transaction_id', ''),
|
||||
'return_url': return_url,
|
||||
'business_id': self.provider_id.poynt_business_id,
|
||||
'is_test': self.provider_id.state == 'test',
|
||||
'business_id': provider.poynt_business_id,
|
||||
'is_test': provider.state == 'test',
|
||||
}
|
||||
|
||||
def _send_payment_request(self):
|
||||
@@ -104,26 +108,29 @@ class PaymentTransaction(models.Model):
|
||||
:rtype: dict
|
||||
"""
|
||||
try:
|
||||
provider = self._get_provider_sudo()
|
||||
order_payload = poynt_utils.build_order_payload(
|
||||
self.reference, self.amount, self.currency_id,
|
||||
business_id=self.provider_id.poynt_business_id,
|
||||
store_id=self.provider_id.poynt_store_id or '',
|
||||
business_id=provider.poynt_business_id,
|
||||
store_id=provider.poynt_store_id or '',
|
||||
)
|
||||
order_result = self.provider_id._poynt_make_request(
|
||||
order_result = provider._poynt_make_request(
|
||||
'POST', 'orders', payload=order_payload,
|
||||
)
|
||||
order_id = order_result.get('id', '')
|
||||
self.poynt_order_id = order_id
|
||||
|
||||
action = 'AUTHORIZE' if self.provider_id.capture_manually else 'SALE'
|
||||
action = 'AUTHORIZE' if provider.capture_manually else 'SALE'
|
||||
txn_payload = poynt_utils.build_transaction_payload(
|
||||
action=action,
|
||||
amount=self.amount,
|
||||
currency=self.currency_id,
|
||||
order_id=order_id,
|
||||
reference=self.reference,
|
||||
business_id=provider.poynt_business_id,
|
||||
store_id=provider.poynt_store_id or '',
|
||||
)
|
||||
txn_result = self.provider_id._poynt_make_request(
|
||||
txn_result = provider._poynt_make_request(
|
||||
'POST', 'transactions', payload=txn_payload,
|
||||
)
|
||||
|
||||
@@ -144,46 +151,68 @@ class PaymentTransaction(models.Model):
|
||||
def _poynt_process_token_payment(self):
|
||||
"""Process a payment using a stored token (card on file).
|
||||
|
||||
For token-based payments we send a SALE or AUTHORIZE using the
|
||||
stored card ID from the payment token.
|
||||
Uses the JWT payment token via POST /cards/tokenize/charge when
|
||||
available. Falls back to the legacy cardId flow for tokens that
|
||||
were created before the JWT migration.
|
||||
"""
|
||||
try:
|
||||
action = 'AUTHORIZE' if self.provider_id.capture_manually else 'SALE'
|
||||
provider = self._get_provider_sudo()
|
||||
action = 'AUTHORIZE' if provider.capture_manually else 'SALE'
|
||||
payment_jwt = self.token_id.poynt_payment_token
|
||||
|
||||
funding_source = {
|
||||
'type': 'CREDIT_DEBIT',
|
||||
'card': {
|
||||
'cardId': self.token_id.poynt_card_id,
|
||||
},
|
||||
}
|
||||
|
||||
order_payload = poynt_utils.build_order_payload(
|
||||
self.reference, self.amount, self.currency_id,
|
||||
business_id=self.provider_id.poynt_business_id,
|
||||
store_id=self.provider_id.poynt_store_id or '',
|
||||
)
|
||||
order_result = self.provider_id._poynt_make_request(
|
||||
'POST', 'orders', payload=order_payload,
|
||||
)
|
||||
order_id = order_result.get('id', '')
|
||||
self.poynt_order_id = order_id
|
||||
|
||||
txn_payload = poynt_utils.build_transaction_payload(
|
||||
action=action,
|
||||
amount=self.amount,
|
||||
currency=self.currency_id,
|
||||
order_id=order_id,
|
||||
reference=self.reference,
|
||||
funding_source=funding_source,
|
||||
)
|
||||
txn_result = self.provider_id._poynt_make_request(
|
||||
'POST', 'transactions', payload=txn_payload,
|
||||
)
|
||||
if payment_jwt:
|
||||
txn_result = provider._poynt_charge_token(
|
||||
payment_jwt=payment_jwt,
|
||||
amount=self.amount,
|
||||
currency=self.currency_id,
|
||||
action=action,
|
||||
reference=self.reference,
|
||||
)
|
||||
else:
|
||||
funding_source = {
|
||||
'type': 'CREDIT_DEBIT',
|
||||
'card': {
|
||||
'cardId': self.token_id.poynt_card_id,
|
||||
},
|
||||
'entryDetails': {
|
||||
'customerPresenceStatus': 'MOTO',
|
||||
'entryMode': 'KEYED',
|
||||
},
|
||||
}
|
||||
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
|
||||
txn_payload = poynt_utils.build_transaction_payload(
|
||||
action=action,
|
||||
amount=self.amount,
|
||||
currency=self.currency_id,
|
||||
order_id=order_id,
|
||||
reference=self.reference,
|
||||
funding_source=funding_source,
|
||||
business_id=provider.poynt_business_id,
|
||||
store_id=provider.poynt_store_id or '',
|
||||
)
|
||||
txn_result = provider._poynt_make_request(
|
||||
'POST', 'transactions', payload=txn_payload,
|
||||
)
|
||||
|
||||
transaction_id = txn_result.get('id', '')
|
||||
self.poynt_transaction_id = transaction_id
|
||||
self.provider_reference = transaction_id
|
||||
|
||||
order_id = txn_result.get('orderIdFromTransaction', '') or \
|
||||
txn_result.get('orderId', '') or \
|
||||
getattr(self, 'poynt_order_id', '') or ''
|
||||
if order_id:
|
||||
self.poynt_order_id = order_id
|
||||
|
||||
payment_data = {
|
||||
'reference': self.reference,
|
||||
'poynt_order_id': order_id,
|
||||
@@ -211,8 +240,9 @@ class PaymentTransaction(models.Model):
|
||||
|
||||
parent_txn_id = source_tx.poynt_transaction_id or source_tx.provider_reference
|
||||
|
||||
provider = self._get_provider_sudo()
|
||||
try:
|
||||
txn_data = self.provider_id._poynt_make_request(
|
||||
txn_data = provider._poynt_make_request(
|
||||
'GET', f'transactions/{parent_txn_id}',
|
||||
)
|
||||
for link in txn_data.get('links', []):
|
||||
@@ -248,7 +278,7 @@ class PaymentTransaction(models.Model):
|
||||
'notes': f'Refund for {source_tx.reference}',
|
||||
}
|
||||
|
||||
result = self.provider_id._poynt_make_request(
|
||||
result = provider._poynt_make_request(
|
||||
'POST', 'transactions', payload=refund_payload,
|
||||
)
|
||||
|
||||
@@ -287,7 +317,7 @@ class PaymentTransaction(models.Model):
|
||||
},
|
||||
}
|
||||
|
||||
result = self.provider_id._poynt_make_request(
|
||||
result = self._get_provider_sudo()._poynt_make_request(
|
||||
'POST', 'transactions', payload=capture_payload,
|
||||
)
|
||||
|
||||
@@ -313,7 +343,7 @@ class PaymentTransaction(models.Model):
|
||||
txn_id = source_tx.provider_reference or source_tx.poynt_transaction_id
|
||||
|
||||
try:
|
||||
result = self.provider_id._poynt_make_request(
|
||||
result = self._get_provider_sudo()._poynt_make_request(
|
||||
'POST', f'transactions/{txn_id}/void',
|
||||
)
|
||||
|
||||
@@ -580,17 +610,18 @@ class PaymentTransaction(models.Model):
|
||||
return super()._create_payment(**extra_create_values)
|
||||
|
||||
self.ensure_one()
|
||||
provider = self._get_provider_sudo()
|
||||
reference = f'{self.reference} - {self.provider_reference or ""}'
|
||||
payment_method_line = self.provider_id.journal_id.inbound_payment_method_line_ids\
|
||||
.filtered(lambda l: l.payment_provider_id == self.provider_id)
|
||||
payment_method_line = provider.journal_id.inbound_payment_method_line_ids\
|
||||
.filtered(lambda l: l.payment_provider_id == provider)
|
||||
payment_values = {
|
||||
'amount': abs(self.amount),
|
||||
'payment_type': 'inbound' if self.amount > 0 else 'outbound',
|
||||
'currency_id': self.currency_id.id,
|
||||
'partner_id': self.partner_id.commercial_partner_id.id,
|
||||
'partner_type': 'customer',
|
||||
'journal_id': self.provider_id.journal_id.id,
|
||||
'company_id': self.provider_id.company_id.id,
|
||||
'journal_id': provider.journal_id.id,
|
||||
'company_id': provider.company_id.id,
|
||||
'payment_method_line_id': payment_method_line.id,
|
||||
'payment_token_id': self.token_id.id,
|
||||
'payment_transaction_id': self.id,
|
||||
@@ -608,7 +639,7 @@ class PaymentTransaction(models.Model):
|
||||
|
||||
payment = self.env['account.payment'].create(payment_values)
|
||||
|
||||
bank_account = self.provider_id.journal_id.default_account_id
|
||||
bank_account = provider.journal_id.default_account_id
|
||||
if bank_account and bank_account.account_type == 'asset_cash':
|
||||
payment.outstanding_account_id = bank_account
|
||||
|
||||
@@ -675,7 +706,7 @@ class PaymentTransaction(models.Model):
|
||||
fields in :attr:`poynt_receipt_data` as a JSON blob."""
|
||||
txn_data = {}
|
||||
try:
|
||||
txn_data = self.provider_id._poynt_make_request(
|
||||
txn_data = self._get_provider_sudo()._poynt_make_request(
|
||||
'GET', f'transactions/{self.poynt_transaction_id}',
|
||||
)
|
||||
except (ValidationError, Exception):
|
||||
@@ -757,7 +788,7 @@ class PaymentTransaction(models.Model):
|
||||
if not invoice:
|
||||
return
|
||||
|
||||
receipt_content = self.provider_id._poynt_fetch_receipt(
|
||||
receipt_content = self._get_provider_sudo()._poynt_fetch_receipt(
|
||||
self.poynt_transaction_id,
|
||||
)
|
||||
if not receipt_content:
|
||||
|
||||
@@ -66,17 +66,21 @@ class PoyntTerminal(models.Model):
|
||||
|
||||
# === BUSINESS METHODS === #
|
||||
|
||||
def _get_provider_sudo(self):
|
||||
return self.provider_id.sudo()
|
||||
|
||||
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
|
||||
provider = terminal._get_provider_sudo()
|
||||
store_id = terminal.store_id_poynt or provider.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)
|
||||
result = provider._poynt_make_request('GET', endpoint)
|
||||
poynt_status = result.get('status', 'UNKNOWN')
|
||||
|
||||
if poynt_status == 'ACTIVATED':
|
||||
@@ -130,7 +134,8 @@ class PoyntTerminal(models.Model):
|
||||
if order_id:
|
||||
payment_request['orderId'] = order_id
|
||||
|
||||
store_id = self.store_id_poynt or self.provider_id.poynt_store_id or ''
|
||||
provider = self._get_provider_sudo()
|
||||
store_id = self.store_id_poynt or provider.poynt_store_id or ''
|
||||
|
||||
data_str = json.dumps({
|
||||
'action': 'sale',
|
||||
@@ -142,12 +147,12 @@ class PoyntTerminal(models.Model):
|
||||
})
|
||||
|
||||
try:
|
||||
result = self.provider_id._poynt_make_request(
|
||||
result = provider._poynt_make_request(
|
||||
'POST',
|
||||
'cloudMessages',
|
||||
business_scoped=False,
|
||||
payload={
|
||||
'businessId': self.provider_id.poynt_business_id,
|
||||
'businessId': provider.poynt_business_id,
|
||||
'storeId': store_id,
|
||||
'deviceId': self.device_id,
|
||||
'ttl': 300,
|
||||
@@ -173,7 +178,7 @@ class PoyntTerminal(models.Model):
|
||||
:return: The full callback URL.
|
||||
:rtype: str
|
||||
"""
|
||||
base_url = self.provider_id.get_base_url()
|
||||
base_url = self._get_provider_sudo().get_base_url()
|
||||
return f"{base_url}/payment/poynt/terminal/callback"
|
||||
|
||||
def action_check_terminal_payment_status(self, reference):
|
||||
@@ -188,8 +193,9 @@ class PoyntTerminal(models.Model):
|
||||
"""
|
||||
self.ensure_one()
|
||||
|
||||
provider = self._get_provider_sudo()
|
||||
try:
|
||||
txn_result = self.provider_id._poynt_make_request(
|
||||
txn_result = provider._poynt_make_request(
|
||||
'GET',
|
||||
'transactions',
|
||||
params={
|
||||
@@ -201,7 +207,7 @@ class PoyntTerminal(models.Model):
|
||||
transactions = txn_result.get('transactions', [])
|
||||
|
||||
if not transactions:
|
||||
txn_result = self.provider_id._poynt_make_request(
|
||||
txn_result = provider._poynt_make_request(
|
||||
'GET',
|
||||
'transactions',
|
||||
params={
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_poynt_terminal_user,poynt.terminal.user,model_poynt_terminal,base.group_user,1,0,0,0
|
||||
access_poynt_terminal_admin,poynt.terminal.admin,model_poynt_terminal,base.group_system,1,1,1,1
|
||||
access_poynt_payment_wizard_user,poynt.payment.wizard.user,model_poynt_payment_wizard,account.group_account_invoice,1,1,1,0
|
||||
access_poynt_payment_wizard_admin,poynt.payment.wizard.admin,model_poynt_payment_wizard,base.group_system,1,1,1,1
|
||||
access_poynt_refund_wizard_user,poynt.refund.wizard.user,model_poynt_refund_wizard,account.group_account_invoice,1,1,1,0
|
||||
access_poynt_refund_wizard_admin,poynt.refund.wizard.admin,model_poynt_refund_wizard,base.group_system,1,1,1,1
|
||||
access_poynt_terminal_user,poynt.terminal.user,model_poynt_terminal,group_fusion_poynt_user,1,0,0,0
|
||||
access_poynt_terminal_admin,poynt.terminal.admin,model_poynt_terminal,group_fusion_poynt_admin,1,1,1,1
|
||||
access_poynt_payment_wizard_user,poynt.payment.wizard.user,model_poynt_payment_wizard,group_fusion_poynt_user,1,1,1,0
|
||||
access_poynt_payment_wizard_admin,poynt.payment.wizard.admin,model_poynt_payment_wizard,group_fusion_poynt_admin,1,1,1,1
|
||||
access_poynt_refund_wizard_user,poynt.refund.wizard.user,model_poynt_refund_wizard,group_fusion_poynt_user,1,1,1,0
|
||||
access_poynt_refund_wizard_admin,poynt.refund.wizard.admin,model_poynt_refund_wizard,group_fusion_poynt_admin,1,1,1,1
|
||||
access_payment_provider_poynt_user,payment.provider.poynt.user,payment.model_payment_provider,group_fusion_poynt_user,1,0,0,0
|
||||
access_payment_transaction_poynt_user,payment.transaction.poynt.user,payment.model_payment_transaction,group_fusion_poynt_user,1,1,1,0
|
||||
access_payment_method_poynt_user,payment.method.poynt.user,payment.model_payment_method,group_fusion_poynt_user,1,0,0,0
|
||||
|
||||
|
49
fusion_poynt/security/security.xml
Normal file
49
fusion_poynt/security/security.xml
Normal file
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- ================================================================== -->
|
||||
<!-- MODULE CATEGORY (required for user settings section rendering) -->
|
||||
<!-- Odoo 19 organizes privileges by ir.module.category. -->
|
||||
<!-- Without this, groups fall into the generic Extra Rights list. -->
|
||||
<!-- ================================================================== -->
|
||||
<record id="module_category_fusion_poynt" model="ir.module.category">
|
||||
<field name="name">Fusion Poynt</field>
|
||||
<field name="sequence">47</field>
|
||||
</record>
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<!-- FUSION POYNT PRIVILEGE (Odoo 19 pattern) -->
|
||||
<!-- Linked to module_category_fusion_poynt so all groups appear -->
|
||||
<!-- under a "FUSION POYNT" section in user settings. -->
|
||||
<!-- ================================================================== -->
|
||||
<record id="res_groups_privilege_fusion_poynt" model="res.groups.privilege">
|
||||
<field name="name">Fusion Poynt</field>
|
||||
<field name="sequence">47</field>
|
||||
<field name="category_id" ref="module_category_fusion_poynt"/>
|
||||
</record>
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<!-- USER GROUP -->
|
||||
<!-- Can view terminals, collect payments, and send receipts. -->
|
||||
<!-- Implies base.group_user and account invoice access. -->
|
||||
<!-- ================================================================== -->
|
||||
<record id="group_fusion_poynt_user" model="res.groups">
|
||||
<field name="name">User</field>
|
||||
<field name="sequence">10</field>
|
||||
<field name="implied_ids" eval="[(4, ref('base.group_user')), (4, ref('account.group_account_invoice'))]"/>
|
||||
<field name="privilege_id" ref="res_groups_privilege_fusion_poynt"/>
|
||||
</record>
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<!-- ADMINISTRATOR GROUP -->
|
||||
<!-- Full access: configure providers, manage terminals, process -->
|
||||
<!-- payments, voids, and refunds. -->
|
||||
<!-- ================================================================== -->
|
||||
<record id="group_fusion_poynt_admin" model="res.groups">
|
||||
<field name="name">Administrator</field>
|
||||
<field name="sequence">20</field>
|
||||
<field name="privilege_id" ref="res_groups_privilege_fusion_poynt"/>
|
||||
<field name="implied_ids" eval="[(4, ref('group_fusion_poynt_user'))]"/>
|
||||
<field name="user_ids" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -239,7 +239,8 @@ def build_order_payload(reference, amount, currency, business_id='',
|
||||
|
||||
|
||||
def build_transaction_payload(
|
||||
action, amount, currency, order_id=None, reference='', funding_source=None
|
||||
action, amount, currency, order_id=None, reference='',
|
||||
funding_source=None, business_id='', store_id='',
|
||||
):
|
||||
"""Build a Poynt transaction payload for charge/auth/capture.
|
||||
|
||||
@@ -249,11 +250,23 @@ def build_transaction_payload(
|
||||
:param str order_id: The Poynt order UUID (optional).
|
||||
:param str reference: The Odoo transaction reference.
|
||||
:param dict funding_source: The funding source / card data (optional).
|
||||
:param str business_id: The Poynt business UUID (optional).
|
||||
:param str store_id: The Poynt store UUID (optional).
|
||||
:return: The Poynt-formatted transaction payload.
|
||||
:rtype: dict
|
||||
"""
|
||||
minor_amount = format_poynt_amount(amount, currency)
|
||||
|
||||
context = {
|
||||
'source': 'WEB',
|
||||
'sourceApp': 'odoo.fusion_poynt',
|
||||
'transactionInstruction': 'ONLINE_AUTH_REQUIRED',
|
||||
}
|
||||
if business_id:
|
||||
context['businessId'] = business_id
|
||||
if store_id:
|
||||
context['storeId'] = store_id
|
||||
|
||||
payload = {
|
||||
'action': action,
|
||||
'amounts': {
|
||||
@@ -263,11 +276,7 @@ def build_transaction_payload(
|
||||
'cashbackAmount': 0,
|
||||
'currency': currency.name,
|
||||
},
|
||||
'context': {
|
||||
'source': 'WEB',
|
||||
'sourceApp': 'odoo.fusion_poynt',
|
||||
'transactionInstruction': 'ONLINE_AUTH_REQUIRED',
|
||||
},
|
||||
'context': context,
|
||||
'notes': reference,
|
||||
}
|
||||
|
||||
@@ -281,3 +290,44 @@ def build_transaction_payload(
|
||||
payload['fundingSource'] = funding_source
|
||||
|
||||
return payload
|
||||
|
||||
|
||||
def build_token_charge_payload(
|
||||
action, amount, currency, payment_jwt,
|
||||
business_id='', store_id='', reference='',
|
||||
):
|
||||
"""Build a payload for POST /cards/tokenize/charge.
|
||||
|
||||
:param str action: SALE or AUTHORIZE.
|
||||
:param float amount: Amount in major currency units.
|
||||
:param recordset currency: Currency record.
|
||||
:param str payment_jwt: The payment token JWT from /cards/tokenize.
|
||||
:param str business_id: Poynt business UUID.
|
||||
:param str store_id: Poynt store UUID.
|
||||
:param str reference: Optional reference note.
|
||||
:return: The charge payload dict.
|
||||
:rtype: dict
|
||||
"""
|
||||
minor_amount = format_poynt_amount(amount, currency)
|
||||
|
||||
context = {}
|
||||
if business_id:
|
||||
context['businessId'] = business_id
|
||||
if store_id:
|
||||
context['storeId'] = store_id
|
||||
|
||||
payload = {
|
||||
'action': action,
|
||||
'context': context,
|
||||
'amounts': {
|
||||
'transactionAmount': minor_amount,
|
||||
'orderAmount': minor_amount,
|
||||
'currency': currency.name,
|
||||
},
|
||||
'fundingSource': {
|
||||
'cardToken': payment_jwt,
|
||||
},
|
||||
}
|
||||
if reference:
|
||||
payload['notes'] = reference
|
||||
return payload
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
class="btn-secondary"
|
||||
icon="fa-credit-card"
|
||||
invisible="state != 'posted' or payment_state not in ('not_paid', 'partial') or move_type != 'out_invoice'"
|
||||
groups="account.group_account_invoice"
|
||||
groups="fusion_poynt.group_fusion_poynt_user"
|
||||
data-hotkey="p"/>
|
||||
</xpath>
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
class="btn-secondary"
|
||||
icon="fa-undo"
|
||||
invisible="state != 'posted' or payment_state not in ('not_paid', 'partial') or move_type != 'out_refund' or poynt_refunded"
|
||||
groups="account.group_account_invoice"
|
||||
groups="fusion_poynt.group_fusion_poynt_user"
|
||||
data-hotkey="r"/>
|
||||
</xpath>
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
class="btn-secondary"
|
||||
icon="fa-envelope"
|
||||
invisible="state != 'posted' or move_type != 'out_invoice' or not has_poynt_receipt"
|
||||
groups="account.group_account_invoice"/>
|
||||
groups="fusion_poynt.group_fusion_poynt_user"/>
|
||||
</xpath>
|
||||
|
||||
<!-- Resend Receipt button on credit notes (refunded via Poynt) -->
|
||||
@@ -62,7 +62,7 @@
|
||||
class="btn-secondary"
|
||||
icon="fa-envelope"
|
||||
invisible="state != 'posted' or move_type != 'out_refund' or not poynt_refunded"
|
||||
groups="account.group_account_invoice"/>
|
||||
groups="fusion_poynt.group_fusion_poynt_user"/>
|
||||
</xpath>
|
||||
|
||||
<!-- Refunded banner on credit notes -->
|
||||
|
||||
@@ -105,6 +105,7 @@
|
||||
name="Poynt Terminals"
|
||||
parent="account.root_payment_menu"
|
||||
action="action_poynt_terminal"
|
||||
sequence="15"/>
|
||||
sequence="15"
|
||||
groups="fusion_poynt.group_fusion_poynt_user"/>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
class="btn-secondary"
|
||||
icon="fa-credit-card"
|
||||
invisible="state not in ('sale', 'done')"
|
||||
groups="fusion_poynt.group_fusion_poynt_user"
|
||||
data-hotkey="p"/>
|
||||
</xpath>
|
||||
</field>
|
||||
|
||||
@@ -42,6 +42,11 @@ class PoyntPaymentWizard(models.TransientModel):
|
||||
required=True,
|
||||
domain="[('code', '=', 'poynt'), ('state', '!=', 'disabled')]",
|
||||
)
|
||||
provider_name = fields.Char(
|
||||
related='provider_id.name',
|
||||
string="Poynt Provider",
|
||||
readonly=True,
|
||||
)
|
||||
|
||||
payment_mode = fields.Selection(
|
||||
selection=[
|
||||
@@ -109,7 +114,7 @@ class PoyntPaymentWizard(models.TransientModel):
|
||||
res['amount'] = invoice.amount_residual
|
||||
res['currency_id'] = invoice.currency_id.id
|
||||
|
||||
provider = self.env['payment.provider'].search([
|
||||
provider = self.env['payment.provider'].sudo().search([
|
||||
('code', '=', 'poynt'),
|
||||
('state', '!=', 'disabled'),
|
||||
], limit=1)
|
||||
@@ -120,10 +125,15 @@ class PoyntPaymentWizard(models.TransientModel):
|
||||
|
||||
return res
|
||||
|
||||
def _get_provider_sudo(self):
|
||||
return self.provider_id.sudo()
|
||||
|
||||
@api.onchange('provider_id')
|
||||
def _onchange_provider_id(self):
|
||||
if self.provider_id and self.provider_id.poynt_default_terminal_id:
|
||||
self.terminal_id = self.provider_id.poynt_default_terminal_id
|
||||
if self.provider_id:
|
||||
provider = self._get_provider_sudo()
|
||||
if provider.poynt_default_terminal_id:
|
||||
self.terminal_id = provider.poynt_default_terminal_id
|
||||
|
||||
def action_collect_payment(self):
|
||||
"""Dispatch to the appropriate payment method."""
|
||||
@@ -213,7 +223,8 @@ class PoyntPaymentWizard(models.TransientModel):
|
||||
},
|
||||
}
|
||||
|
||||
action = 'AUTHORIZE' if self.provider_id.capture_manually else 'SALE'
|
||||
provider = self._get_provider_sudo()
|
||||
action = 'AUTHORIZE' if provider.capture_manually else 'SALE'
|
||||
minor_amount = poynt_utils.format_poynt_amount(
|
||||
self.amount, self.currency_id,
|
||||
)
|
||||
@@ -232,7 +243,7 @@ class PoyntPaymentWizard(models.TransientModel):
|
||||
'source': 'WEB',
|
||||
'sourceApp': 'odoo.fusion_poynt',
|
||||
'transactionInstruction': 'ONLINE_AUTH_REQUIRED',
|
||||
'businessId': self.provider_id.poynt_business_id,
|
||||
'businessId': provider.poynt_business_id,
|
||||
},
|
||||
'notes': reference,
|
||||
}
|
||||
@@ -243,7 +254,7 @@ class PoyntPaymentWizard(models.TransientModel):
|
||||
'type': 'POYNT_ORDER',
|
||||
}]
|
||||
|
||||
result = self.provider_id._poynt_make_request(
|
||||
result = provider._poynt_make_request(
|
||||
'POST', 'transactions', payload=txn_payload,
|
||||
)
|
||||
|
||||
@@ -303,7 +314,7 @@ class PoyntPaymentWizard(models.TransientModel):
|
||||
if not terminal:
|
||||
raise UserError(_("No terminal associated with this payment."))
|
||||
|
||||
provider = self.provider_id
|
||||
provider = self._get_provider_sudo()
|
||||
|
||||
try:
|
||||
txn = self._find_terminal_transaction(provider)
|
||||
@@ -517,14 +528,15 @@ class PoyntPaymentWizard(models.TransientModel):
|
||||
|
||||
def _create_poynt_order(self, reference):
|
||||
"""Create a Poynt order via the API."""
|
||||
provider = self._get_provider_sudo()
|
||||
order_payload = poynt_utils.build_order_payload(
|
||||
reference,
|
||||
self.amount,
|
||||
self.currency_id,
|
||||
business_id=self.provider_id.poynt_business_id,
|
||||
store_id=self.provider_id.poynt_store_id or '',
|
||||
business_id=provider.poynt_business_id,
|
||||
store_id=provider.poynt_store_id or '',
|
||||
)
|
||||
return self.provider_id._poynt_make_request(
|
||||
return provider._poynt_make_request(
|
||||
'POST', 'orders', payload=order_payload,
|
||||
)
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<form string="Collect Poynt Payment">
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="poynt_transaction_ref" invisible="1"/>
|
||||
<field name="provider_id" invisible="1"/>
|
||||
|
||||
<!-- Status banner for waiting / done / error -->
|
||||
<div class="alert alert-info" role="alert"
|
||||
@@ -34,8 +35,7 @@
|
||||
<field name="partner_id"/>
|
||||
<field name="amount"/>
|
||||
<field name="currency_id"/>
|
||||
<field name="provider_id"
|
||||
readonly="state != 'draft'"/>
|
||||
<field name="provider_name"/>
|
||||
</group>
|
||||
<group string="Payment Mode"
|
||||
invisible="state not in ('draft', 'error')">
|
||||
|
||||
@@ -51,6 +51,11 @@ class PoyntRefundWizard(models.TransientModel):
|
||||
required=True,
|
||||
readonly=True,
|
||||
)
|
||||
provider_name = fields.Char(
|
||||
related='provider_id.name',
|
||||
string="Poynt Provider",
|
||||
readonly=True,
|
||||
)
|
||||
original_transaction_id = fields.Many2one(
|
||||
'payment.transaction',
|
||||
string="Original Transaction",
|
||||
@@ -104,6 +109,9 @@ class PoyntRefundWizard(models.TransientModel):
|
||||
)
|
||||
status_message = fields.Text(string="Status", readonly=True)
|
||||
|
||||
def _get_provider_sudo(self):
|
||||
return self.provider_id.sudo()
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields_list):
|
||||
res = super().default_get(fields_list)
|
||||
@@ -130,8 +138,9 @@ class PoyntRefundWizard(models.TransientModel):
|
||||
res['original_invoice_id'] = credit_note.reversed_entry_id.id
|
||||
res['original_poynt_txn_id'] = orig_tx.poynt_transaction_id
|
||||
|
||||
if orig_tx.provider_id.poynt_default_terminal_id:
|
||||
res['terminal_id'] = orig_tx.provider_id.poynt_default_terminal_id.id
|
||||
provider = orig_tx.provider_id.sudo()
|
||||
if provider.poynt_default_terminal_id:
|
||||
res['terminal_id'] = provider.poynt_default_terminal_id.id
|
||||
|
||||
age_days = 0
|
||||
if orig_tx.create_date:
|
||||
@@ -201,7 +210,7 @@ class PoyntRefundWizard(models.TransientModel):
|
||||
still showing ``status: CAPTURED``. We must check the full chain.
|
||||
"""
|
||||
orig_tx = self.original_transaction_id
|
||||
provider = self.provider_id
|
||||
provider = self._get_provider_sudo()
|
||||
txn_id = orig_tx.poynt_transaction_id
|
||||
|
||||
try:
|
||||
@@ -278,7 +287,7 @@ class PoyntRefundWizard(models.TransientModel):
|
||||
def _process_referenced_refund(self):
|
||||
"""Send a referenced REFUND using the original transaction's parentId."""
|
||||
orig_tx = self.original_transaction_id
|
||||
provider = self.provider_id
|
||||
provider = self._get_provider_sudo()
|
||||
|
||||
parent_txn_id = orig_tx.poynt_transaction_id
|
||||
try:
|
||||
@@ -346,7 +355,7 @@ class PoyntRefundWizard(models.TransientModel):
|
||||
"The customer's card must be present on the device."
|
||||
))
|
||||
|
||||
provider = self.provider_id
|
||||
provider = self._get_provider_sudo()
|
||||
orig_tx = self.original_transaction_id
|
||||
minor_amount = poynt_utils.format_poynt_amount(
|
||||
self.amount, self.currency_id,
|
||||
|
||||
@@ -44,7 +44,8 @@
|
||||
<field name="currency_id" invisible="1"/>
|
||||
</group>
|
||||
<group string="Original Payment">
|
||||
<field name="provider_id" readonly="1"/>
|
||||
<field name="provider_id" invisible="1"/>
|
||||
<field name="provider_name"/>
|
||||
<field name="original_transaction_id" readonly="1"/>
|
||||
<field name="original_poynt_txn_id" readonly="1"/>
|
||||
<field name="card_info" readonly="1"
|
||||
|
||||
Reference in New Issue
Block a user