[REF] payment_alipay: migrate Alipay to the new payment API
See the merge commit for more details. task-2333027
This commit is contained in:
committed by
Antoine Vandevenne (anv)
parent
a30aa2aeed
commit
6b42234b8b
@@ -1,6 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import controllers
|
||||
from . import models
|
||||
from odoo.addons.payment.models.payment_acquirer import create_missing_journal_for_acquirers
|
||||
|
||||
from odoo.addons.payment import reset_payment_acquirer
|
||||
from odoo.addons.payment.models.payment_acquirer import create_missing_journals # post-init hook
|
||||
|
||||
|
||||
def uninstall_hook(cr, registry):
|
||||
reset_payment_acquirer(cr, registry, 'alipay')
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': 'Alipay Payment Acquirer',
|
||||
'category': 'Accounting/Payment Acquirers',
|
||||
'version': '2.0',
|
||||
'sequence': 345,
|
||||
'summary': 'Payment Acquirer: Alipay Implementation',
|
||||
'description': """Alipay Payment Acquirer""",
|
||||
'depends': ['payment'],
|
||||
'data': [
|
||||
'views/alipay_views.xml',
|
||||
'views/payment_alipay_templates.xml',
|
||||
'views/payment_views.xml',
|
||||
'data/payment_acquirer_data.xml',
|
||||
],
|
||||
'application': True,
|
||||
'post_init_hook': 'create_missing_journal_for_acquirers',
|
||||
'post_init_hook': 'create_missing_journals',
|
||||
'uninstall_hook': 'uninstall_hook',
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import main
|
||||
|
||||
@@ -1,63 +1,62 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
import pprint
|
||||
|
||||
import requests
|
||||
import werkzeug
|
||||
|
||||
from odoo import http
|
||||
from odoo import _, http
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.http import request
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AlipayController(http.Controller):
|
||||
_notify_url = '/payment/alipay/notify'
|
||||
_return_url = '/payment/alipay/return'
|
||||
_notify_url = '/payment/alipay/notify'
|
||||
|
||||
def _alipay_validate_data(self, **post):
|
||||
resp = post.get('trade_status')
|
||||
if resp:
|
||||
if resp in ['TRADE_FINISHED', 'TRADE_SUCCESS']:
|
||||
_logger.info('Alipay: validated data')
|
||||
elif resp == 'TRADE_CLOSED':
|
||||
_logger.warning('Alipay: payment refunded to user and closed the transaction')
|
||||
else:
|
||||
_logger.warning('Alipay: unrecognized alipay answer, received %s instead of TRADE_FINISHED/TRADE_SUCCESS and TRADE_CLOSED' % (post['trade_status']))
|
||||
if post.get('out_trade_no') and post.get('trade_no'):
|
||||
post['reference'] = request.env['payment.transaction'].sudo().search([('reference', '=', post['out_trade_no'])]).reference
|
||||
return request.env['payment.transaction'].sudo().form_feedback(post, 'alipay')
|
||||
return False
|
||||
|
||||
def _alipay_validate_notification(self, **post):
|
||||
if post.get('out_trade_no'):
|
||||
alipay = request.env['payment.transaction'].sudo().search([('reference', '=', post.get('out_trade_no'))]).acquirer_id
|
||||
else:
|
||||
alipay = request.env['payment.acquirer'].sudo().search([('provider', '=', 'alipay')])
|
||||
val = {
|
||||
'service': 'notify_verify',
|
||||
'partner': alipay.alipay_merchant_partner_id,
|
||||
'notify_id': post['notify_id']
|
||||
}
|
||||
response = requests.post(alipay.alipay_get_form_action_url(), val)
|
||||
response.raise_for_status()
|
||||
_logger.info('Validate alipay Notification %s' % response.text)
|
||||
# After program is executed, the page must print “success” (without quote). If not, Alipay server would keep re-sending notification, until over 24 hour 22 minutes Generally, there are 8 notifications within 25 hours (Frequency: 2m,10m,15m,1h,2h,6h,15h)
|
||||
if response.text == 'true':
|
||||
self._alipay_validate_data(**post)
|
||||
return 'success'
|
||||
return ""
|
||||
|
||||
@http.route('/payment/alipay/return', type='http', auth="public", methods=['GET', 'POST'])
|
||||
def alipay_return(self, **post):
|
||||
@http.route(_return_url, type='http', auth="public", methods=['GET'])
|
||||
def alipay_return_from_redirect(self, **data):
|
||||
""" Alipay return """
|
||||
_logger.info('Beginning Alipay form_feedback with post data %s', pprint.pformat(post))
|
||||
self._alipay_validate_data(**post)
|
||||
return werkzeug.utils.redirect('/payment/process')
|
||||
_logger.info("received Alipay return data:\n%s", pprint.pformat(data))
|
||||
request.env['payment.transaction'].sudo()._handle_feedback_data('alipay', data)
|
||||
return werkzeug.utils.redirect('/payment/status')
|
||||
|
||||
@http.route('/payment/alipay/notify', type='http', auth='public', methods=['POST'], csrf=False)
|
||||
@http.route(_notify_url, type='http', auth='public', methods=['POST'], csrf=False)
|
||||
def alipay_notify(self, **post):
|
||||
""" Alipay Notify """
|
||||
_logger.info('Beginning Alipay notification form_feedback with post data %s', pprint.pformat(post))
|
||||
return self._alipay_validate_notification(**post)
|
||||
_logger.info("received Alipay notification data:\n%s", pprint.pformat(post))
|
||||
self._alipay_validate_notification(**post)
|
||||
request.env['payment.transaction'].sudo()._handle_feedback_data('alipay', post)
|
||||
return 'success' # Return 'success' to stop receiving notifications for this tx
|
||||
|
||||
def _alipay_validate_notification(self, **post):
|
||||
tx_sudo = request.env['payment.transaction'].sudo()._get_tx_from_feedback_data(
|
||||
'alipay', post
|
||||
)
|
||||
if not tx_sudo:
|
||||
raise ValidationError(
|
||||
"Alipay: " + _(
|
||||
"Received notification data with unknown reference:\n%s", pprint.pformat(post)
|
||||
)
|
||||
)
|
||||
|
||||
# Ensure that the notification was sent by Alipay
|
||||
# See https://global.alipay.com/docs/ac/wap/async
|
||||
acquirer_sudo = tx_sudo.acquirer_id
|
||||
val = {
|
||||
'service': 'notify_verify',
|
||||
'partner': acquirer_sudo.alipay_merchant_partner_id,
|
||||
'notify_id': post['notify_id']
|
||||
}
|
||||
response = requests.post(acquirer_sudo._alipay_get_api_url(), val, timeout=60)
|
||||
response.raise_for_status()
|
||||
if response.text != 'true':
|
||||
raise ValidationError(
|
||||
"Alipay: " + _(
|
||||
"Received notification data not acknowledged by Alipay:\n%s",
|
||||
pprint.pformat(post)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo noupdate="1">
|
||||
|
||||
<record id="payment.payment_acquirer_alipay" model="payment.acquirer">
|
||||
<field name="name">Alipay</field>
|
||||
<field name="provider">alipay</field>
|
||||
<field name="company_id" ref="base.main_company"/>
|
||||
<field name="view_template_id" ref="alipay_form"/>
|
||||
<field name="redirect_form_view_id" ref="redirect_form"/>
|
||||
<field name="support_authorization">False</field>
|
||||
<field name="support_fees_computation">True</field>
|
||||
<field name="support_tokenization">False</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import payment
|
||||
from . import payment_acquirer
|
||||
from . import payment_transaction
|
||||
|
||||
@@ -1,226 +0,0 @@
|
||||
# -*- 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.sudo().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)
|
||||
@@ -0,0 +1,60 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
from hashlib import md5
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
_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(
|
||||
string="Account",
|
||||
help="* Cross-border: For the overseas seller \n* Express Checkout: For the Chinese Seller",
|
||||
selection=[
|
||||
('express_checkout', 'Express Checkout (only for Chinese merchants)'),
|
||||
('standard_checkout', 'Cross-border')
|
||||
], default='express_checkout', required_if_provider='alipay')
|
||||
alipay_merchant_partner_id = fields.Char(
|
||||
string="Merchant Partner ID",
|
||||
help="The public partner ID solely used to identify the account with Alipay",
|
||||
required_if_provider='alipay')
|
||||
alipay_md5_signature_key = fields.Char(
|
||||
string="MD5 Signature Key", required_if_provider='alipay', groups='base.group_system')
|
||||
alipay_seller_email = fields.Char(
|
||||
string="Alipay Seller Email", help="The public Alipay partner email")
|
||||
|
||||
@api.model
|
||||
def _get_compatible_acquirers(self, *args, currency_id=None, **kwargs):
|
||||
""" Override of payment to unlist Alipay acquirers for unsupported currencies. """
|
||||
acquirers = super()._get_compatible_acquirers(*args, currency_id=currency_id, **kwargs)
|
||||
|
||||
currency = self.env['res.currency'].browse(currency_id).exists()
|
||||
if currency and currency.name != 'CNY':
|
||||
acquirers = acquirers.filtered(
|
||||
lambda a: a.provider != 'alipay' or a.alipay_payment_method != 'express_checkout'
|
||||
)
|
||||
|
||||
return acquirers
|
||||
|
||||
def _alipay_build_sign(self, val):
|
||||
# Rearrange parameters in the data set alphabetically
|
||||
data_to_sign = sorted(val.items())
|
||||
# Format key-value pairs of parameters that should be signed
|
||||
data_to_sign = [f"{k}={v}" for k, v in data_to_sign
|
||||
if k not in ['sign', 'sign_type', 'reference']]
|
||||
# Build the data string of &-separated key-value pairs
|
||||
data_string = '&'.join(data_to_sign)
|
||||
data_string += self.alipay_md5_signature_key
|
||||
return md5(data_string.encode('utf-8')).hexdigest()
|
||||
|
||||
def _alipay_get_api_url(self):
|
||||
if self.state == 'enabled':
|
||||
return 'https://mapi.alipay.com/gateway.do'
|
||||
else: # test environment
|
||||
return 'https://openapi.alipaydev.com/gateway.do'
|
||||
@@ -0,0 +1,168 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
|
||||
from werkzeug import urls
|
||||
|
||||
from odoo import _, api, models
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tools.float_utils import float_compare
|
||||
|
||||
from odoo.addons.payment_alipay.controllers.main import AlipayController
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PaymentTransaction(models.Model):
|
||||
_inherit = 'payment.transaction'
|
||||
|
||||
def _get_specific_rendering_values(self, processing_values):
|
||||
""" Override of payment to return Alipay-specific rendering values.
|
||||
|
||||
Note: self.ensure_one() from `_get_processing_values`
|
||||
|
||||
:param dict processing_values: The generic and specific processing values of the transaction
|
||||
:return: The dict of acquirer-specific processing values
|
||||
:rtype: dict
|
||||
"""
|
||||
res = super()._get_specific_rendering_values(processing_values)
|
||||
if self.provider != 'alipay':
|
||||
return res
|
||||
|
||||
base_url = self.acquirer_id._get_base_url()
|
||||
if self.fees:
|
||||
# Similarly to what is done in `payment::payment.transaction.create`, we need to round
|
||||
# the sum of the amount and of the fees to avoid inconsistent string representations.
|
||||
# E.g., str(1111.11 + 7.09) == '1118.1999999999998'
|
||||
total_fee = self.currency_id.round(self.amount + self.fees)
|
||||
else:
|
||||
total_fee = self.amount
|
||||
rendering_values = {
|
||||
'_input_charset': 'utf-8',
|
||||
'notify_url': urls.url_join(base_url, AlipayController._notify_url),
|
||||
'out_trade_no': self.reference,
|
||||
'partner': self.acquirer_id.alipay_merchant_partner_id,
|
||||
'return_url': urls.url_join(base_url, AlipayController._return_url),
|
||||
'subject': self.reference,
|
||||
'total_fee': total_fee,
|
||||
}
|
||||
if self.acquirer_id.alipay_payment_method == 'standard_checkout':
|
||||
# https://global.alipay.com/docs/ac/global/create_forex_trade
|
||||
rendering_values.update({
|
||||
'service': 'create_forex_trade',
|
||||
'product_code': 'NEW_OVERSEAS_SELLER',
|
||||
'currency': self.currency_id.name,
|
||||
})
|
||||
else:
|
||||
rendering_values.update({
|
||||
'service': 'create_direct_pay_by_user',
|
||||
'payment_type': 1,
|
||||
'seller_email': self.acquirer_id.alipay_seller_email,
|
||||
})
|
||||
|
||||
sign = self.acquirer_id._alipay_build_sign(rendering_values)
|
||||
rendering_values.update({
|
||||
'sign_type': 'MD5',
|
||||
'sign': sign,
|
||||
'api_url': self.acquirer_id._alipay_get_api_url(),
|
||||
})
|
||||
return rendering_values
|
||||
|
||||
@api.model
|
||||
def _get_tx_from_feedback_data(self, provider, data):
|
||||
""" Override of payment to find the transaction based on Alipay data.
|
||||
|
||||
:param str provider: The provider of the acquirer that handled the transaction
|
||||
:param dict data: The feedback data sent by the provider
|
||||
:return: The transaction if found
|
||||
:rtype: recordset of `payment.transaction`
|
||||
:raise: ValidationError if inconsistent data were received
|
||||
:raise: ValidationError if the data match no transaction
|
||||
"""
|
||||
tx = super()._get_tx_from_feedback_data(provider, data)
|
||||
if provider != 'alipay':
|
||||
return tx
|
||||
|
||||
reference = data.get('reference') or data.get('out_trade_no')
|
||||
txn_id = data.get('trade_no')
|
||||
if not reference or not txn_id:
|
||||
raise ValidationError(
|
||||
"Alipay: " + _(
|
||||
"Received data with missing reference %(r)s or txn_id %(t)s.",
|
||||
r=reference, t=txn_id
|
||||
)
|
||||
)
|
||||
|
||||
tx = self.search([('reference', '=', reference), ('provider', '=', 'alipay')])
|
||||
if not tx:
|
||||
raise ValidationError(
|
||||
"Alipay: " + _("No transaction found matching reference %s.", reference)
|
||||
)
|
||||
|
||||
# Verify signature (done here because we need the reference to get the acquirer)
|
||||
sign_check = tx.acquirer_id._alipay_build_sign(data)
|
||||
sign = data.get('sign')
|
||||
if sign != sign_check:
|
||||
raise ValidationError(
|
||||
"Alipay: " + _(
|
||||
"Expected signature %(sc) but received %(sign)s.", sc=sign_check, sign=sign
|
||||
)
|
||||
)
|
||||
|
||||
return tx
|
||||
|
||||
def _process_feedback_data(self, data):
|
||||
""" Override of payment to process the transaction based on Alipay data.
|
||||
|
||||
Note: self.ensure_one()
|
||||
|
||||
:param dict data: The feedback data sent by the provider
|
||||
:return: None
|
||||
:raise: ValidationError if inconsistent data were received
|
||||
"""
|
||||
super()._process_feedback_data(data)
|
||||
if self.provider != 'alipay':
|
||||
return
|
||||
|
||||
if float_compare(float(data.get('total_fee', '0.0')), (self.amount + self.fees), 2) != 0:
|
||||
# mc_gross is amount + fees
|
||||
logging_values = {
|
||||
'amount': data.get('total_fee', '0.0'),
|
||||
'total': self.amount,
|
||||
'fees': self.fees,
|
||||
'reference': self.reference,
|
||||
}
|
||||
_logger.error(
|
||||
"the paid amount (%(amount)s) does not match the total + fees (%(total)s + "
|
||||
"%(fees)s) for the transaction with reference %(reference)s", logging_values
|
||||
)
|
||||
raise ValidationError("Alipay: " + _("The amount does not match the total + fees."))
|
||||
if self.acquirer_id.alipay_payment_method == 'standard_checkout':
|
||||
if data.get('currency') != self.currency_id.name:
|
||||
raise ValidationError(
|
||||
"Alipay: " + _(
|
||||
"The currency returned by Alipay %(rc)s does not match the transaction "
|
||||
"currency %(tc)s.", rc=data.get('currency'), tc=self.currency_id.name
|
||||
)
|
||||
)
|
||||
elif data.get('seller_email') != self.acquirer_id.alipay_seller_email:
|
||||
_logger.error(
|
||||
"the seller email (%s) does not match the configured Alipay account (%s).",
|
||||
data.get('seller_email'), self.acquirer_id.alipay_seller_email
|
||||
)
|
||||
raise ValidationError(
|
||||
"Alipay: " + _("The seller email does not match the configured Alipay account.")
|
||||
)
|
||||
|
||||
self.acquirer_reference = data.get('trade_no')
|
||||
status = data.get('trade_status')
|
||||
if status in ['TRADE_FINISHED', 'TRADE_SUCCESS']:
|
||||
self._set_done()
|
||||
elif status == 'TRADE_CLOSED':
|
||||
self._set_canceled()
|
||||
else:
|
||||
_logger.info(
|
||||
"received invalid transaction status for transaction with reference %s: %s",
|
||||
self.reference, status
|
||||
)
|
||||
self._set_error("Alipay: " + _("received invalid transaction status: %s", status))
|
||||
@@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import common
|
||||
from . import test_alipay
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.payment.tests.common import PaymentCommon
|
||||
|
||||
|
||||
class AlipayCommon(PaymentCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.currency_yuan = cls._prepare_currency('CNY')
|
||||
cls.alipay = cls._prepare_acquirer('alipay', update_values={
|
||||
'alipay_merchant_partner_id': 'dummy',
|
||||
'alipay_md5_signature_key': 'dummy',
|
||||
'alipay_seller_email': 'dummy',
|
||||
'fees_active': False, # Only activate fees in dedicated tests
|
||||
})
|
||||
|
||||
# override defaults for helpers
|
||||
cls.acquirer = cls.alipay
|
||||
cls.currency = cls.currency_yuan
|
||||
@@ -1,87 +1,100 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from werkzeug import urls
|
||||
from lxml import objectify
|
||||
|
||||
import odoo
|
||||
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import mute_logger
|
||||
from odoo.addons.payment.models.payment_acquirer import ValidationError
|
||||
from odoo.addons.payment.tests.common import PaymentAcquirerCommon
|
||||
from odoo.addons.payment_alipay.controllers.main import AlipayController
|
||||
|
||||
@odoo.tests.tagged('post_install', '-at_install', 'external', '-standard')
|
||||
class AlipayTest(PaymentAcquirerCommon):
|
||||
from .common import AlipayCommon
|
||||
from ..controllers.main import AlipayController
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref=None):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref)
|
||||
|
||||
cls.currency_yuan = cls.env['res.currency'].search([('name', '=', 'CNY'),
|
||||
'|',
|
||||
('active', '=', True),
|
||||
('active', '=', False)], limit=1)
|
||||
cls.alipay = cls.env.ref('payment.payment_acquirer_alipay')
|
||||
cls.alipay.write({
|
||||
'alipay_merchant_partner_id': 'dummy',
|
||||
'alipay_md5_signature_key': 'dummy',
|
||||
'alipay_seller_email': 'dummy',
|
||||
'state': 'test',
|
||||
})
|
||||
@tagged('post_install', '-at_install')
|
||||
class AlipayTest(AlipayCommon):
|
||||
|
||||
def test_10_alipay_form_render(self):
|
||||
base_url = self.env['ir.config_parameter'].get_param('web.base.url')
|
||||
self.assertEqual(self.alipay.state, 'test', 'test without test environment')
|
||||
def test_compatible_acquirers(self):
|
||||
self.alipay.alipay_payment_method = 'express_checkout'
|
||||
acquirers = self.env['payment.acquirer']._get_compatible_acquirers(
|
||||
partner_id=self.partner.id,
|
||||
currency_id=self.currency_yuan.id, # 'CNY'
|
||||
company_id=self.company.id,
|
||||
)
|
||||
self.assertIn(self.alipay, acquirers)
|
||||
acquirers = self.env['payment.acquirer']._get_compatible_acquirers(
|
||||
partner_id=self.partner.id,
|
||||
currency_id=self.currency_euro.id,
|
||||
company_id=self.company.id,
|
||||
)
|
||||
self.assertNotIn(self.alipay, acquirers)
|
||||
|
||||
# ----------------------------------------
|
||||
# Test: button direct rendering
|
||||
# ----------------------------------------
|
||||
self.alipay.alipay_payment_method = 'standard_checkout'
|
||||
acquirers = self.env['payment.acquirer']._get_compatible_acquirers(
|
||||
partner_id=self.partner.id,
|
||||
currency_id=self.currency_yuan.id, # 'CNY'
|
||||
company_id=self.company.id,
|
||||
)
|
||||
self.assertIn(self.alipay, acquirers)
|
||||
acquirers = self.env['payment.acquirer']._get_compatible_acquirers(
|
||||
partner_id=self.partner.id,
|
||||
currency_id=self.currency_euro.id,
|
||||
company_id=self.company.id,
|
||||
)
|
||||
self.assertIn(self.alipay, acquirers)
|
||||
|
||||
# render the button
|
||||
res = self.alipay.render(
|
||||
'test_ref0', 0.01, self.currency_euro.id,
|
||||
values=self.buyer_values)
|
||||
def test_01_redirect_form_standard_checkout(self):
|
||||
self.alipay.alipay_payment_method = 'standard_checkout'
|
||||
self._test_alipay_redirect_form()
|
||||
|
||||
form_values = {
|
||||
def test_02_redirect_form_express_checkout(self):
|
||||
self.alipay.alipay_payment_method = 'express_checkout'
|
||||
self._test_alipay_redirect_form()
|
||||
|
||||
def _test_alipay_redirect_form(self):
|
||||
tx = self.create_transaction(flow='redirect') # Only flow implemented
|
||||
|
||||
expected_values = {
|
||||
'_input_charset': 'utf-8',
|
||||
'notify_url': urls.url_join(base_url, AlipayController._notify_url),
|
||||
'out_trade_no': 'SO12345-1',
|
||||
'notify_url': self._build_url(AlipayController._notify_url),
|
||||
'out_trade_no': self.reference,
|
||||
'partner': self.alipay.alipay_merchant_partner_id,
|
||||
'return_url': urls.url_join(base_url, AlipayController._return_url),
|
||||
'subject': 'test_ref0',
|
||||
'total_fee': '0.01',
|
||||
'return_url': self._build_url(AlipayController._return_url),
|
||||
'subject': self.reference,
|
||||
'total_fee': str(self.amount), # Fees disabled by default
|
||||
}
|
||||
|
||||
if self.alipay.alipay_payment_method == 'standard_checkout':
|
||||
form_values.update({
|
||||
expected_values.update({
|
||||
'service': 'create_forex_trade',
|
||||
'currency': 'EUR',
|
||||
'product_code': 'NEW_OVERSEAS_SELLER',
|
||||
'currency': self.currency_yuan.name,
|
||||
})
|
||||
else:
|
||||
form_values.update({
|
||||
'payment_type': '1',
|
||||
expected_values.update({
|
||||
'service': 'create_direct_pay_by_user',
|
||||
'payment_type': str(1),
|
||||
'seller_email': self.alipay.alipay_seller_email,
|
||||
'service': 'create_direct_pay_by_user'
|
||||
})
|
||||
sign = self.alipay._build_sign(form_values)
|
||||
sign = self.alipay._alipay_build_sign(expected_values)
|
||||
|
||||
form_values.update({'sign': sign, 'sign_type': 'MD5'})
|
||||
# check form result
|
||||
tree = objectify.fromstring(res)
|
||||
with mute_logger('odoo.addons.payment.models.payment_transaction'):
|
||||
processing_values = tx._get_processing_values()
|
||||
redirect_form_data = self._extract_values_from_html_form(processing_values['redirect_form_html'])
|
||||
|
||||
data_set = tree.xpath("//input[@name='data_set']")
|
||||
self.assertEqual(len(data_set), 1, 'Alipay: Found %d "data_set" input instead of 1' % len(data_set))
|
||||
self.assertEqual(data_set[0].get('data-action-url'), 'https://openapi.alipaydev.com/gateway.do', 'alipay: wrong form POST url')
|
||||
for form_input in tree.input:
|
||||
if form_input.get('name') in ['submit', 'data_set', 'sign', 'out_trade_no']:
|
||||
continue
|
||||
self.assertEqual(form_input.get('value'), form_values[form_input.get('name')], 'alipay: wrong value for input %s: received %s instead of %s' % (form_input.get('name'), form_input.get('value'), form_values[form_input.get('name')]))
|
||||
expected_values.update({
|
||||
'sign': sign,
|
||||
'sign_type': 'MD5',
|
||||
})
|
||||
|
||||
def test_11_alipay_form_with_fees(self):
|
||||
self.assertEqual(self.alipay.state, 'test', 'test without test environment')
|
||||
self.assertEqual(
|
||||
redirect_form_data['action'],
|
||||
'https://openapi.alipaydev.com/gateway.do',
|
||||
)
|
||||
self.assertDictEqual(
|
||||
expected_values,
|
||||
redirect_form_data['inputs'],
|
||||
"Alipay: invalid inputs specified in the redirect form.",
|
||||
)
|
||||
|
||||
def test_03_redirect_form_with_fees(self):
|
||||
# update acquirer: compute fees
|
||||
self.alipay.write({
|
||||
'fees_active': True,
|
||||
@@ -91,34 +104,41 @@ class AlipayTest(PaymentAcquirerCommon):
|
||||
'fees_int_var': 0.50,
|
||||
})
|
||||
|
||||
# render the button
|
||||
res = self.alipay.render(
|
||||
'test_ref0', 12.50, self.currency_euro.id,
|
||||
values=self.buyer_values)
|
||||
transaction_fees = self.currency.round(
|
||||
self.alipay._compute_fees(
|
||||
self.amount,
|
||||
self.currency,
|
||||
self.partner.country_id,
|
||||
)
|
||||
)
|
||||
self.assertEqual(transaction_fees, 7.09)
|
||||
total_fee = self.currency.round(self.amount + transaction_fees)
|
||||
self.assertEqual(total_fee, 1118.2)
|
||||
|
||||
tree = objectify.fromstring(res)
|
||||
tx = self.create_transaction(flow='redirect')
|
||||
self.assertEqual(tx.fees, 7.09)
|
||||
with mute_logger('odoo.addons.payment.models.payment_transaction'):
|
||||
processing_values = tx._get_processing_values()
|
||||
redirect_form_data = self._extract_values_from_html_form(processing_values['redirect_form_html'])
|
||||
|
||||
data_set = tree.xpath("//input[@name='data_set']")
|
||||
self.assertEqual(len(data_set), 1, 'alipay: Found %d "data_set" input instead of 1' % len(data_set))
|
||||
self.assertEqual(data_set[0].get('data-action-url'), 'https://openapi.alipaydev.com/gateway.do', 'alipay: wrong form POST url')
|
||||
for form_input in tree.input:
|
||||
if form_input.get('name') in ['total_fee']:
|
||||
self.assertEqual(form_input.get('value'), '14.07', 'alipay: wrong computed fees') # total amount = amount + fees
|
||||
self.assertEqual(redirect_form_data['inputs']['total_fee'], str(total_fee))
|
||||
|
||||
@mute_logger('odoo.addons.payment_alipay.models.payment', 'ValidationError')
|
||||
def test_20_alipay_form_management(self):
|
||||
def test_21_standard_checkout_feedback(self):
|
||||
self.alipay.alipay_payment_method = 'standard_checkout'
|
||||
self._test_20_alipay_form_management()
|
||||
self.currency = self.currency_euro
|
||||
self._test_alipay_feedback_processing()
|
||||
|
||||
def test_22_express_checkout_feedback(self):
|
||||
self.alipay.alipay_payment_method = 'express_checkout'
|
||||
self._test_20_alipay_form_management()
|
||||
|
||||
def _test_20_alipay_form_management(self):
|
||||
self.assertEqual(self.alipay.state, 'test', 'test without test environment')
|
||||
self.currency = self.currency_yuan
|
||||
self._test_alipay_feedback_processing()
|
||||
|
||||
def _test_alipay_feedback_processing(self):
|
||||
# typical data posted by alipay after client has successfully paid
|
||||
custom_reference = 'test_ref_' + self.alipay.alipay_payment_method
|
||||
alipay_post_data = {
|
||||
'trade_no': '2017112321001003690200384552',
|
||||
'reference': 'test_ref_' + self.alipay.alipay_payment_method,
|
||||
'reference': custom_reference,
|
||||
'total_fee': 1.95,
|
||||
'trade_status': 'TRADE_CLOSED',
|
||||
}
|
||||
@@ -132,33 +152,25 @@ class AlipayTest(PaymentAcquirerCommon):
|
||||
'currency': 'EUR',
|
||||
})
|
||||
|
||||
alipay_post_data['sign'] = self.alipay._build_sign(alipay_post_data)
|
||||
# should raise error about unknown tx
|
||||
with self.assertRaises(ValidationError):
|
||||
self.env['payment.transaction'].form_feedback(alipay_post_data, 'alipay')
|
||||
alipay_post_data['sign'] = self.alipay._alipay_build_sign(alipay_post_data)
|
||||
with self.assertRaises(ValidationError): # unknown transactiion
|
||||
self.env['payment.transaction']._handle_feedback_data('alipay', alipay_post_data)
|
||||
|
||||
if self.alipay.alipay_payment_method == 'express_checkout':
|
||||
currency = self.currency_yuan
|
||||
else:
|
||||
currency = self.currency_euro
|
||||
|
||||
# create tx
|
||||
tx = self.env['payment.transaction'].create({
|
||||
'amount': 1.95,
|
||||
'acquirer_id': self.alipay.id,
|
||||
'currency_id': currency.id,
|
||||
'reference': 'test_ref_' + self.alipay.alipay_payment_method,
|
||||
'partner_name': 'Norbert Buyer',
|
||||
'partner_country_id': self.country_france.id
|
||||
'currency_id': self.currency.id,
|
||||
'reference': custom_reference,
|
||||
'partner_id': self.partner.id
|
||||
})
|
||||
|
||||
# validate tx
|
||||
tx.form_feedback(alipay_post_data, 'alipay')
|
||||
# check tx
|
||||
self.assertEqual(tx.state, 'cancel', 'alipay: wrong state after receiving a valid pending notification')
|
||||
self.assertEqual(tx.acquirer_reference, '2017112321001003690200384552', 'alipay: wrong txn_id after receiving a valid pending notification')
|
||||
self.env['payment.transaction']._handle_feedback_data('alipay', alipay_post_data)
|
||||
self.assertEqual(tx.state, 'cancel',
|
||||
'Alipay: wrong state after receiving a valid pending notification')
|
||||
self.assertEqual(tx.acquirer_reference, '2017112321001003690200384552',
|
||||
'Alipay: wrong txn_id after receiving a valid pending notification')
|
||||
|
||||
# update tx
|
||||
# reset the transaction
|
||||
tx.write({'state': 'draft', 'acquirer_reference': False})
|
||||
|
||||
# update notification from alipay should not go through since it has already been set as 'done'
|
||||
@@ -166,37 +178,16 @@ class AlipayTest(PaymentAcquirerCommon):
|
||||
alipay_post_data['trade_status'] = 'TRADE_FINISHED'
|
||||
else:
|
||||
alipay_post_data['trade_status'] = 'TRADE_SUCCESS'
|
||||
alipay_post_data['sign'] = self.alipay._build_sign(alipay_post_data)
|
||||
# validate tx
|
||||
tx.form_feedback(alipay_post_data, 'alipay')
|
||||
# check tx
|
||||
self.assertEqual(tx.acquirer_reference, '2017112321001003690200384552', 'alipay: notification should not go throught since it has already been validated')
|
||||
alipay_post_data['sign'] = self.alipay._alipay_build_sign(alipay_post_data)
|
||||
|
||||
self.env['payment.transaction']._handle_feedback_data('alipay', alipay_post_data)
|
||||
self.assertEqual(tx.acquirer_reference, '2017112321001003690200384552',
|
||||
'Alipay: notification should not go throught since it has already been validated')
|
||||
|
||||
# this time it should go through since the transaction is not validated yet
|
||||
tx.write({'state': 'draft', 'acquirer_reference': False})
|
||||
tx.form_feedback(alipay_post_data, 'alipay')
|
||||
self.assertEqual(tx.state, 'done', 'alipay: wrong state after receiving a valid pending notification')
|
||||
self.assertEqual(tx.acquirer_reference, '2017112321001003690200384552', 'alipay: wrong txn_id after receiving a valid pending notification')
|
||||
|
||||
@mute_logger('odoo.addons.payment_alipay.models.payment', 'ValidationError')
|
||||
def test_30_alipay_bad_configuration(self):
|
||||
self.alipay.alipay_payment_method = 'express_checkout'
|
||||
|
||||
# should raise error since `express_checkout` must only be used with CNY currency
|
||||
with self.assertRaises(ValidationError):
|
||||
# create tx
|
||||
tx = self.env['payment.transaction'].create({
|
||||
'acquirer_id': self.alipay.id,
|
||||
'amount': 4,
|
||||
'currency_id': self.currency_euro.id,
|
||||
'reference': 'test_ref_2',
|
||||
'partner_country_id': self.country_france.id
|
||||
})
|
||||
|
||||
tx = self.env['payment.transaction'].create({
|
||||
'acquirer_id': self.alipay.id,
|
||||
'amount': 4,
|
||||
'currency_id': self.currency_yuan.id,
|
||||
'reference': 'test_ref_2',
|
||||
'partner_country_id': self.country_france.id
|
||||
})
|
||||
self.env['payment.transaction']._handle_feedback_data('alipay', alipay_post_data)
|
||||
self.assertEqual(tx.state, 'done',
|
||||
'Alipay: wrong state after receiving a valid pending notification')
|
||||
self.assertEqual(tx.acquirer_reference, '2017112321001003690200384552',
|
||||
'Alipay: wrong txn_id after receiving a valid pending notification')
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<template id="alipay_form">
|
||||
<div>
|
||||
<input type="hidden" name="data_set" t-att-data-action-url="tx_url" data-remove-me=""/>
|
||||
|
||||
<template id="redirect_form">
|
||||
<form t-att-action="api_url" method="post">
|
||||
<input type="hidden" name="_input_charset" t-att-value="_input_charset"/>
|
||||
<input t-if="service == 'create_forex_trade'" type="hidden" name="currency" t-att-value="currency"/>
|
||||
<input type="hidden" name="notify_url" t-att-value="notify_url"/>
|
||||
@@ -17,6 +17,7 @@
|
||||
<input type="hidden" name="sign_type" t-att-value="sign_type"/>
|
||||
<input t-if="payment_type" type="hidden" name="payment_type" t-att-value="payment_type"/>
|
||||
<input t-if="seller_email" type="hidden" name="seller_email" t-att-value="seller_email"/>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
|
||||
+7
-4
@@ -1,18 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="payment_acquirer_view_form_inherit_payment_alipay" model="ir.ui.view">
|
||||
<field name="name">payment.acquirer.view.form.inherit.payment.alipay</field>
|
||||
|
||||
<record id="payment_acquirer_form" model="ir.ui.view">
|
||||
<field name="name">Alipay Acquirer Form</field>
|
||||
<field name="model">payment.acquirer</field>
|
||||
<field name="inherit_id" ref="payment.acquirer_form"/>
|
||||
<field name="inherit_id" ref="payment.payment_acquirer_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr='//group[@name="acquirer"]' position='inside'>
|
||||
<group attrs="{'invisible': [('provider', '!=', 'alipay')]}">
|
||||
<field name="alipay_payment_method" widget="radio"/>
|
||||
<field name="alipay_seller_email" attrs="{'invisible': [('alipay_payment_method', '=', 'standard_checkout')], 'required': [('provider', '=', 'alipay'), ('alipay_payment_method', '=', 'express_checkout')]}"/>
|
||||
<field name="alipay_seller_email"
|
||||
attrs="{'invisible': [('alipay_payment_method', '=', 'standard_checkout')], 'required': [('provider', '=', 'alipay'), ('alipay_payment_method', '=', 'express_checkout')]}"/>
|
||||
<field name="alipay_merchant_partner_id" password="True"/>
|
||||
<field name="alipay_md5_signature_key" password="True"/>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user