[ADD] payment_alipay: added new Alipay payment acquirer
This commit introduces the Alipay payment provider, a popular acquirer in the Chinese market. There are 2 possible ways to use this acquirer: - express checkout mode (only available for merchants located in CN) - standard checkout mode (availabe for foreign merchants) Note that this provider does not support server-to-server payments, tokenization or any other bells and whistles besides fees. There are no specific behaviours related to this acquirer, it behaves like most 'form based' payment acquirers with a form submission, s2s notification from the provider as well as redirect in case the s2s did not reach the server in time. closes odoo/odoo#21855 Signed-off-by: Damien Bouvy (dbo) <dbo@odoo.com>
This commit is contained in:
committed by
Damien Bouvy
parent
728ca33cb9
commit
e335d235f6
@@ -265,4 +265,21 @@
|
||||
ref("payment.payment_icon_cc_codensa_easy_credit")])]'/>
|
||||
</record>
|
||||
|
||||
<record id="payment_acquirer_alipay" model="payment.acquirer">
|
||||
<field name="name">Alipay</field>
|
||||
<field name="image" type="base64" file="payment_alipay/static/description/icon.png"/>
|
||||
<field name="view_template_id" ref="default_acquirer_button"/>
|
||||
<field name="module_id" ref="base.module_payment_alipay"/>
|
||||
<field name="description" type="html">
|
||||
<p>
|
||||
Alipay is the most popular online payment platform in China. Chinese consumers can buy online using their Alipay eWallet.
|
||||
</p>
|
||||
<ul class="list-inline">
|
||||
<li><i class="fa fa-check"/>Online Payment</li>
|
||||
<li><i class="fa fa-check"/>Payment Status Tracking</li>
|
||||
</ul>
|
||||
</field>
|
||||
<!-- https://intl.alipay.com/ihome/home/about/buy.htm?topic=paymentMethods -->
|
||||
<field name="payment_icon_ids" eval='[(6, 0, [ref("payment.payment_icon_cc_jcb"),ref("payment.payment_icon_cc_mastercard"),ref("payment.payment_icon_cc_western_union"),ref("payment.payment_icon_cc_webmoney"),ref("payment.payment_icon_cc_visa")])]'/>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
@@ -54,4 +54,14 @@
|
||||
<field name="name">Codensa Easy Credit</field>
|
||||
<field name="image" type="base64" file="payment/static/img/codensa_easy_credit.png"/>
|
||||
</record>
|
||||
|
||||
<record id="payment_icon_cc_western_union" model="payment.icon">
|
||||
<field name="name">Western Union</field>
|
||||
<field name="image" type="base64" file="payment/static/img/western_union.png"/>
|
||||
</record>
|
||||
|
||||
<record id="payment_icon_cc_webmoney" model="payment.icon">
|
||||
<field name="name">WebMoney</field>
|
||||
<field name="image" type="base64" file="payment/static/img/webmoney.png"/>
|
||||
</record>
|
||||
</odoo>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
@@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import controllers
|
||||
from . import models
|
||||
@@ -0,0 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': 'Alipay Payment Acquirer',
|
||||
'category': 'Accounting',
|
||||
'summary': 'Payment Acquirer: Alipay Implementation',
|
||||
'description': """Alipay Payment Acquirer""",
|
||||
'depends': ['payment'],
|
||||
'data': [
|
||||
'views/alipay_views.xml',
|
||||
'views/payment_alipay_templates.xml',
|
||||
'data/payment_acquirer_data.xml',
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import main
|
||||
@@ -0,0 +1,63 @@
|
||||
# -*- 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.http import request
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AlipayController(http.Controller):
|
||||
_notify_url = '/payment/alipay/notify'
|
||||
_return_url = '/payment/alipay/return'
|
||||
|
||||
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._get_alipay_urls(alipay.environment), 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="none", methods=['GET', 'POST'])
|
||||
def alipay_return(self, redirect_url=False, **post):
|
||||
""" Alipay return """
|
||||
_logger.info('Beginning Alipay form_feedback with post data %s', pprint.pformat(post))
|
||||
self._alipay_validate_data(**post)
|
||||
return werkzeug.utils.redirect(redirect_url or "/")
|
||||
|
||||
@http.route('/payment/alipay/notify', type='http', auth='none', 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)
|
||||
@@ -0,0 +1,18 @@
|
||||
<?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="environment">test</field>
|
||||
<field name="pre_msg" type="html">
|
||||
<p>
|
||||
You will be redirected to the Alipay website after clicking on the payment button.
|
||||
</p>
|
||||
</field>
|
||||
<field name="alipay_merchant_partner_id">dummy</field>
|
||||
<field name="alipay_md5_signature_key">dummy</field>
|
||||
<field name="alipay_seller_email">dummy</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import payment
|
||||
@@ -0,0 +1,228 @@
|
||||
# -*- 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')])
|
||||
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'
|
||||
|
||||
@api.multi
|
||||
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
|
||||
|
||||
@api.multi
|
||||
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()
|
||||
|
||||
@api.multi
|
||||
def _get_alipay_tx_values(self, values):
|
||||
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
|
||||
|
||||
tx = self.env['payment.transaction'].search([('reference', '=', values.get('reference'))], limit=1)
|
||||
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) + '?' + urls.url_encode({'redirect_url': values.get('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
|
||||
|
||||
@api.multi
|
||||
def alipay_form_generate_values(self, values):
|
||||
values.update(self._get_alipay_tx_values(values))
|
||||
return values
|
||||
|
||||
@api.multi
|
||||
def alipay_get_form_action_url(self):
|
||||
return self._get_alipay_urls(self.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
|
||||
|
||||
@api.model
|
||||
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
|
||||
|
||||
@api.multi
|
||||
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
|
||||
|
||||
@api.multi
|
||||
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(state='done', date=date_validate)
|
||||
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='cancel', state_message=data.get('close_reason', ''))
|
||||
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='error', state_message=error)
|
||||
return self.write(res)
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 8.4 KiB |
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_alipay
|
||||
@@ -0,0 +1,195 @@
|
||||
# -*- 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.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):
|
||||
|
||||
def setUp(self):
|
||||
super(AlipayTest, self).setUp()
|
||||
|
||||
self.currency_yuan = self.env['res.currency'].search([('name', '=', 'CNY'),
|
||||
'|',
|
||||
('active', '=', True),
|
||||
('active', '=', False)], limit=1)
|
||||
self.alipay = self.env.ref('payment.payment_acquirer_alipay')
|
||||
|
||||
def test_10_alipay_form_render(self):
|
||||
base_url = self.env['ir.config_parameter'].get_param('web.base.url')
|
||||
self.assertEqual(self.alipay.environment, 'test', 'test without test environment')
|
||||
|
||||
# ----------------------------------------
|
||||
# Test: button direct rendering
|
||||
# ----------------------------------------
|
||||
|
||||
# render the button
|
||||
res = self.alipay.render(
|
||||
'test_ref0', 0.01, self.currency_euro.id,
|
||||
values=self.buyer_values)
|
||||
|
||||
form_values = {
|
||||
'_input_charset': 'utf-8',
|
||||
'notify_url': urls.url_join(base_url, AlipayController._notify_url),
|
||||
'out_trade_no': 'SO12345-1',
|
||||
'partner': self.alipay.alipay_merchant_partner_id,
|
||||
'return_url': urls.url_join(base_url, AlipayController._return_url) + '?' + urls.url_encode({'redirect_url': '/payment/process'}),
|
||||
'subject': 'test_ref0',
|
||||
'total_fee': '0.01',
|
||||
}
|
||||
|
||||
if self.alipay.alipay_payment_method == 'standard_checkout':
|
||||
form_values.update({
|
||||
'service': 'create_forex_trade',
|
||||
'currency': 'EUR',
|
||||
'product_code': 'NEW_OVERSEAS_SELLER',
|
||||
})
|
||||
else:
|
||||
form_values.update({
|
||||
'payment_type': '1',
|
||||
'seller_email': self.alipay.alipay_seller_email,
|
||||
'service': 'create_direct_pay_by_user'
|
||||
})
|
||||
sign = self.alipay._build_sign(form_values)
|
||||
|
||||
form_values.update({'sign': sign, 'sign_type': 'MD5'})
|
||||
# check form result
|
||||
tree = objectify.fromstring(res)
|
||||
|
||||
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')]))
|
||||
|
||||
def test_11_alipay_form_with_fees(self):
|
||||
self.assertEqual(self.alipay.environment, 'test', 'test without test environment')
|
||||
|
||||
# update acquirer: compute fees
|
||||
self.alipay.write({
|
||||
'fees_active': True,
|
||||
'fees_dom_fixed': 1.0,
|
||||
'fees_dom_var': 0.35,
|
||||
'fees_int_fixed': 1.5,
|
||||
'fees_int_var': 0.50,
|
||||
})
|
||||
|
||||
# render the button
|
||||
res = self.alipay.render(
|
||||
'test_ref0', 12.50, self.currency_euro.id,
|
||||
values=self.buyer_values)
|
||||
|
||||
tree = objectify.fromstring(res)
|
||||
|
||||
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
|
||||
|
||||
@mute_logger('odoo.addons.payment_alipay.models.payment', 'ValidationError')
|
||||
def test_20_alipay_form_management(self):
|
||||
self.alipay.alipay_payment_method = 'standard_checkout'
|
||||
self._test_20_alipay_form_management()
|
||||
self.alipay.alipay_payment_method = 'express_checkout'
|
||||
self._test_20_alipay_form_management()
|
||||
|
||||
def _test_20_alipay_form_management(self):
|
||||
self.assertEqual(self.alipay.environment, 'test', 'test without test environment')
|
||||
|
||||
# typical data posted by alipay after client has successfully paid
|
||||
alipay_post_data = {
|
||||
'trade_no': '2017112321001003690200384552',
|
||||
'reference': 'test_ref_' + self.alipay.alipay_payment_method,
|
||||
'total_fee': 1.95,
|
||||
'trade_status': 'TRADE_CLOSED',
|
||||
}
|
||||
|
||||
if self.alipay.alipay_payment_method == 'express_checkout':
|
||||
alipay_post_data.update({
|
||||
'seller_email': self.alipay.alipay_seller_email,
|
||||
})
|
||||
else:
|
||||
alipay_post_data.update({
|
||||
'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')
|
||||
|
||||
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
|
||||
})
|
||||
|
||||
# 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')
|
||||
|
||||
# update tx
|
||||
tx.write({'acquirer_reference': False})
|
||||
|
||||
# update notification from alipay should not go through since it has already been set as 'done'
|
||||
if self.alipay.alipay_payment_method == 'standard_checkout':
|
||||
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')
|
||||
|
||||
# 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
|
||||
})
|
||||
@@ -0,0 +1,18 @@
|
||||
<?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>
|
||||
<field name="model">payment.acquirer</field>
|
||||
<field name="inherit_id" ref="payment.acquirer_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr='//group[@name="acquirer"]' position='after'>
|
||||
<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_merchant_partner_id" password="True"/>
|
||||
<field name="alipay_md5_signature_key" password="True"/>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?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=""/>
|
||||
<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"/>
|
||||
<input type="hidden" name="out_trade_no" t-att-value="out_trade_no"/>
|
||||
<input type="hidden" name="partner" t-att-value="partner"/>
|
||||
<input t-if="product_code" type="hidden" name="product_code" t-att-value="product_code"/>
|
||||
<input type="hidden" name="return_url" t-att-value="return_url"/>
|
||||
<input type="hidden" name="service" t-att-value="service"/>
|
||||
<input type="hidden" name="sign" t-att-value="sign"/>
|
||||
<input type="hidden" name="subject" t-att-value="subject"/>
|
||||
<input type="hidden" name="total_fee" t-att-value="total_fee"/>
|
||||
<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>
|
||||
</template>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user