With this commit, Selection fields with `required=True` which are extended via `selection_add` are given proper ondelete policies to ensure the cleanup of records containing these extended options during uninstall of the extending module. This commit also cleans up leftover uninstall hooks that were being used to handle the same set of problems prior to the ondelete mechanism being implemented for Selection fields. closes odoo/odoo#46325 Related: odoo/enterprise#9117 Signed-off-by: Raphael Collet (rco) <rco@openerp.com>
227 lines
10 KiB
Python
227 lines
10 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import logging
|
|
|
|
from hashlib import md5
|
|
from werkzeug import urls
|
|
|
|
from odoo import api, fields, models, _
|
|
from odoo.tools.float_utils import float_compare
|
|
from odoo.addons.payment_alipay.controllers.main import AlipayController
|
|
from odoo.addons.payment.models.payment_acquirer import ValidationError
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class PaymentAcquirer(models.Model):
|
|
_inherit = 'payment.acquirer'
|
|
|
|
provider = fields.Selection(selection_add=[
|
|
('alipay', 'Alipay')
|
|
], ondelete={'alipay': 'set default'})
|
|
alipay_payment_method = fields.Selection([
|
|
('express_checkout', 'Express Checkout (only for Chinese Merchant)'),
|
|
('standard_checkout', 'Cross-border'),
|
|
], string='Account', default='express_checkout',
|
|
help=" * Cross-border: For the Overseas seller \n * Express Checkout: For the Chinese Seller")
|
|
alipay_merchant_partner_id = fields.Char(
|
|
string='Merchant Partner ID', required_if_provider='alipay', groups='base.group_user',
|
|
help='The Merchant Partner ID is used to ensure communications coming from Alipay are valid and secured.')
|
|
alipay_md5_signature_key = fields.Char(
|
|
string='MD5 Signature Key', required_if_provider='alipay', groups='base.group_user',
|
|
help="The MD5 private key is the 32-byte string which is composed of English letters and numbers.")
|
|
alipay_seller_email = fields.Char(string='Alipay Seller Email', groups='base.group_user')
|
|
|
|
def _get_feature_support(self):
|
|
res = super(PaymentAcquirer, self)._get_feature_support()
|
|
res['fees'].append('alipay')
|
|
return res
|
|
|
|
@api.model
|
|
def _get_alipay_urls(self, environment):
|
|
""" Alipay URLS """
|
|
if environment == 'prod':
|
|
return 'https://mapi.alipay.com/gateway.do'
|
|
return 'https://openapi.alipaydev.com/gateway.do'
|
|
|
|
def alipay_compute_fees(self, amount, currency_id, country_id):
|
|
""" Compute alipay fees.
|
|
|
|
:param float amount: the amount to pay
|
|
:param integer country_id: an ID of a res.country, or None. This is
|
|
the customer's country, to be compared to
|
|
the acquirer company country.
|
|
:return float fees: computed fees
|
|
"""
|
|
fees = 0.0
|
|
if self.fees_active:
|
|
country = self.env['res.country'].browse(country_id)
|
|
if country and self.company_id.country_id.id == country.id:
|
|
percentage = self.fees_dom_var
|
|
fixed = self.fees_dom_fixed
|
|
else:
|
|
percentage = self.fees_int_var
|
|
fixed = self.fees_int_fixed
|
|
fees = (percentage / 100.0 * amount + fixed) / (1 - percentage / 100.0)
|
|
return fees
|
|
|
|
def _build_sign(self, val):
|
|
# Rearrange parameters in the data set alphabetically
|
|
data_to_sign = sorted(val.items())
|
|
# Exclude parameters that should not be signed
|
|
data_to_sign = ["{}={}".format(k, v) for k, v in data_to_sign if k not in ['sign', 'sign_type', 'reference']]
|
|
# And connect rearranged parameters with &
|
|
data_string = '&'.join(data_to_sign)
|
|
data_string += self.alipay_md5_signature_key
|
|
return md5(data_string.encode('utf-8')).hexdigest()
|
|
|
|
def _get_alipay_tx_values(self, values):
|
|
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
|
|
|
|
alipay_tx_values = ({
|
|
'_input_charset': 'utf-8',
|
|
'notify_url': urls.url_join(base_url, AlipayController._notify_url),
|
|
'out_trade_no': values.get('reference'),
|
|
'partner': self.alipay_merchant_partner_id,
|
|
'return_url': urls.url_join(base_url, AlipayController._return_url),
|
|
'subject': values.get('reference'),
|
|
'total_fee': values.get('amount') + values.get('fees'),
|
|
})
|
|
if self.alipay_payment_method == 'standard_checkout':
|
|
alipay_tx_values.update({
|
|
'service': 'create_forex_trade',
|
|
'product_code': 'NEW_OVERSEAS_SELLER',
|
|
'currency': values.get('currency').name,
|
|
})
|
|
else:
|
|
alipay_tx_values.update({
|
|
'service': 'create_direct_pay_by_user',
|
|
'payment_type': 1,
|
|
'seller_email': self.alipay_seller_email,
|
|
})
|
|
sign = self._build_sign(alipay_tx_values)
|
|
alipay_tx_values.update({
|
|
'sign_type': 'MD5',
|
|
'sign': sign,
|
|
})
|
|
return alipay_tx_values
|
|
|
|
def alipay_form_generate_values(self, values):
|
|
values.update(self._get_alipay_tx_values(values))
|
|
return values
|
|
|
|
def alipay_get_form_action_url(self):
|
|
self.ensure_one()
|
|
environment = 'prod' if self.state == 'enabled' else 'test'
|
|
return self._get_alipay_urls(environment)
|
|
|
|
|
|
class PaymentTransaction(models.Model):
|
|
_inherit = 'payment.transaction'
|
|
|
|
def _check_alipay_configuration(self, vals):
|
|
acquirer_id = int(vals.get('acquirer_id'))
|
|
acquirer = self.env['payment.acquirer'].sudo().browse(acquirer_id)
|
|
if acquirer and acquirer.provider == 'alipay' and acquirer.alipay_payment_method == 'express_checkout':
|
|
currency_id = int(vals.get('currency_id'))
|
|
if currency_id:
|
|
currency = self.env['res.currency'].sudo().browse(currency_id)
|
|
if currency and currency.name != 'CNY':
|
|
_logger.info("Only CNY currency is allowed for Alipay Express Checkout")
|
|
raise ValidationError(_("""
|
|
Only transactions in Chinese Yuan (CNY) are allowed for Alipay Express Checkout.\n
|
|
If you wish to use another currency than CNY for your transactions, switch your
|
|
configuration to a Cross-border account on the Alipay payment acquirer in Odoo.
|
|
"""))
|
|
return True
|
|
|
|
def write(self, vals):
|
|
if vals.get('currency_id') or vals.get('acquirer_id'):
|
|
for payment in self:
|
|
check_vals = {
|
|
'acquirer_id': vals.get('acquirer_id', payment.acquirer_id.id),
|
|
'currency_id': vals.get('currency_id', payment.currency_id.id)
|
|
}
|
|
payment._check_alipay_configuration(check_vals)
|
|
return super(PaymentTransaction, self).write(vals)
|
|
|
|
@api.model
|
|
def create(self, vals):
|
|
self._check_alipay_configuration(vals)
|
|
return super(PaymentTransaction, self).create(vals)
|
|
|
|
# --------------------------------------------------
|
|
# FORM RELATED METHODS
|
|
# --------------------------------------------------
|
|
|
|
@api.model
|
|
def _alipay_form_get_tx_from_data(self, data):
|
|
reference, txn_id, sign = data.get('reference'), data.get('trade_no'), data.get('sign')
|
|
if not reference or not txn_id:
|
|
_logger.info('Alipay: received data with missing reference (%s) or txn_id (%s)' % (reference, txn_id))
|
|
raise ValidationError(_('Alipay: received data with missing reference (%s) or txn_id (%s)') % (reference, txn_id))
|
|
|
|
txs = self.env['payment.transaction'].search([('reference', '=', reference)])
|
|
if not txs or len(txs) > 1:
|
|
error_msg = _('Alipay: received data for reference %s') % (reference)
|
|
logger_msg = 'Alipay: received data for reference %s' % (reference)
|
|
if not txs:
|
|
error_msg += _('; no order found')
|
|
logger_msg += '; no order found'
|
|
else:
|
|
error_msg += _('; multiple order found')
|
|
logger_msg += '; multiple order found'
|
|
_logger.info(logger_msg)
|
|
raise ValidationError(error_msg)
|
|
|
|
# verify sign
|
|
sign_check = txs.acquirer_id._build_sign(data)
|
|
if sign != sign_check:
|
|
_logger.info('Alipay: invalid sign, received %s, computed %s, for data %s' % (sign, sign_check, data))
|
|
raise ValidationError(_('Alipay: invalid sign, received %s, computed %s, for data %s') % (sign, sign_check, data))
|
|
|
|
return txs
|
|
|
|
def _alipay_form_get_invalid_parameters(self, data):
|
|
invalid_parameters = []
|
|
|
|
if float_compare(float(data.get('total_fee', '0.0')), (self.amount + self.fees), 2) != 0:
|
|
invalid_parameters.append(('total_fee', data.get('total_fee'), '%.2f' % (self.amount + self.fees))) # mc_gross is amount + fees
|
|
if self.acquirer_id.alipay_payment_method == 'standard_checkout':
|
|
if data.get('currency') != self.currency_id.name:
|
|
invalid_parameters.append(('currency', data.get('currency'), self.currency_id.name))
|
|
else:
|
|
if data.get('seller_email') != self.acquirer_id.alipay_seller_email:
|
|
invalid_parameters.append(('seller_email', data.get('seller_email'), self.acquirer_id.alipay_seller_email))
|
|
return invalid_parameters
|
|
|
|
def _alipay_form_validate(self, data):
|
|
if self.state in ['done']:
|
|
_logger.info('Alipay: trying to validate an already validated tx (ref %s)', self.reference)
|
|
return True
|
|
|
|
status = data.get('trade_status')
|
|
res = {
|
|
'acquirer_reference': data.get('trade_no'),
|
|
}
|
|
if status in ['TRADE_FINISHED', 'TRADE_SUCCESS']:
|
|
_logger.info('Validated Alipay payment for tx %s: set as done' % (self.reference))
|
|
date_validate = fields.Datetime.now()
|
|
res.update(date=date_validate)
|
|
self._set_transaction_done()
|
|
self.write(res)
|
|
self.execute_callback()
|
|
return True
|
|
elif status == 'TRADE_CLOSED':
|
|
_logger.info('Received notification for Alipay payment %s: set as Canceled' % (self.reference))
|
|
res.update(state_message=data.get('close_reason', ''))
|
|
self._set_transaction_cancel()
|
|
return self.write(res)
|
|
else:
|
|
error = 'Received unrecognized status for Alipay payment %s: %s, set as error' % (self.reference, status)
|
|
_logger.info(error)
|
|
res.update(state_message=error)
|
|
self._set_transaction_error()
|
|
return self.write(res)
|