1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
|
# -*- 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)
|