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:
@@ -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={
|
||||
|
||||
Reference in New Issue
Block a user