summaryrefslogtreecommitdiff
path: root/addons/payment_authorize/models
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/payment_authorize/models
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/payment_authorize/models')
-rw-r--r--addons/payment_authorize/models/__init__.py2
-rw-r--r--addons/payment_authorize/models/authorize_request.py401
-rw-r--r--addons/payment_authorize/models/payment.py335
3 files changed, 738 insertions, 0 deletions
diff --git a/addons/payment_authorize/models/__init__.py b/addons/payment_authorize/models/__init__.py
new file mode 100644
index 00000000..01bcbec2
--- /dev/null
+++ b/addons/payment_authorize/models/__init__.py
@@ -0,0 +1,2 @@
+# -*- coding: utf-8 -*-
+from . import payment
diff --git a/addons/payment_authorize/models/authorize_request.py b/addons/payment_authorize/models/authorize_request.py
new file mode 100644
index 00000000..78db4977
--- /dev/null
+++ b/addons/payment_authorize/models/authorize_request.py
@@ -0,0 +1,401 @@
+# -*- coding: utf-8 -*-
+import json
+import logging
+import requests
+
+from uuid import uuid4
+
+from odoo import _
+from odoo.exceptions import UserError
+
+from odoo.addons.payment.models.payment_acquirer import _partner_split_name
+
+_logger = logging.getLogger(__name__)
+
+
+class AuthorizeAPI():
+ """Authorize.net Gateway API integration.
+
+ This class allows contacting the Authorize.net API with simple operation
+ requests. It implements a *very limited* subset of the complete API
+ (http://developer.authorize.net/api/reference); namely:
+ - Customer Profile/Payment Profile creation
+ - Transaction authorization/capture/voiding
+ """
+
+ AUTH_ERROR_STATUS = 3
+
+ def __init__(self, acquirer):
+ """Initiate the environment with the acquirer data.
+
+ :param record acquirer: payment.acquirer account that will be contacted
+ """
+ if acquirer.state == 'test':
+ self.url = 'https://apitest.authorize.net/xml/v1/request.api'
+ else:
+ self.url = 'https://api.authorize.net/xml/v1/request.api'
+
+ self.state = acquirer.state
+ self.name = acquirer.authorize_login
+ self.transaction_key = acquirer.authorize_transaction_key
+
+ def _authorize_request(self, data):
+ _logger.info('_authorize_request: Sending values to URL %s, values:\n%s', self.url, data)
+ resp = requests.post(self.url, json.dumps(data))
+ resp.raise_for_status()
+ resp = json.loads(resp.content)
+ _logger.info("_authorize_request: Received response:\n%s", resp)
+ messages = resp.get('messages')
+ if messages and messages.get('resultCode') == 'Error':
+ return {
+ 'err_code': messages.get('message')[0].get('code'),
+ 'err_msg': messages.get('message')[0].get('text')
+ }
+
+ return resp
+
+ # Customer profiles
+ def create_customer_profile(self, partner, opaqueData):
+ """Create a payment and customer profile in the Authorize.net backend.
+
+ Creates a customer profile for the partner/credit card combination and links
+ a corresponding payment profile to it. Note that a single partner in the Odoo
+ database can have multiple customer profiles in Authorize.net (i.e. a customer
+ profile is created for every res.partner/payment.token couple).
+
+ :param record partner: the res.partner record of the customer
+ :param str cardnumber: cardnumber in string format (numbers only, no separator)
+ :param str expiration_date: expiration date in 'YYYY-MM' string format
+ :param str card_code: three- or four-digit verification number
+
+ :return: a dict containing the profile_id and payment_profile_id of the
+ newly created customer profile and payment profile
+ :rtype: dict
+ """
+ values = {
+ 'createCustomerProfileRequest': {
+ 'merchantAuthentication': {
+ 'name': self.name,
+ 'transactionKey': self.transaction_key
+ },
+ 'profile': {
+ 'description': ('ODOO-%s-%s' % (partner.id, uuid4().hex[:8]))[:20],
+ 'email': partner.email or '',
+ 'paymentProfiles': {
+ 'customerType': 'business' if partner.is_company else 'individual',
+ 'billTo': {
+ 'firstName': '' if partner.is_company else _partner_split_name(partner.name)[0],
+ 'lastName': _partner_split_name(partner.name)[1],
+ 'address': (partner.street or '' + (partner.street2 if partner.street2 else '')) or None,
+ 'city': partner.city,
+ 'state': partner.state_id.name or None,
+ 'zip': partner.zip or '',
+ 'country': partner.country_id.name or None,
+ 'phoneNumber': partner.phone or '',
+ },
+ 'payment': {
+ 'opaqueData': {
+ 'dataDescriptor': opaqueData.get('dataDescriptor'),
+ 'dataValue': opaqueData.get('dataValue')
+ }
+ }
+ }
+ },
+ 'validationMode': 'liveMode' if self.state == 'enabled' else 'testMode'
+ }
+ }
+
+ response = self._authorize_request(values)
+
+ if response and response.get('err_code'):
+ raise UserError(_(
+ "Authorize.net Error:\nCode: %s\nMessage: %s",
+ response.get('err_code'), response.get('err_msg'),
+ ))
+
+ return {
+ 'profile_id': response.get('customerProfileId'),
+ 'payment_profile_id': response.get('customerPaymentProfileIdList')[0]
+ }
+
+ def create_customer_profile_from_tx(self, partner, transaction_id):
+ """Create an Auth.net payment/customer profile from an existing transaction.
+
+ Creates a customer profile for the partner/credit card combination and links
+ a corresponding payment profile to it. Note that a single partner in the Odoo
+ database can have multiple customer profiles in Authorize.net (i.e. a customer
+ profile is created for every res.partner/payment.token couple).
+
+ Note that this function makes 2 calls to the authorize api, since we need to
+ obtain a partial cardnumber to generate a meaningful payment.token name.
+
+ :param record partner: the res.partner record of the customer
+ :param str transaction_id: id of the authorized transaction in the
+ Authorize.net backend
+
+ :return: a dict containing the profile_id and payment_profile_id of the
+ newly created customer profile and payment profile as well as the
+ last digits of the card number
+ :rtype: dict
+ """
+ values = {
+ 'createCustomerProfileFromTransactionRequest': {
+ "merchantAuthentication": {
+ "name": self.name,
+ "transactionKey": self.transaction_key
+ },
+ 'transId': transaction_id,
+ 'customer': {
+ 'merchantCustomerId': ('ODOO-%s-%s' % (partner.id, uuid4().hex[:8]))[:20],
+ 'email': partner.email or ''
+ }
+ }
+ }
+
+ response = self._authorize_request(values)
+
+ if not response.get('customerProfileId'):
+ _logger.warning(
+ 'Unable to create customer payment profile, data missing from transaction. Transaction_id: %s - Partner_id: %s'
+ % (transaction_id, partner)
+ )
+ return False
+
+ res = {
+ 'profile_id': response.get('customerProfileId'),
+ 'payment_profile_id': response.get('customerPaymentProfileIdList')[0]
+ }
+
+ values = {
+ 'getCustomerPaymentProfileRequest': {
+ "merchantAuthentication": {
+ "name": self.name,
+ "transactionKey": self.transaction_key
+ },
+ 'customerProfileId': res['profile_id'],
+ 'customerPaymentProfileId': res['payment_profile_id'],
+ }
+ }
+
+ response = self._authorize_request(values)
+
+ res['name'] = response.get('paymentProfile', {}).get('payment', {}).get('creditCard', {}).get('cardNumber')
+ return res
+
+ # Transaction management
+ def auth_and_capture(self, token, amount, reference):
+ """Authorize and capture a payment for the given amount.
+
+ Authorize and immediately capture a payment for the given payment.token
+ record for the specified amount with reference as communication.
+
+ :param record token: the payment.token record that must be charged
+ :param str amount: transaction amount (up to 15 digits with decimal point)
+ :param str reference: used as "invoiceNumber" in the Authorize.net backend
+
+ :return: a dict containing the response code, transaction id and transaction type
+ :rtype: dict
+ """
+ values = {
+ 'createTransactionRequest': {
+ "merchantAuthentication": {
+ "name": self.name,
+ "transactionKey": self.transaction_key
+ },
+ 'transactionRequest': {
+ 'transactionType': 'authCaptureTransaction',
+ 'amount': str(amount),
+ 'profile': {
+ 'customerProfileId': token.authorize_profile,
+ 'paymentProfile': {
+ 'paymentProfileId': token.acquirer_ref,
+ }
+ },
+ 'order': {
+ 'invoiceNumber': reference[:20],
+ 'description': reference[:255],
+ }
+ }
+
+ }
+ }
+ response = self._authorize_request(values)
+
+ if response and response.get('err_code'):
+ return {
+ 'x_response_code': self.AUTH_ERROR_STATUS,
+ 'x_response_reason_text': response.get('err_msg')
+ }
+
+ result = {
+ 'x_response_code': response.get('transactionResponse', {}).get('responseCode'),
+ 'x_trans_id': response.get('transactionResponse', {}).get('transId'),
+ 'x_type': 'auth_capture'
+ }
+ errors = response.get('transactionResponse', {}).get('errors')
+ if errors:
+ result['x_response_reason_text'] = '\n'.join([e.get('errorText') for e in errors])
+ return result
+
+ def authorize(self, token, amount, reference):
+ """Authorize a payment for the given amount.
+
+ Authorize (without capture) a payment for the given payment.token
+ record for the specified amount with reference as communication.
+
+ :param record token: the payment.token record that must be charged
+ :param str amount: transaction amount (up to 15 digits with decimal point)
+ :param str reference: used as "invoiceNumber" in the Authorize.net backend
+
+ :return: a dict containing the response code, transaction id and transaction type
+ :rtype: dict
+ """
+ values = {
+ 'createTransactionRequest': {
+ "merchantAuthentication": {
+ "name": self.name,
+ "transactionKey": self.transaction_key
+ },
+ 'transactionRequest': {
+ 'transactionType': 'authOnlyTransaction',
+ 'amount': str(amount),
+ 'profile': {
+ 'customerProfileId': token.authorize_profile,
+ 'paymentProfile': {
+ 'paymentProfileId': token.acquirer_ref,
+ }
+ },
+ 'order': {
+ 'invoiceNumber': reference[:20],
+ 'description': reference[:255],
+ }
+ }
+
+ }
+ }
+ response = self._authorize_request(values)
+
+ if response and response.get('err_code'):
+ return {
+ 'x_response_code': self.AUTH_ERROR_STATUS,
+ 'x_response_reason_text': response.get('err_msg')
+ }
+
+ return {
+ 'x_response_code': response.get('transactionResponse', {}).get('responseCode'),
+ 'x_trans_id': response.get('transactionResponse', {}).get('transId'),
+ 'x_type': 'auth_only'
+ }
+
+ def capture(self, transaction_id, amount):
+ """Capture a previously authorized payment for the given amount.
+
+ Capture a previsouly authorized payment. Note that the amount is required
+ even though we do not support partial capture.
+
+ :param str transaction_id: id of the authorized transaction in the
+ Authorize.net backend
+ :param str amount: transaction amount (up to 15 digits with decimal point)
+
+ :return: a dict containing the response code, transaction id and transaction type
+ :rtype: dict
+ """
+ values = {
+ 'createTransactionRequest': {
+ "merchantAuthentication": {
+ "name": self.name,
+ "transactionKey": self.transaction_key
+ },
+ 'transactionRequest': {
+ 'transactionType': 'priorAuthCaptureTransaction',
+ 'amount': str(amount),
+ 'refTransId': transaction_id,
+ }
+ }
+ }
+
+ response = self._authorize_request(values)
+
+ if response and response.get('err_code'):
+ return {
+ 'x_response_code': self.AUTH_ERROR_STATUS,
+ 'x_response_reason_text': response.get('err_msg')
+ }
+
+ return {
+ 'x_response_code': response.get('transactionResponse', {}).get('responseCode'),
+ 'x_trans_id': response.get('transactionResponse', {}).get('transId'),
+ 'x_type': 'prior_auth_capture'
+ }
+
+ def void(self, transaction_id):
+ """Void a previously authorized payment.
+
+ :param str transaction_id: the id of the authorized transaction in the
+ Authorize.net backend
+
+ :return: a dict containing the response code, transaction id and transaction type
+ :rtype: dict
+ """
+ values = {
+ 'createTransactionRequest': {
+ "merchantAuthentication": {
+ "name": self.name,
+ "transactionKey": self.transaction_key
+ },
+ 'transactionRequest': {
+ 'transactionType': 'voidTransaction',
+ 'refTransId': transaction_id
+ }
+ }
+ }
+
+ response = self._authorize_request(values)
+
+ if response and response.get('err_code'):
+ return {
+ 'x_response_code': self.AUTH_ERROR_STATUS,
+ 'x_response_reason_text': response.get('err_msg')
+ }
+
+ return {
+ 'x_response_code': response.get('transactionResponse', {}).get('responseCode'),
+ 'x_trans_id': response.get('transactionResponse', {}).get('transId'),
+ 'x_type': 'void'
+ }
+
+ # Test
+ def test_authenticate(self):
+ """Test Authorize.net communication with a simple credentials check.
+
+ :return: True if authentication was successful, else False (or throws an error)
+ :rtype: bool
+ """
+ values = {
+ 'authenticateTestRequest': {
+ "merchantAuthentication": {
+ "name": self.name,
+ "transactionKey": self.transaction_key
+ },
+ }
+ }
+
+ response = self._authorize_request(values)
+ if response and response.get('err_code'):
+ return False
+ return True
+
+ # Client Key
+ def get_client_secret(self):
+ """ Create a client secret that will be needed for the AcceptJS integration. """
+ values = {
+ "getMerchantDetailsRequest": {
+ "merchantAuthentication": {
+ "name": self.name,
+ "transactionKey": self.transaction_key,
+ }
+ }
+ }
+ response = self._authorize_request(values)
+ client_secret = response.get('publicClientKey')
+ return client_secret
diff --git a/addons/payment_authorize/models/payment.py b/addons/payment_authorize/models/payment.py
new file mode 100644
index 00000000..c4146253
--- /dev/null
+++ b/addons/payment_authorize/models/payment.py
@@ -0,0 +1,335 @@
+# coding: utf-8
+from werkzeug import urls
+
+from .authorize_request import AuthorizeAPI
+import hashlib
+import hmac
+import logging
+import time
+
+from odoo import _, api, fields, models
+from odoo.addons.payment.models.payment_acquirer import ValidationError
+from odoo.addons.payment_authorize.controllers.main import AuthorizeController
+from odoo.tools.float_utils import float_compare, float_repr
+from odoo.exceptions import UserError
+
+_logger = logging.getLogger(__name__)
+
+
+class PaymentAcquirerAuthorize(models.Model):
+ _inherit = 'payment.acquirer'
+
+ provider = fields.Selection(selection_add=[
+ ('authorize', 'Authorize.Net')
+ ], ondelete={'authorize': 'set default'})
+ authorize_login = fields.Char(string='API Login Id', required_if_provider='authorize', groups='base.group_user')
+ authorize_transaction_key = fields.Char(string='API Transaction Key', required_if_provider='authorize', groups='base.group_user')
+ authorize_signature_key = fields.Char(string='API Signature Key', required_if_provider='authorize', groups='base.group_user')
+ authorize_client_key = fields.Char(string='API Client Key', groups='base.group_user')
+
+ @api.onchange('provider', 'check_validity')
+ def onchange_check_validity(self):
+ if self.provider == 'authorize' and self.check_validity:
+ self.check_validity = False
+ return {'warning': {
+ 'title': _("Warning"),
+ 'message': ('This option is not supported for Authorize.net')}}
+
+ def action_client_secret(self):
+ api = AuthorizeAPI(self)
+ if not api.test_authenticate():
+ raise UserError(_('Unable to fetch Client Key, make sure the API Login and Transaction Key are correct.'))
+ self.authorize_client_key = api.get_client_secret()
+ return True
+
+ def _get_feature_support(self):
+ """Get advanced feature support by provider.
+
+ Each provider should add its technical in the corresponding
+ key for the following features:
+ * fees: support payment fees computations
+ * authorize: support authorizing payment (separates
+ authorization and capture)
+ * tokenize: support saving payment data in a payment.tokenize
+ object
+ """
+ res = super(PaymentAcquirerAuthorize, self)._get_feature_support()
+ res['authorize'].append('authorize')
+ res['tokenize'].append('authorize')
+ return res
+
+ def _get_authorize_urls(self, environment):
+ """ Authorize URLs """
+ if environment == 'prod':
+ return {'authorize_form_url': 'https://secure2.authorize.net/gateway/transact.dll'}
+ else:
+ return {'authorize_form_url': 'https://test.authorize.net/gateway/transact.dll'}
+
+ def _authorize_generate_hashing(self, values):
+ data = '^'.join([
+ values['x_login'],
+ values['x_fp_sequence'],
+ values['x_fp_timestamp'],
+ values['x_amount'],
+ values['x_currency_code']]).encode('utf-8')
+
+ return hmac.new(bytes.fromhex(self.authorize_signature_key), data, hashlib.sha512).hexdigest().upper()
+
+ def authorize_form_generate_values(self, values):
+ self.ensure_one()
+ # State code is only supported in US, use state name by default
+ # See https://developer.authorize.net/api/reference/
+ state = values['partner_state'].name if values.get('partner_state') else ''
+ if values.get('partner_country') and values.get('partner_country') == self.env.ref('base.us', False):
+ state = values['partner_state'].code if values.get('partner_state') else ''
+ billing_state = values['billing_partner_state'].name if values.get('billing_partner_state') else ''
+ if values.get('billing_partner_country') and values.get('billing_partner_country') == self.env.ref('base.us', False):
+ billing_state = values['billing_partner_state'].code if values.get('billing_partner_state') else ''
+
+ base_url = self.get_base_url()
+ authorize_tx_values = dict(values)
+ temp_authorize_tx_values = {
+ 'x_login': self.authorize_login,
+ 'x_amount': float_repr(values['amount'], values['currency'].decimal_places if values['currency'] else 2),
+ 'x_show_form': 'PAYMENT_FORM',
+ 'x_type': 'AUTH_CAPTURE' if not self.capture_manually else 'AUTH_ONLY',
+ 'x_method': 'CC',
+ 'x_fp_sequence': '%s%s' % (self.id, int(time.time())),
+ 'x_version': '3.1',
+ 'x_relay_response': 'TRUE',
+ 'x_fp_timestamp': str(int(time.time())),
+ 'x_relay_url': urls.url_join(base_url, AuthorizeController._return_url),
+ 'x_cancel_url': urls.url_join(base_url, AuthorizeController._cancel_url),
+ 'x_currency_code': values['currency'] and values['currency'].name or '',
+ 'address': values.get('partner_address'),
+ 'city': values.get('partner_city'),
+ 'country': values.get('partner_country') and values.get('partner_country').name or '',
+ 'email': values.get('partner_email'),
+ 'zip_code': values.get('partner_zip'),
+ 'first_name': values.get('partner_first_name'),
+ 'last_name': values.get('partner_last_name'),
+ 'phone': values.get('partner_phone'),
+ 'state': state,
+ 'billing_address': values.get('billing_partner_address'),
+ 'billing_city': values.get('billing_partner_city'),
+ 'billing_country': values.get('billing_partner_country') and values.get('billing_partner_country').name or '',
+ 'billing_email': values.get('billing_partner_email'),
+ 'billing_zip_code': values.get('billing_partner_zip'),
+ 'billing_first_name': values.get('billing_partner_first_name'),
+ 'billing_last_name': values.get('billing_partner_last_name'),
+ 'billing_phone': values.get('billing_partner_phone'),
+ 'billing_state': billing_state,
+ }
+ temp_authorize_tx_values['returndata'] = authorize_tx_values.pop('return_url', '')
+ temp_authorize_tx_values['x_fp_hash'] = self._authorize_generate_hashing(temp_authorize_tx_values)
+ authorize_tx_values.update(temp_authorize_tx_values)
+ return authorize_tx_values
+
+ def authorize_get_form_action_url(self):
+ self.ensure_one()
+ environment = 'prod' if self.state == 'enabled' else 'test'
+ return self._get_authorize_urls(environment)['authorize_form_url']
+
+ @api.model
+ def authorize_s2s_form_process(self, data):
+ values = {
+ 'opaqueData': data.get('opaqueData'),
+ 'encryptedCardData': data.get('encryptedCardData'),
+ 'acquirer_id': int(data.get('acquirer_id')),
+ 'partner_id': int(data.get('partner_id'))
+ }
+ PaymentMethod = self.env['payment.token'].sudo().create(values)
+ return PaymentMethod
+
+ def authorize_s2s_form_validate(self, data):
+ error = dict()
+ mandatory_fields = ["opaqueData", "encryptedCardData"]
+ # Validation
+ for field_name in mandatory_fields:
+ if not data.get(field_name):
+ error[field_name] = 'missing'
+ return False if error else True
+
+ def authorize_test_credentials(self):
+ self.ensure_one()
+ transaction = AuthorizeAPI(self.acquirer_id)
+ return transaction.test_authenticate()
+
+class TxAuthorize(models.Model):
+ _inherit = 'payment.transaction'
+
+ _authorize_valid_tx_status = 1
+ _authorize_pending_tx_status = 4
+ _authorize_cancel_tx_status = 2
+ _authorize_error_tx_status = 3
+
+ # --------------------------------------------------
+ # FORM RELATED METHODS
+ # --------------------------------------------------
+
+ @api.model
+ def _authorize_form_get_tx_from_data(self, data):
+ """ Given a data dict coming from authorize, verify it and find the related
+ transaction record. """
+ reference, description, trans_id, fingerprint = data.get('x_invoice_num'), data.get('x_description'), data.get('x_trans_id'), data.get('x_SHA2_Hash') or data.get('x_MD5_Hash')
+ if not reference or not trans_id or not fingerprint:
+ error_msg = _('Authorize: received data with missing reference (%s) or trans_id (%s) or fingerprint (%s)') % (reference, trans_id, fingerprint)
+ _logger.info(error_msg)
+ raise ValidationError(error_msg)
+ tx = self.search(['|', ('reference', '=', reference), ('reference', '=', description)])
+ if not tx or len(tx) > 1:
+ error_msg = 'Authorize: received data for x_invoice_num %s and x_description %s' % (reference, description)
+ if not tx:
+ error_msg += '; no order found'
+ else:
+ error_msg += '; multiple order found'
+ _logger.info(error_msg)
+ raise ValidationError(error_msg)
+ return tx[0]
+
+ def _authorize_form_get_invalid_parameters(self, data):
+ invalid_parameters = []
+
+ if self.acquirer_reference and data.get('x_trans_id') != self.acquirer_reference:
+ invalid_parameters.append(('Transaction Id', data.get('x_trans_id'), self.acquirer_reference))
+ # check what is buyed
+ if float_compare(float(data.get('x_amount', '0.0')), self.amount, 2) != 0:
+ invalid_parameters.append(('Amount', data.get('x_amount'), '%.2f' % self.amount))
+ return invalid_parameters
+
+ def _authorize_form_validate(self, data):
+ if self.state == 'done':
+ _logger.warning('Authorize: trying to validate an already validated tx (ref %s)' % self.reference)
+ return True
+ status_code = int(data.get('x_response_code', '0'))
+ if status_code == self._authorize_valid_tx_status:
+ if data.get('x_type').lower() in ['auth_capture', 'prior_auth_capture']:
+ self.write({
+ 'acquirer_reference': data.get('x_trans_id'),
+ 'date': fields.Datetime.now(),
+ })
+ self._set_transaction_done()
+ elif data.get('x_type').lower() in ['auth_only']:
+ self.write({'acquirer_reference': data.get('x_trans_id')})
+ self._set_transaction_authorized()
+ if self.partner_id and not self.payment_token_id and \
+ (self.type == 'form_save' or self.acquirer_id.save_token == 'always'):
+ transaction = AuthorizeAPI(self.acquirer_id)
+ res = transaction.create_customer_profile_from_tx(self.partner_id, self.acquirer_reference)
+ if res:
+ token_id = self.env['payment.token'].create({
+ 'authorize_profile': res.get('profile_id'),
+ 'name': res.get('name'),
+ 'acquirer_ref': res.get('payment_profile_id'),
+ 'acquirer_id': self.acquirer_id.id,
+ 'partner_id': self.partner_id.id,
+ })
+ self.payment_token_id = token_id
+ return True
+ elif status_code == self._authorize_pending_tx_status:
+ self.write({'acquirer_reference': data.get('x_trans_id')})
+ self._set_transaction_pending()
+ return True
+ else:
+ error = data.get('x_response_reason_text')
+ _logger.info(error)
+ self.write({
+ 'state_message': error,
+ 'acquirer_reference': data.get('x_trans_id'),
+ })
+ self._set_transaction_cancel()
+ return False
+
+ def authorize_s2s_do_transaction(self, **data):
+ self.ensure_one()
+ transaction = AuthorizeAPI(self.acquirer_id)
+
+ if not self.payment_token_id.authorize_profile:
+ raise UserError(_('Invalid token found: the Authorize profile is missing.'
+ 'Please make sure the token has a valid acquirer reference.'))
+
+ if not self.acquirer_id.capture_manually:
+ res = transaction.auth_and_capture(self.payment_token_id, round(self.amount, self.currency_id.decimal_places), self.reference)
+ else:
+ res = transaction.authorize(self.payment_token_id, round(self.amount, self.currency_id.decimal_places), self.reference)
+ return self._authorize_s2s_validate_tree(res)
+
+ def authorize_s2s_capture_transaction(self):
+ self.ensure_one()
+ transaction = AuthorizeAPI(self.acquirer_id)
+ tree = transaction.capture(self.acquirer_reference or '', round(self.amount, self.currency_id.decimal_places))
+ return self._authorize_s2s_validate_tree(tree)
+
+ def authorize_s2s_void_transaction(self):
+ self.ensure_one()
+ transaction = AuthorizeAPI(self.acquirer_id)
+ tree = transaction.void(self.acquirer_reference or '')
+ return self._authorize_s2s_validate_tree(tree)
+
+ def _authorize_s2s_validate_tree(self, tree):
+ return self._authorize_s2s_validate(tree)
+
+ def _authorize_s2s_validate(self, tree):
+ if self.state == 'done':
+ _logger.warning('Authorize: trying to validate an already validated tx (ref %s)' % self.reference)
+ return True
+ status_code = int(tree.get('x_response_code', '0'))
+ if status_code == self._authorize_valid_tx_status:
+ if tree.get('x_type').lower() in ['auth_capture', 'prior_auth_capture']:
+ init_state = self.state
+ self.write({
+ 'acquirer_reference': tree.get('x_trans_id'),
+ 'date': fields.Datetime.now(),
+ })
+
+ self._set_transaction_done()
+
+ if init_state != 'authorized':
+ self.execute_callback()
+ if tree.get('x_type').lower() == 'auth_only':
+ self.write({'acquirer_reference': tree.get('x_trans_id')})
+ self._set_transaction_authorized()
+ self.execute_callback()
+ if tree.get('x_type').lower() == 'void':
+ self._set_transaction_cancel()
+ return True
+ elif status_code == self._authorize_pending_tx_status:
+ self.write({'acquirer_reference': tree.get('x_trans_id')})
+ self._set_transaction_pending()
+ return True
+ else:
+ error = tree.get('x_response_reason_text')
+ _logger.info(error)
+ self.write({
+ 'acquirer_reference': tree.get('x_trans_id'),
+ })
+ self._set_transaction_error(msg=error)
+ return False
+
+
+class PaymentToken(models.Model):
+ _inherit = 'payment.token'
+
+ authorize_profile = fields.Char(string='Authorize.net Profile ID', help='This contains the unique reference '
+ 'for this partner/payment token combination in the Authorize.net backend')
+ provider = fields.Selection(string='Provider', related='acquirer_id.provider', readonly=False)
+ save_token = fields.Selection(string='Save Cards', related='acquirer_id.save_token', readonly=False)
+
+ @api.model
+ def authorize_create(self, values):
+ if values.get('opaqueData') and values.get('encryptedCardData'):
+ acquirer = self.env['payment.acquirer'].browse(values['acquirer_id'])
+ partner = self.env['res.partner'].browse(values['partner_id'])
+ transaction = AuthorizeAPI(acquirer)
+ res = transaction.create_customer_profile(partner, values['opaqueData'])
+ if res.get('profile_id') and res.get('payment_profile_id'):
+ return {
+ 'authorize_profile': res.get('profile_id'),
+ 'name': values['encryptedCardData'].get('cardNumber'),
+ 'acquirer_ref': res.get('payment_profile_id'),
+ 'verified': True
+ }
+ else:
+ raise ValidationError(_('The Customer Profile creation in Authorize.NET failed.'))
+ else:
+ return values