This commit fixes all issues detected by the new pylint
gettext-variable test.
It converts some calls to the new syntax
_("Foo %s", bar)
to progressively migrate the code to the new syntax.
A few calls were not technically incorrect but still detected by the
linter.
_("Foo" +
"Bar")
has been converted to
_("Foo"
"Bar")
as it has the same effect and make sure the argument is of type
asteroid.Const instead of BinOp).
closes odoo/odoo#53683
Related: odoo/enterprise#11467
Signed-off-by: Raphael Collet (rco) <rco@openerp.com>
400 lines
15 KiB
Python
400 lines
15 KiB
Python
# -*- 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]
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
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]
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
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
|