Merge pull request #5918 from expsa/change_token_flow
[FIX] payment_hyperpay_tokenization: fix brand error & http response,…
This commit is contained in:
commit
fe1ec9137a
|
|
@ -4,7 +4,6 @@ import logging
|
||||||
from odoo import _
|
from odoo import _
|
||||||
from odoo.http import route, request, Controller
|
from odoo.http import route, request, Controller
|
||||||
from odoo.addons.payment_hyperpay.data.payment_icon import payment_icon
|
from odoo.addons.payment_hyperpay.data.payment_icon import payment_icon
|
||||||
from odoo.addons.payment.controllers.portal import PaymentProcessing
|
|
||||||
from odoo.addons.payment.models.payment_acquirer import PaymentToken
|
from odoo.addons.payment.models.payment_acquirer import PaymentToken
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
@ -24,9 +23,10 @@ class HyperPayTokenization(Controller):
|
||||||
checkout_data = self._register_payment_card(kwargs)
|
checkout_data = self._register_payment_card(kwargs)
|
||||||
return checkout_data
|
return checkout_data
|
||||||
except Exception as er:
|
except Exception as er:
|
||||||
|
_logger.exception('Error in token_checkout: %s' % er)
|
||||||
return {
|
return {
|
||||||
'state': False,
|
'state': False,
|
||||||
'message': er
|
'message': str(er)
|
||||||
}
|
}
|
||||||
|
|
||||||
def _save_session_data(self, data):
|
def _save_session_data(self, data):
|
||||||
|
|
@ -37,13 +37,11 @@ class HyperPayTokenization(Controller):
|
||||||
acquirer_id = int(data.get('acquirer_id'))
|
acquirer_id = int(data.get('acquirer_id'))
|
||||||
acquirer = request.env['payment.acquirer'].sudo().search([('id', '=', acquirer_id)])
|
acquirer = request.env['payment.acquirer'].sudo().search([('id', '=', acquirer_id)])
|
||||||
payment_icons = acquirer.payment_icon_ids.mapped('name')
|
payment_icons = acquirer.payment_icon_ids.mapped('name')
|
||||||
data_brands = "VISA"
|
|
||||||
base_url = request.httprequest.host_url
|
base_url = request.httprequest.host_url
|
||||||
if (len(payment_icon) > 1):
|
|
||||||
brands = [payment_icon[i.upper()] for i in payment_icons if i.upper() in payment_icon.keys()]
|
brands = [payment_icon[i.upper()] for i in payment_icons if i.upper() in payment_icon.keys()]
|
||||||
data_brands = brands and " ".join(brands) or data_brands
|
data_brands = brands and " ".join(brands) or "VISA"
|
||||||
tx = self._create_validation_transaction(acquirer)
|
tx = self._create_validation_transaction(acquirer)
|
||||||
payload = self._get_hyperpay_token_payload(acquirer, data_brands, data)
|
payload = self._get_hyperpay_token_payload(acquirer, data)
|
||||||
|
|
||||||
payload.update({
|
payload.update({
|
||||||
"merchantTransactionId": tx.reference,
|
"merchantTransactionId": tx.reference,
|
||||||
|
|
@ -81,19 +79,17 @@ class HyperPayTokenization(Controller):
|
||||||
'return_url': return_url,
|
'return_url': return_url,
|
||||||
}
|
}
|
||||||
|
|
||||||
def _get_hyperpay_token_payload(self, acquirer, data_brands, data):
|
def _get_hyperpay_token_payload(self, acquirer, data):
|
||||||
reference_id = request.session.get('hyperpay_token_reference_id')
|
reference_id = request.session.get('hyperpay_token_reference_id')
|
||||||
reference_model = request.session.get('hyperpay_token_reference_model')
|
reference_model = request.session.get('hyperpay_token_reference_model')
|
||||||
partner_id = request.env.user.partner_id
|
partner_id = request.env.user.partner_id
|
||||||
currency = partner_id.currency_id
|
currency = partner_id.currency_id
|
||||||
lang_code = str(request.env['res.lang'].sudo().search([('code', '=', request.env.user.lang)]).iso_code or '').upper()
|
|
||||||
amount = self._get_validation_amount(currency)
|
amount = self._get_validation_amount(currency)
|
||||||
return {
|
return {
|
||||||
"entityId": acquirer.hyperpay_merchant_id,
|
"entityId": acquirer.hyperpay_merchant_id,
|
||||||
'createRegistration': True,
|
'createRegistration': True,
|
||||||
"amount": '%.2f' % amount,
|
"amount": '%.2f' % amount,
|
||||||
"currency": currency.name,
|
"currency": currency.name,
|
||||||
'paymentBrand': data_brands,
|
|
||||||
"paymentType": "DB",
|
"paymentType": "DB",
|
||||||
'standingInstruction.mode': 'INITIAL',
|
'standingInstruction.mode': 'INITIAL',
|
||||||
'standingInstruction.source': 'CIT',
|
'standingInstruction.source': 'CIT',
|
||||||
|
|
@ -103,16 +99,14 @@ class HyperPayTokenization(Controller):
|
||||||
"billing.street1": partner_id.street or 'Riyadh',
|
"billing.street1": partner_id.street or 'Riyadh',
|
||||||
"billing.street2": partner_id.street2 or '',
|
"billing.street2": partner_id.street2 or '',
|
||||||
"billing.city": partner_id.city or 'Riyadh',
|
"billing.city": partner_id.city or 'Riyadh',
|
||||||
"billing.state": partner_id.state_id.name or 'Riyadh',
|
"billing.state": partner_id.state_id.name if partner_id.state_id else 'Riyadh',
|
||||||
"billing.postcode": partner_id.zip or '',
|
"billing.postcode": partner_id.zip or '',
|
||||||
"billing.country": partner_id.country_id.code or 'SA',
|
"billing.country": partner_id.country_id.code or 'SA',
|
||||||
"customer.givenName": partner_id.name,
|
"customer.givenName": partner_id.name,
|
||||||
"customer.surname": '',
|
"customer.surname": '',
|
||||||
"customer.email": partner_id.email,
|
"customer.email": partner_id.email or 'noemail@example.com',
|
||||||
"customer.mobile": partner_id.mobile or partner_id.phone or '',
|
"customer.mobile": partner_id.mobile or partner_id.phone or '',
|
||||||
"customer.phone": partner_id.phone or partner_id.mobile or '',
|
"customer.phone": partner_id.phone or partner_id.mobile or '',
|
||||||
'customer.ip': request.httprequest.environ["REMOTE_ADDR"],
|
|
||||||
'customer.language': lang_code,
|
|
||||||
"customParameters[SHOPPER_acquirer_id]": data.get('acquirer_id', 0),
|
"customParameters[SHOPPER_acquirer_id]": data.get('acquirer_id', 0),
|
||||||
"customParameters[SHOPPER_hyperpay_token_reference_id]": reference_id,
|
"customParameters[SHOPPER_hyperpay_token_reference_id]": reference_id,
|
||||||
"customParameters[SHOPPER_hyperpay_token_reference_model]": reference_model,
|
"customParameters[SHOPPER_hyperpay_token_reference_model]": reference_model,
|
||||||
|
|
@ -142,22 +136,34 @@ class HyperPayTokenization(Controller):
|
||||||
return PaymentToken.VALIDATION_AMOUNTS.get(currency.name)
|
return PaymentToken.VALIDATION_AMOUNTS.get(currency.name)
|
||||||
else:
|
else:
|
||||||
# If we don't find the user's currency, then we set the currency to EUR and the amount to 1€50.
|
# If we don't find the user's currency, then we set the currency to EUR and the amount to 1€50.
|
||||||
currency = self.env['res.currency'].search([('name', '=', 'EUR')])
|
|
||||||
return 1.5
|
return 1.5
|
||||||
|
|
||||||
@route('/hyperpay/tokens/result', type='http', auth='public', website=True)
|
@route('/hyperpay/tokens/result', type='http', auth='public', website=True)
|
||||||
def token_return(self, **post):
|
def token_return(self, **post):
|
||||||
try:
|
try:
|
||||||
_logger.info('Hyperpay Token Return Post: %s' % post)
|
_logger.info('Hyperpay Token Return Post: %s' % post)
|
||||||
transaction_id = request.env['payment.transaction'].sudo().search([('id', '=', int(post.get('transaction_id', 0)))])
|
|
||||||
|
transaction_id = request.env['payment.transaction'].sudo().search([('id', '=', int(post.get('transaction_id', 0)))], limit=1)
|
||||||
|
if not transaction_id:
|
||||||
|
_logger.error('Transaction not found: %s' % post.get('transaction_id'))
|
||||||
|
return request.make_response('Transaction not found', headers=[('Content-Type', 'text/html')])
|
||||||
|
|
||||||
acquirer_id = transaction_id.acquirer_id
|
acquirer_id = transaction_id.acquirer_id
|
||||||
|
if not acquirer_id:
|
||||||
|
_logger.error('Acquirer not found for transaction: %s' % transaction_id.id)
|
||||||
|
return request.make_response('Payment provider not found', headers=[('Content-Type', 'text/html')])
|
||||||
|
|
||||||
|
resource_path = post.get('resourcePath')
|
||||||
|
if not resource_path:
|
||||||
|
_logger.error('resourcePath not found in callback data: %s' % post)
|
||||||
|
return request.make_response('Invalid callback data', headers=[('Content-Type', 'text/html')])
|
||||||
|
|
||||||
if acquirer_id.state == 'test':
|
if acquirer_id.state == 'test':
|
||||||
domain = TEST_URL
|
domain = TEST_URL
|
||||||
else:
|
else:
|
||||||
domain = LIVE_URL
|
domain = LIVE_URL
|
||||||
|
|
||||||
url = f"{domain}{post.get('resourcePath')}?entityId={acquirer_id.hyperpay_merchant_id}"
|
url = f"{domain}{resource_path}?entityId={acquirer_id.hyperpay_merchant_id}"
|
||||||
headers = {
|
headers = {
|
||||||
"Authorization": f"Bearer {acquirer_id.hyperpay_authorization}"
|
"Authorization": f"Bearer {acquirer_id.hyperpay_authorization}"
|
||||||
}
|
}
|
||||||
|
|
@ -166,54 +172,84 @@ class HyperPayTokenization(Controller):
|
||||||
_logger.info('Hyperpay Token Status Response Text: %s' % response_data.text)
|
_logger.info('Hyperpay Token Status Response Text: %s' % response_data.text)
|
||||||
resp = response_data.json()
|
resp = response_data.json()
|
||||||
_logger.info('Hyperpay Token Status Response JSON: %s' % resp)
|
_logger.info('Hyperpay Token Status Response JSON: %s' % resp)
|
||||||
result = resp.get('result', {})
|
|
||||||
result_code = result.get('code')
|
|
||||||
|
|
||||||
if result_code and not re.match(r"^(000\.000\.|000\.100\.1|000\.[36]|000\.400\.1[12]0|000\.400\.0[^3]|000\.400\.100|000\.200)", result_code):
|
resp.update({'tx_id': transaction_id.id})
|
||||||
# Handle failed cards logic here
|
request.env['payment.transaction'].sudo().form_feedback(resp, "hyperpay")
|
||||||
return {'state': False, 'message': result.get('description', ''), 'errors': resp.get('parameterErrors', [])}
|
|
||||||
|
|
||||||
# create card record and activate recurring donation
|
transaction_id = request.env['payment.transaction'].sudo().search([('id', '=', transaction_id.id)], limit=1)
|
||||||
card = resp.get('card', {})
|
|
||||||
if not card:
|
if transaction_id.state != 'done':
|
||||||
return {'state': False, 'message': 'Card data not found'}
|
_logger.error('Validation transaction not done: %s (state: %s)' % (transaction_id.id, transaction_id.state))
|
||||||
self._post_process_token_return(transaction_id, resp)
|
return request.make_response('Payment validation failed. Please try again or contact support.', headers=[('Content-Type', 'text/html')])
|
||||||
|
|
||||||
|
res = self._post_process_token_return(transaction_id, resp)
|
||||||
|
if not res.get('state'):
|
||||||
|
return request.make_response(res.get('message'), headers=[('Content-Type', 'text/html')])
|
||||||
except Exception as er:
|
except Exception as er:
|
||||||
# request.env.cr.rollback()
|
# request.env.cr.rollback()
|
||||||
_logger.error(er)
|
_logger.exception('Error processing token return: %s' % er)
|
||||||
|
return request.make_response('An error occurred while processing your payment. Please contact support.\n Stack Trace: %s' % er, headers=[('Content-Type', 'text/html')])
|
||||||
|
|
||||||
return request.redirect('/my/recurring_donation')
|
return request.redirect('/my/recurring_donation')
|
||||||
|
|
||||||
|
|
||||||
def _post_process_token_return(self, transaction_id, data):
|
def _post_process_token_return(self, transaction_id, data):
|
||||||
data.update({'tx_id': transaction_id.id})
|
|
||||||
request.env['payment.transaction'].sudo().form_feedback(data, "hyperpay")
|
card = data.get('card', {})
|
||||||
if not transaction_id.state == 'done':
|
if not card:
|
||||||
_logger.error('Hyperpay Token Return Transaction not done: %s' % transaction_id.id)
|
_logger.error('Card data not found in response')
|
||||||
return False
|
return {'state': False, 'message': 'Card data not found in response'}
|
||||||
|
|
||||||
|
initial_tx_id = data.get('resultDetails', {}).get('CardholderInitiatedTransactionID') or data.get('CardholderInitiatedTransactionID', '')
|
||||||
|
registration_id = data.get('id', '')
|
||||||
|
|
||||||
|
if not initial_tx_id:
|
||||||
|
_logger.error('Initial transaction ID not found - token will not work for recurring payments')
|
||||||
|
return {'state': False, 'message': 'Initial transaction ID not found - token will not work for recurring payments'}
|
||||||
|
|
||||||
|
if not registration_id:
|
||||||
|
_logger.error('Registration ID not found in response')
|
||||||
|
return {'state': False, 'message': 'Registration ID not found in response'}
|
||||||
|
|
||||||
|
acquirer_id = transaction_id.acquirer_id
|
||||||
|
existing_token = request.env['payment.token'].sudo().search([
|
||||||
|
('acquirer_ref', '=', registration_id),
|
||||||
|
('acquirer_id', '=', acquirer_id.id)
|
||||||
|
], limit=1)
|
||||||
|
|
||||||
|
if existing_token:
|
||||||
|
_logger.warning('Token already exists for registration %s, returning existing token' % registration_id)
|
||||||
|
reference_id = int(data.get('customParameters', {}).get('SHOPPER_hyperpay_token_reference_id', 0))
|
||||||
|
reference_model = data.get('customParameters', {}).get('SHOPPER_hyperpay_token_reference_model', '')
|
||||||
|
if reference_id and reference_model and reference_model in request.env:
|
||||||
|
record_id = request.env[reference_model].sudo().search([('id', '=', reference_id)])
|
||||||
|
if record_id and hasattr(record_id, '_post_process_card_tokenization'):
|
||||||
|
record_id._post_process_card_tokenization(existing_token)
|
||||||
|
return {'state': True, 'message': 'Token already exists for registration %s, returning existing token' % registration_id}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
transaction_id.hyperpay_s2s_do_refund()
|
transaction_id.hyperpay_s2s_do_refund()
|
||||||
except Exception as er:
|
except Exception as er:
|
||||||
_logger.error('Hyperpay Token Return Transaction refund failed: %s' % er)
|
_logger.error('Hyperpay Token Return Transaction refund failed: %s' % er)
|
||||||
|
|
||||||
acquirer_id = transaction_id.acquirer_id
|
|
||||||
card = data.get('card', {})
|
|
||||||
card_vals = {
|
card_vals = {
|
||||||
'name': f"{card.get('bin', '')}XXXXXXXXXXXX{card.get('last4Digits', '')}",
|
'name': f"{card.get('bin', '')}XXXXXXXXXXXX{card.get('last4Digits', '')}",
|
||||||
'partner_id': request.env.user.partner_id.id,
|
'partner_id': request.env.user.partner_id.id,
|
||||||
'acquirer_id': acquirer_id.id,
|
'acquirer_id': acquirer_id.id,
|
||||||
'acquirer_ref': data.get('id', ''),
|
'acquirer_ref': registration_id,
|
||||||
'hyperpay_payment_brand': data.get('paymentBrand'),
|
'hyperpay_payment_brand': data.get('paymentBrand'),
|
||||||
'initial_transaction_id': data.get('resultDetails.CardholderInitiatedTransactionID') or data.get('CardholderInitiatedTransactionID', ''),
|
'hyperpay_initial_transaction_id': initial_tx_id,
|
||||||
'verified': True,
|
'verified': True,
|
||||||
}
|
}
|
||||||
|
|
||||||
token_id = request.env['payment.token'].sudo().create(card_vals)
|
token_id = request.env['payment.token'].sudo().create(card_vals)
|
||||||
|
_logger.info('Created payment token %s for partner %s' % (token_id.id, request.env.user.partner_id.id))
|
||||||
|
|
||||||
reference_id = int(data.get('customParameters', {}).get('SHOPPER_hyperpay_token_reference_id', 0))
|
reference_id = int(data.get('customParameters', {}).get('SHOPPER_hyperpay_token_reference_id', 0))
|
||||||
reference_model = data.get('customParameters', {}).get('SHOPPER_hyperpay_token_reference_model', '')
|
reference_model = data.get('customParameters', {}).get('SHOPPER_hyperpay_token_reference_model', '')
|
||||||
if reference_id and reference_model and reference_model in request.env:
|
if reference_id and reference_model and reference_model in request.env:
|
||||||
record_id = request.env[reference_model].sudo().search([('id', '=', reference_id)])
|
record_id = request.env[reference_model].sudo().search([('id', '=', reference_id)])
|
||||||
if record_id and hasattr(record_id, '_post_process_card_tokenization'):
|
if record_id and hasattr(record_id, '_post_process_card_tokenization'):
|
||||||
record_id._post_process_card_tokenization(token_id)
|
record_id._post_process_card_tokenization(token_id)
|
||||||
return token_id
|
|
||||||
|
|
||||||
|
return {'state': True, 'message': 'Token created successfully'}
|
||||||
Loading…
Reference in New Issue