[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:
Nikunj Ladava
2019-05-03 11:40:05 +00:00
committed by Damien Bouvy
parent 728ca33cb9
commit e335d235f6
16 changed files with 603 additions and 0 deletions
@@ -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>
+10
View File
@@ -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

+5
View File
@@ -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
+15
View File
@@ -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
+63
View File
@@ -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>
+4
View File
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import payment
+228
View File
@@ -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

+4
View File
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_alipay
+195
View File
@@ -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>