odoo/addons/payment_stripe/models/payment.py

221 lines
8.8 KiB
Python

# coding: utf-8
import logging
import requests
from odoo import api, fields, models, _
from odoo.addons.payment.models.payment_acquirer import ValidationError
from odoo.exceptions import UserError
from odoo.tools.safe_eval import safe_eval
from odoo.tools.float_utils import float_round
_logger = logging.getLogger(__name__)
# Force the API version to avoid breaking in case of update on Stripe side
# cf https://stripe.com/docs/api#versioning
# changelog https://stripe.com/docs/upgrades#api-changelog
STRIPE_HEADERS = {'Stripe-Version': '2016-03-07'}
# The following currencies are integer only, see https://stripe.com/docs/currencies#zero-decimal
INT_CURRENCIES = [
u'BIF', u'XAF', u'XPF', u'CLP', u'KMF', u'DJF', u'GNF', u'JPY', u'MGA', u'PYGí', u'RWF', u'KRW',
u'VUV', u'VND', u'XOF'
];
class PaymentAcquirerStripe(models.Model):
_inherit = 'payment.acquirer'
provider = fields.Selection(selection_add=[('stripe', 'Stripe')])
stripe_secret_key = fields.Char(required_if_provider='stripe', groups='base.group_user')
stripe_publishable_key = fields.Char(required_if_provider='stripe', groups='base.group_user')
stripe_image_url = fields.Char(
"Checkout Image URL", groups='base.group_user',
help="A relative or absolute URL pointing to a square image of your "
"brand or product. As defined in your Stripe profile. See: "
"https://stripe.com/docs/checkout")
@api.multi
def stripe_form_generate_values(self, tx_values):
self.ensure_one()
stripe_tx_values = dict(tx_values)
temp_stripe_tx_values = {
'company': self.company_id.name,
'amount': tx_values['amount'], # Mandatory
'currency': tx_values['currency'].name, # Mandatory anyway
'currency_id': tx_values['currency'].id, # same here
'address_line1': tx_values.get('partner_address'), # Any info of the partner is not mandatory
'address_city': tx_values.get('partner_city'),
'address_country': tx_values.get('partner_country') and tx_values.get('partner_country').name or '',
'email': tx_values.get('partner_email'),
'address_zip': tx_values.get('partner_zip'),
'name': tx_values.get('partner_name'),
'phone': tx_values.get('partner_phone'),
}
temp_stripe_tx_values['returndata'] = stripe_tx_values.pop('return_url', '')
stripe_tx_values.update(temp_stripe_tx_values)
return stripe_tx_values
@api.model
def _get_stripe_api_url(self):
return 'api.stripe.com/v1'
@api.model
def stripe_s2s_form_process(self, data):
payment_token = self.env['payment.token'].sudo().create({
'cc_number': data['cc_number'],
'cc_holder_name': data['cc_holder_name'],
'cc_expiry': data['cc_expiry'],
'cc_brand': data['cc_brand'],
'cvc': data['cvc'],
'acquirer_id': int(data['acquirer_id']),
'partner_id': int(data['partner_id'])
})
return payment_token.id
@api.multi
def stripe_s2s_form_validate(self, data):
self.ensure_one()
# mandatory fields
for field_name in ["cc_number", "cvc", "cc_holder_name", "cc_expiry", "cc_brand"]:
if not data.get(field_name):
return False
return True
class PaymentTransactionStripe(models.Model):
_inherit = 'payment.transaction'
def _create_stripe_charge(self, acquirer_ref=None, tokenid=None, email=None):
api_url_charge = 'https://%s/charges' % (self.acquirer_id._get_stripe_api_url())
charge_params = {
'amount': int(self.amount if self.currency_id.name in INT_CURRENCIES else float_round(self.amount * 100, 2)),
'currency': self.currency_id.name,
'metadata[reference]': self.reference
}
if acquirer_ref:
charge_params['customer'] = acquirer_ref
if tokenid:
charge_params['card'] = str(tokenid)
if email:
charge_params['receipt_email'] = email
r = requests.post(api_url_charge,
auth=(self.acquirer_id.stripe_secret_key, ''),
params=charge_params,
headers=STRIPE_HEADERS)
return r.json()
@api.multi
def stripe_s2s_do_transaction(self, **kwargs):
self.ensure_one()
result = self._create_stripe_charge(acquirer_ref=self.payment_token_id.acquirer_ref, email=self.partner_email)
return self._stripe_s2s_validate_tree(result)
@api.model
def _stripe_form_get_tx_from_data(self, data):
""" Given a data dict coming from stripe, verify it and find the related
transaction record. """
reference = data.get('metadata', {}).get('reference')
if not reference:
error_msg = _(
'Stripe: invalid reply received from provider, missing reference. Additional message: %s'
% data.get('error', {}).get('message', '')
)
_logger.error(error_msg)
raise ValidationError(error_msg)
tx = self.search([('reference', '=', reference)])
if not tx:
error_msg = (_('Stripe: no order found for reference %s') % reference)
_logger.error(error_msg)
raise ValidationError(error_msg)
elif len(tx) > 1:
error_msg = (_('Stripe: %s orders found for reference %s') % (len(tx), reference))
_logger.error(error_msg)
raise ValidationError(error_msg)
return tx[0]
@api.multi
def _stripe_s2s_validate_tree(self, tree):
self.ensure_one()
if self.state not in ('draft', 'pending'):
_logger.info('Stripe: trying to validate an already validated tx (ref %s)', self.reference)
return True
status = tree.get('status')
if status == 'succeeded':
self.write({
'state': 'done',
'date_validate': fields.datetime.now(),
'acquirer_reference': tree.get('id'),
})
if self.sudo().callback_eval:
safe_eval(self.sudo().callback_eval, {'self': self})
return True
else:
error = tree['error']['message']
_logger.warn(error)
self.sudo().write({
'state': 'error',
'state_message': error,
'acquirer_reference': tree.get('id'),
'date_validate': fields.datetime.now(),
})
return False
@api.multi
def _stripe_form_get_invalid_parameters(self, data):
invalid_parameters = []
reference = data['metadata']['reference']
if reference != self.reference:
invalid_parameters.append(('Reference', reference, self.reference))
return invalid_parameters
@api.multi
def _stripe_form_validate(self, data):
return self._stripe_s2s_validate_tree(data)
class PaymentTokenStripe(models.Model):
_inherit = 'payment.token'
@api.model
def stripe_create(self, values):
res = {}
payment_acquirer = self.env['payment.acquirer'].browse(values.get('acquirer_id'))
url_token = 'https://%s/tokens' % payment_acquirer._get_stripe_api_url()
url_customer = 'https://%s/customers' % payment_acquirer._get_stripe_api_url()
if values.get('cc_number'):
payment_params = {
'card[number]': values['cc_number'].replace(' ', ''),
'card[exp_month]': str(values['cc_expiry'][:2]),
'card[exp_year]': str(values['cc_expiry'][-2:]),
'card[cvc]': values['cvc'],
}
r = requests.post(url_token,
auth=(payment_acquirer.stripe_secret_key, ''),
params=payment_params,
headers=STRIPE_HEADERS)
token = r.json()
if token.get('id'):
customer_params = {
'source': token['id']
}
r = requests.post(url_customer,
auth=(payment_acquirer.stripe_secret_key, ''),
params=customer_params,
headers=STRIPE_HEADERS)
customer = r.json()
res = {
'acquirer_ref': customer['id'],
'name': 'XXXXXXXXXXXX%s - %s' % (values['cc_number'][-4:], values['cc_holder_name'])
}
elif token.get('error'):
raise UserError(token['error']['message'])
# pop credit card info to info sent to create
for field_name in ["cc_number", "cvc", "cc_holder_name", "cc_expiry", "cc_brand"]:
values.pop(field_name, None)
return res