[IMP] payment_stripe: integrate stripe with new checkout api

There is two payment flow in stripe:

1) Redirection Flow (Form Based):
	in this flow, the user will redirect to stripe website for payment
	- create a session for a user and redirect the user to stripe with that session
	- after successful payment, the user will redirect back to odoo
	- get all the details of charge by using payment intent API
	- if user have tick save data then the token will be created

2) S2S Flow:
	after clicking on pay now button, one popup display with card element
	element is provided by a stripe with all validation facilities
	After submitting details, payment flow is
        - create payment method with card element on stripe
        - create a customer with partner email on stripe
        - attach customer to the payment method on stripe
        - create a token with customer and payment method in odoo
        - create payment intent in odoo
        - make a request to stripe
        - after successful request, payment will be charged for that card

task- 1986267
closes: https://github.com/odoo/odoo/pull/33978
This commit is contained in:
Nikunj Ladava
2019-07-18 06:55:22 +00:00
committed by Damien Bouvy
parent 998ff5e07f
commit 595c0ad8ee
6 changed files with 383 additions and 394 deletions
+6 -61
View File
@@ -1,41 +1,21 @@
# -*- coding: utf-8 -*-
import logging
import pprint
import werkzeug
from odoo import http
from odoo.http import request
from odoo.addons.payment.controllers.portal import PaymentProcessing
_logger = logging.getLogger(__name__)
class StripeController(http.Controller):
_success_url = '/payment/stripe/success'
_cancel_url = '/payment/stripe/cancel'
@http.route(['/payment/stripe/s2s/create_json'], type='json', auth='public')
def stripe_s2s_create_json(self, **kwargs):
acquirer_id = int(kwargs.get('acquirer_id'))
acquirer = request.env['payment.acquirer'].browse(acquirer_id)
if not kwargs.get('partner_id'):
kwargs = dict(kwargs, partner_id=request.env.user.partner_id.id)
return acquirer.s2s_process(kwargs).id
@http.route(['/payment/stripe/s2s/create'], type='http', auth='public')
def stripe_s2s_create(self, **post):
acquirer_id = int(post.get('acquirer_id'))
acquirer = request.env['payment.acquirer'].browse(acquirer_id)
error = None
try:
acquirer.s2s_process(post)
except Exception as e:
error = str(e)
return_url = post.get('return_url', '/')
if error:
separator = '?' if werkzeug.urls.url_parse(return_url).query == '' else '&'
return_url += '{}{}'.format(separator, werkzeug.urls.url_encode({'error': error}))
return werkzeug.utils.redirect(return_url)
@http.route(['/payment/stripe/success', '/payment/stripe/cancel'], type='http', auth='public')
def stripe_success(self, **kwargs):
request.env['payment.transaction'].sudo().form_feedback(kwargs, 'stripe')
return werkzeug.utils.redirect('/payment/process')
@http.route(['/payment/stripe/s2s/create_json_3ds'], type='json', auth='public', csrf=False)
def stripe_s2s_create_json_3ds(self, verify_validity=False, **kwargs):
@@ -62,38 +42,3 @@ class StripeController(http.Controller):
res['verified'] = token.verified
return res
@http.route(['/payment/stripe/create_charge'], type='json', auth='public')
def stripe_create_charge(self, **post):
""" Create a payment transaction
Expects the result from the user input from checkout.js popup"""
TX = request.env['payment.transaction']
tx = None
if post.get('tx_ref'):
tx = TX.sudo().search([('reference', '=', post['tx_ref'])])
if not tx:
tx_id = (post.get('tx_id') or request.session.get('sale_transaction_id') or
request.session.get('website_payment_tx_id'))
tx = TX.sudo().browse(int(tx_id))
if not tx:
raise werkzeug.exceptions.NotFound()
stripe_token = post['token']
response = None
if tx.type == 'form_save' and tx.partner_id:
payment_token_id = request.env['payment.token'].sudo().create({
'acquirer_id': tx.acquirer_id.id,
'partner_id': tx.partner_id.id,
'stripe_token': stripe_token
})
tx.payment_token_id = payment_token_id
response = tx._create_stripe_charge(acquirer_ref=payment_token_id.acquirer_ref, email=stripe_token['email'])
else:
response = tx._create_stripe_charge(tokenid=stripe_token['id'], email=stripe_token['email'])
_logger.info('Stripe: entering form_feedback with post data %s', pprint.pformat(response))
if response:
request.env['payment.transaction'].sudo().with_context(lang=None).form_feedback(response, 'stripe')
# add the payment transaction into the session to let the page /payment/process to handle it
PaymentProcessing.add_payment_transaction(tx)
return "/payment/process"
+121 -151
View File
@@ -3,19 +3,15 @@
import logging
import requests
import pprint
from werkzeug import urls
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__)
from odoo.addons.payment.models.payment_acquirer import ValidationError
from odoo.addons.payment_stripe.controllers.main import StripeController
# 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'}
_logger = logging.getLogger(__name__)
# The following currencies are integer only, see https://stripe.com/docs/currencies#zero-decimal
INT_CURRENCIES = [
@@ -38,58 +34,59 @@ class PaymentAcquirerStripe(models.Model):
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'),
}
stripe_tx_values.update(temp_stripe_tx_values)
return stripe_tx_values
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
stripe_session_data = {
'payment_method_types[]': 'card',
'line_items[][amount]': int(tx_values['amount'] if tx_values['currency'].name in INT_CURRENCIES else float_round(tx_values['amount'] * 100, 2)),
'line_items[][currency]': tx_values['currency'].name,
'line_items[][quantity]': 1,
'line_items[][name]': tx_values['reference'],
'client_reference_id': tx_values['reference'],
'success_url': urls.url_join(base_url, StripeController._success_url) + '?reference=%s' % tx_values['reference'],
'cancel_url': urls.url_join(base_url, StripeController._cancel_url) + '?reference=%s' % tx_values['reference'],
'customer_email': tx_values['partner_email'],
}
tx_values['session_id'] = self._create_stripe_session(stripe_session_data)
return tx_values
def _stripe_request(self, url, data=False):
self.ensure_one()
url = urls.url_join(self._get_stripe_api_url(), url)
headers = {'AUTHORIZATION': 'Bearer %s' % self.sudo().stripe_secret_key}
resp = requests.post(url, data, headers=headers)
resp.raise_for_status()
return resp.json()
def _create_stripe_session(self, kwargs):
self.ensure_one()
resp = self._stripe_request('checkout/sessions', kwargs)
if resp.get('payment_intent') and kwargs.get('client_reference_id'):
tx = self.env['payment.transaction'].sudo().search([('reference', '=', kwargs['client_reference_id'])])
tx.stripe_payment_intent = resp['payment_intent']
return resp['id']
@api.model
def _get_stripe_api_url(self):
return 'api.stripe.com/v1'
return 'https://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'])
'partner_id': int(data['partner_id']),
'stripe_payment_method': data.get('id'),
'name': 'XXXXXXXXXXXX%s' % data.get('card', {}).get('last4'),
'acquirer_ref': data.get('customer')
})
return payment_token
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
def _get_feature_support(self):
"""Get advanced feature support by provider.
Each provider should add its technical in the corresponding
key for the following features:
* fees: support payment fees computations
* authorize: support authorizing payment (separates
authorization and capture)
* tokenize: support saving payment data in a payment.tokenize
object
"""
@@ -101,27 +98,38 @@ class PaymentAcquirerStripe(models.Model):
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())
stripe_payment_intent = fields.Char(string='Stripe Payment Intent ID', readonly=True)
def form_feedback(self, data, acquirer_name):
if data.get('reference') and acquirer_name == 'stripe':
transaction = self.env['payment.transaction'].search([('reference', '=', data['reference'])])
url = 'payment_intents/%s' % transaction.stripe_payment_intent
resp = transaction.acquirer_id._stripe_request(url)
if resp.get('charges') and resp.get('charges').get('total_count'):
resp = resp.get('charges').get('data')[0]
data.update(resp)
_logger.info('Stripe: entering form_feedback with post data %s' % pprint.pformat(data))
return super(PaymentTransactionStripe, self).form_feedback(data, acquirer_name)
def _create_stripe_charge(self, acquirer_ref=None, email=None):
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,
'description': self.reference,
'payment_method_types[]': 'card',
'customer': acquirer_ref,
'off_session': 'recurring',
'confirm': True,
'payment_method': self.payment_token_id.stripe_payment_method
}
if acquirer_ref:
charge_params['customer'] = acquirer_ref
if tokenid:
charge_params['card'] = str(tokenid)
if email:
charge_params['receipt_email'] = email.strip()
_logger.info('_create_stripe_charge: Sending values to stripe, values:\n%s', pprint.pformat(charge_params))
res = self.acquirer_id._stripe_request('payment_intents', charge_params)
if res.get('charges') and res.get('charges').get('total_count'):
res = res.get('charges').get('data')[0]
_logger.info('_create_stripe_charge: Sending values to URL %s, values:\n%s', api_url_charge, pprint.pformat(charge_params))
r = requests.post(api_url_charge,
auth=(self.acquirer_id.stripe_secret_key, ''),
params=charge_params,
headers=STRIPE_HEADERS)
res = r.json()
_logger.info('_create_stripe_charge: Values received:\n%s', pprint.pformat(res))
return res
@@ -130,9 +138,7 @@ class PaymentTransactionStripe(models.Model):
result = self._create_stripe_charge(acquirer_ref=self.payment_token_id.acquirer_ref, email=self.partner_email)
return self._stripe_s2s_validate_tree(result)
def _create_stripe_refund(self):
api_url_refund = 'https://%s/refunds' % (self.acquirer_id._get_stripe_api_url())
refund_params = {
'charge': self.acquirer_reference,
@@ -140,13 +146,10 @@ class PaymentTransactionStripe(models.Model):
'metadata[reference]': self.reference,
}
_logger.info('_create_stripe_refund: Sending values to URL %s, values:\n%s', api_url_refund, pprint.pformat(refund_params))
r = requests.post(api_url_refund,
auth=(self.acquirer_id.stripe_secret_key, ''),
params=refund_params,
headers=STRIPE_HEADERS)
res = r.json()
_logger.info('_create_stripe_refund: Sending values to stripe URL, values:\n%s', pprint.pformat(refund_params))
res = self.acquirer_id._stripe_request('refunds', refund_params)
_logger.info('_create_stripe_refund: Values received:\n%s', pprint.pformat(res))
return res
def stripe_s2s_do_refund(self, **kwargs):
@@ -158,11 +161,11 @@ class PaymentTransactionStripe(models.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')
reference = data.get('reference')
if not reference:
stripe_error = data.get('error', {}).get('message', '')
_logger.error('Stripe: invalid reply received from stripe API, looks like '
'the transaction failed. (error: %s)', stripe_error or 'n/a')
'the transaction failed. (error: %s)', stripe_error or 'n/a')
error_msg = _("We're sorry to report that the transaction has failed.")
if stripe_error:
error_msg += " " + (_("Stripe gave us the following info about the problem: '%s'") %
@@ -189,111 +192,78 @@ class PaymentTransactionStripe(models.Model):
return True
status = tree.get('status')
tx_id = tree.get('id')
vals = {
'date': fields.datetime.now(),
'acquirer_reference': tx_id,
}
if status == 'succeeded':
self.write({
'date': fields.datetime.now(),
'acquirer_reference': tree.get('id'),
})
self.write(vals)
self._set_transaction_done()
self.execute_callback()
if self.type == 'form_save':
s2s_data = {
'customer': tree.get('customer'),
'id': tree.get('payment_method'),
'card': tree.get('payment_method_details').get('card'),
'acquirer_id': self.acquirer_id.id,
'partner_id': self.partner_id.id
}
token = self.acquirer_id.stripe_s2s_form_process(s2s_data)
self.payment_token_id = token.id
if self.payment_token_id:
self.payment_token_id.verified = True
return True
if status == 'processing':
self.write(vals)
self._set_transaction_pending()
return True
else:
error = tree['error']['message']
error = tree.get('failure_message')
_logger.warn(error)
self.sudo().write({
'state_message': error,
'acquirer_reference': tree.get('id'),
'date': fields.datetime.now(),
})
vals.update({'state_message': error})
self.write(vals)
self._set_transaction_cancel()
return False
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))
if data.get('amount') != int(self.amount if self.currency_id.name in INT_CURRENCIES else float_round(self.amount * 100, 2)):
invalid_parameters.append(('Amount', data.get('amount'), self.amount * 100))
if data.get('currency').upper() != self.currency_id.name:
invalid_parameters.append(('Currency', data.get('currency'), self.currency_id.name))
if data.get('payment_intent') and data.get('payment_intent') != self.stripe_payment_intent:
invalid_parameters.append(('Payment Intent', data.get('payment_intent'), self.stripe_payment_intent))
return invalid_parameters
def _stripe_form_validate(self, data):
def _stripe_form_validate(self, data):
return self._stripe_s2s_validate_tree(data)
class PaymentTokenStripe(models.Model):
_inherit = 'payment.token'
stripe_payment_method = fields.Char('Payment Method ID')
@api.model
def stripe_create(self, values):
token = values.get('stripe_token')
description = None
payment_acquirer = self.env['payment.acquirer'].browse(values.get('acquirer_id'))
# when asking to create a token on Stripe servers
if values.get('cc_number'):
url_token = 'https://%s/tokens' % payment_acquirer._get_stripe_api_url()
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'],
'card[name]': values['cc_holder_name'],
if values.get('stripe_payment_method') and not values.get('acquirer_ref'):
partner_id = self.env['res.partner'].browse(values.get('partner_id'))
payment_acquirer = self.env['payment.acquirer'].browse(values.get('acquirer_id'))
# create customer to stipe
customer_data = {
'email': partner_id.email
}
r = requests.post(url_token,
auth=(payment_acquirer.stripe_secret_key, ''),
params=payment_params,
headers=STRIPE_HEADERS)
token = r.json()
description = values['cc_holder_name']
else:
partner_id = self.env['res.partner'].browse(values['partner_id'])
description = 'Partner: %s (id: %s)' % (partner_id.name, partner_id.id)
if not token:
raise UserError(_("Stripe: no payment token was provided or the token creation failed."))
res = self._stripe_create_customer(token, description, payment_acquirer.id)
# pop credit card info to info sent to create
for field_name in ["cc_number", "cvc", "cc_holder_name", "cc_expiry", "cc_brand", "stripe_token"]:
values.pop(field_name, None)
return res
def _stripe_create_customer(self, token, description=None, acquirer_id=None):
if token.get('error'):
_logger.error('payment.token.stripe_create_customer: Token error:\n%s', pprint.pformat(token['error']))
raise Exception(token['error']['message'])
if token['object'] != 'token':
_logger.error('payment.token.stripe_create_customer: Cannot create a customer for object type "%s"', token.get('object'))
raise Exception('We are unable to process your credit card information.')
if token['type'] != 'card':
_logger.error('payment.token.stripe_create_customer: Cannot create a customer for token type "%s"', token.get('type'))
raise Exception('We are unable to process your credit card information.')
payment_acquirer = self.env['payment.acquirer'].browse(acquirer_id or self.acquirer_id.id)
url_customer = 'https://%s/customers' % payment_acquirer._get_stripe_api_url()
customer_params = {
'source': token['id'],
'description': description or token["card"]["name"]
}
r = requests.post(url_customer,
auth=(payment_acquirer.stripe_secret_key, ''),
params=customer_params,
headers=STRIPE_HEADERS)
customer = r.json()
if customer.get('error'):
_logger.error('payment.token.stripe_create_customer: Customer error:\n%s', pprint.pformat(customer['error']))
raise Exception(customer['error']['message'])
values = {
'acquirer_ref': customer['id'],
'name': 'XXXXXXXXXXXX%s - %s' % (token['card']['last4'], customer_params["description"])
}
cust_resp = payment_acquirer._stripe_request('customers', customer_data)
# link customer with payment method
api_url_payment_method = 'payment_methods/%s/attach' % values['stripe_payment_method']
method_data = {
'customer': cust_resp.get('id')
}
payment_acquirer._stripe_request(api_url_payment_method, method_data)
return {
'acquirer_ref': cust_resp['id'],
}
return values
@@ -0,0 +1,154 @@
odoo.define('payment_stripe.payment_form', function (require) {
"use strict";
var ajax = require('web.ajax');
var core = require('web.core');
var Dialog = require('web.Dialog');
var PaymentForm = require('payment.payment_form');
var qweb = core.qweb;
var _t = core._t;
ajax.loadXML('/payment_stripe/static/src/xml/stripe_templates.xml', qweb);
PaymentForm.include({
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* called when clicking on pay now or add payment event to create token for credit card/debit card.
*
* @private
* @param {Event} ev
* @param {DOMElement} checkedRadio
* @param {Boolean} addPmEvent
*/
_createStripeToken: function (ev, $checkedRadio, addPmEvent) {
var self = this;
var acquirerID = this.getAcquirerIdFromRadio($checkedRadio);
var acquirerForm = this.$('#o_payment_add_token_acq_' + acquirerID);
var inputsForm = $('input', acquirerForm);
if (this.options.partnerId === undefined) {
console.warn('payment_form: unset partner_id when adding new token; things could go wrong');
}
ajax.loadJS("https://js.stripe.com/v3/").then(function () {
var formData = self.getFormData(inputsForm);
var stripe = Stripe(formData.stripe_publishable_key);
var element = stripe.elements();
var card = element.create('card', {hidePostalCode: true});
var dialog = new Dialog(self, {
title: _t('Card Details'),
size: 'medium',
technical: false,
buttons: [{text: _t('Submit'), classes: 'btn-primary', click: function (ev) {
stripe.createPaymentMethod('card', card).then(function (result) {
if (result.error) {
// Inform the user if there was an error.
var errorElement = document.getElementById('card-errors');
errorElement.textContent = result.error.message;
} else {
ev.currentTarget.disabled = true;
_.extend(formData, result.paymentMethod);
// Send the token to server.
self._rpc({
route: formData.data_set,
params: formData
}).then (function (data) {
if (addPmEvent) {
if (formData.return_url) {
window.location = formData.return_url;
} else {
window.location.reload();
}
} else {
$checkedRadio.val(data.id);
self.$el.submit();
}
}).guardedCatch(function (error) {
// if the rpc fails, pretty obvious
self.enableButton(ev.target);
self.displayError(
_t('Server Error'),
_t("We are not able to add your payment method at the moment.") +
error.message.data.message
);
});
}
});
}}],
$content: $(qweb.render('stripe.payment.element')),
});
dialog.opened().then(function () {
self.disableButton(ev.target);
card.mount('#card-element');
card.addEventListener('change', function (event) {
var displayError = document.getElementById('card-errors');
displayError.textContent = '';
if (event.error) {
displayError.textContent = event.error.message;
}
});
});
dialog.on('closed', self, function () {
self.enableButton(ev.target);
});
dialog.open();
});
},
/**
* @override
*/
updateNewPaymentDisplayStatus: function () {
var $checkedRadio = this.$('input[type="radio"]:checked');
if ($checkedRadio.length !== 1) {
return;
}
// hide add token form for stripe
if ($checkedRadio.data('provider') === 'stripe' && this.isNewPaymentRadio($checkedRadio)) {
this.$('[id*="o_payment_add_token_acq_"]').addClass('d-none');
} else {
this._super.apply(this, arguments);
}
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* @override
*/
payEvent: function (ev) {
ev.preventDefault();
var $checkedRadio = this.$('input[type="radio"]:checked');
// first we check that the user has selected a stripe as s2s payment method
if ($checkedRadio.length === 1 && this.isNewPaymentRadio($checkedRadio) && $checkedRadio.data('provider') === 'stripe') {
this._createStripeToken(ev, $checkedRadio);
} else {
this._super.apply(this, arguments);
}
},
/**
* @override
*/
addPmEvent: function (ev) {
ev.stopPropagation();
ev.preventDefault();
var $checkedRadio = this.$('input[type="radio"]:checked');
// first we check that the user has selected a stripe as add payment method
if ($checkedRadio.length === 1 && this.isNewPaymentRadio($checkedRadio) && $checkedRadio.data('provider') === 'stripe') {
this._createStripeToken(ev, $checkedRadio, true);
} else {
this._super.apply(this, arguments);
}
},
});
});
+67 -127
View File
@@ -1,141 +1,81 @@
odoo.define('payment_stripe.stripe', function(require) {
"use strict";
odoo.define('payment_stripe.stripe', function (require) {
"use strict";
var ajax = require('web.ajax');
var core = require('web.core');
var _t = core._t;
var qweb = core.qweb;
ajax.loadXML('/payment_stripe/static/src/xml/stripe_templates.xml', qweb);
var ajax = require('web.ajax');
var core = require('web.core');
// The following currencies are integer only, see
// https://stripe.com/docs/currencies#zero-decimal
var int_currencies = [
'BIF', 'XAF', 'XPF', 'CLP', 'KMF', 'DJF', 'GNF', 'JPY', 'MGA', 'PYG',
'RWF', 'KRW', 'VUV', 'VND', 'XOF'
];
var qweb = core.qweb;
var _t = core._t;
if ($.blockUI) {
// our message needs to appear above the modal dialog
$.blockUI.defaults.baseZ = 2147483647; //same z-index as StripeCheckout
$.blockUI.defaults.css.border = '0';
$.blockUI.defaults.css["background-color"] = '';
$.blockUI.defaults.overlayCSS["opacity"] = '0.9';
}
var stripeHandler;
function getStripeHandler()
{
if (stripeHandler) {
return stripeHandler;
}
var handler = stripeHandler = StripeCheckout.configure({
key: $("input[name='stripe_key']").val(),
image: $("input[name='stripe_image']").val(),
locale: 'auto',
token: function(token, args) {
handler.isTokenGenerate = true;
if ($.blockUI) {
var msg = _t("Just one more second, confirming your payment...");
$.blockUI({
'message': '<h2 class="text-white"><img src="/web/static/src/img/spin.png" class="fa-pulse"/>' +
' <br />' + msg +
'</h2>'
});
}
ajax.jsonRpc("/payment/stripe/create_charge", 'call', {
tokenid: token.id, // TBE TODO: for backward compatibility, remove on master
email: token.email, // TBE TODO: for backward compatibility, remove on master
token: token,
amount: $("input[name='amount']").val(),
acquirer_id: $("#acquirer_stripe").val(),
currency: $("input[name='currency']").val(),
invoice_num: $("input[name='invoice_num']").val(),
tx_ref: $("input[name='invoice_num']").val(),
return_url: $("input[name='return_url']").val()
}).then(function(data){
handler.isTokenGenerate = false;
window.location.href = data;
if ($.blockUI) {
$.unblockUI();
}
}).guardedCatch(function(data){
var msg = data && data.data && data.data.message;
var wizard = $(qweb.render('stripe.error', {'msg': msg || _t('Payment error')}));
wizard.appendTo($('body')).modal({'keyboard': true});
if ($.blockUI) {
$.unblockUI();
}
});
},
});
return handler;
}
ajax.loadXML('/payment_stripe/static/src/xml/stripe_templates.xml', qweb);
require('web.dom_ready');
if (!$('.o_payment_form').length) {
return Promise.reject("DOM doesn't contain '.o_payment_form'");
}
if ($.blockUI) {
// our message needs to appear above the modal dialog
$.blockUI.defaults.baseZ = 2147483647; //same z-index as StripeCheckout
$.blockUI.defaults.css.border = '0';
$.blockUI.defaults.css["background-color"] = '';
$.blockUI.defaults.overlayCSS["opacity"] = '0.9';
}
var observer = new MutationObserver(function(mutations, observer) {
for(var i=0; i<mutations.length; ++i) {
for(var j=0; j<mutations[i].addedNodes.length; ++j) {
if(mutations[i].addedNodes[j].tagName.toLowerCase() === "form" && mutations[i].addedNodes[j].getAttribute('provider') == 'stripe') {
display_stripe_form($(mutations[i].addedNodes[j]));
}
require('web.dom_ready');
if (!$('.o_payment_form').length) {
return Promise.reject("DOM doesn't contain '.o_payment_form'");
}
var observer = new MutationObserver(function (mutations, observer) {
for (var i = 0; i < mutations.length; ++i) {
for (var j = 0; j < mutations[i].addedNodes.length; ++j) {
if (mutations[i].addedNodes[j].tagName.toLowerCase() === "form" && mutations[i].addedNodes[j].getAttribute('provider') === 'stripe') {
_redirectToStripeCheckout($(mutations[i].addedNodes[j]));
}
}
});
}
});
function displayError(message) {
var wizard = $(qweb.render('stripe.error', {'msg': message || _t('Payment error')}));
wizard.appendTo($('body')).modal({'keyboard': true});
if ($.blockUI) {
$.unblockUI();
}
$("#o_payment_form_pay").removeAttr('disabled');
}
function display_stripe_form(provider_form) {
// Open Checkout with further options
var payment_form = $('.o_payment_form');
if(!payment_form.find('i').length)
payment_form.append('<i class="fa fa-spinner fa-spin"/>');
payment_form.attr('disabled','disabled');
var payment_tx_url = payment_form.find('input[name="prepare_tx_url"]').val();
var access_token = $("input[name='access_token']").val() || $("input[name='token']").val() || '';
var get_input_value = function(name) {
return provider_form.find('input[name="' + name + '"]').val();
}
var acquirer_id = parseInt(provider_form.find('#acquirer_stripe').val());
var amount = parseFloat(get_input_value("amount") || '0.0');
var currency = get_input_value("currency");
var email = get_input_value("email");
var invoice_num = get_input_value("invoice_num");
var merchant = get_input_value("merchant");
// Search if the user wants to save the credit card information
var form_save_token = false;
var acquirer_form = $('#o_payment_form_acq_' + acquirer_id);
if (acquirer_form.length) {
form_save_token = acquirer_form.find('input[name="o_payment_form_save_token"]').prop('checked');
}
ajax.jsonRpc(payment_tx_url, 'call', {
acquirer_id: acquirer_id,
access_token: access_token,
save_token: form_save_token,
}).then(function(data) {
var $pay_stripe = $('#pay_stripe').detach();
try { provider_form[0].innerHTML = data; } catch (e) {}
// Restore 'Pay Now' button HTML since data might have changed it.
$(provider_form[0]).find('#pay_stripe').replaceWith($pay_stripe);
}).then(function () {
getStripeHandler().open({
name: merchant,
description: invoice_num,
email: email,
currency: currency,
amount: _.contains(int_currencies, currency) ? amount : amount * 100,
});
function _redirectToStripeCheckout(providerForm) {
// Open Checkout with further options
if ($.blockUI) {
var msg = _t("Just one more second, We are redirecting you to Stripe...");
$.blockUI({
'message': '<h2 class="text-white"><img src="/web/static/src/img/spin.png" class="fa-pulse"/>' +
' <br />' + msg +
'</h2>'
});
}
$.getScript("https://checkout.stripe.com/checkout.js", function(data, textStatus, jqxhr) {
observer.observe(document.body, {childList: true});
display_stripe_form($('form[provider="stripe"]'));
var paymentForm = $('.o_payment_form');
if (!paymentForm.find('i').length) {
paymentForm.append('<i class="fa fa-spinner fa-spin"/>');
paymentForm.attr('disabled', 'disabled');
}
var _getStripeInputValue = function (name) {
return providerForm.find('input[name="' + name + '"]').val();
};
var stripe = Stripe(_getStripeInputValue('stripe_key'));
stripe.redirectToCheckout({
sessionId: _getStripeInputValue('session_id')
}).then(function (result) {
if (result.error) {
displayError(result.error.message);
}
});
}
$.getScript("https://js.stripe.com/v3/", function (data, textStatus, jqxhr) {
observer.observe(document.body, {childList: true});
_redirectToStripeCheckout($('form[provider="stripe"]'));
});
});
@@ -1,21 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="stripe.error">
<div role="dialog" class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<header class="modal-header">
<h4 class="modal-title">Error</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">&amp;times;</button>
</header>
<main class="modal-body">
<t t-esc="msg"></t>
</main>
<footer class="modal-footer">
<a role="button" href="#" class="btn btn-link btn-sm" data-dismiss="modal">Close</a>
</footer>
<t t-name="stripe.error">
<div role="dialog" class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<header class="modal-header">
<h4 class="modal-title">Error</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">&amp;times;</button>
</header>
<main class="modal-body">
<t t-esc="msg"></t>
</main>
<footer class="modal-footer">
<a role="button" href="#" class="btn btn-link btn-sm" data-dismiss="modal">Close</a>
</footer>
</div>
</div>
</div>
</t>
<div t-name="stripe.payment.element">
<div id="payment-form">
<div id="card-errors" class="m-3 text-danger text-center"/>
<div id="card-element" class="m-3"/>
</div>
</div>
</t>
</templates>
@@ -3,23 +3,7 @@
<data>
<template id="stripe_form">
<input type="hidden" name="data_set" t-att-data-action-url="tx_url" data-remove-me=""/>
<input type="hidden" name='stripe_image' t-att-value="acquirer.stripe_image_url"/>
<input type="hidden" name='merchant' t-att-value='acquirer.company_id.name'/>
<input type="hidden" name='amount' t-att-value="amount"/>
<input type="hidden" name='return_url' t-att-value='returndata'/>
<!--Order Information -->
<input type="hidden" name='invoice_num' t-att-value='reference'/>
<input type="hidden" name='currency' t-att-value='currency'/>
<input type="hidden" name='currency_id' t-att-value='currency_id'/>
<!-- Billing Information-->
<input type="hidden" name='name' t-att-value='name'/>
<input type="hidden" name='address_line1' t-att-value='address_line1'/>
<input type="hidden" name='address_city' t-att-value='address_city'/>
<input type="hidden" name='address_zip' t-att-value='address_zip'/>
<input type="hidden" name='address_country' t-att-value='address_country'/>
<input type="hidden" name='phone' t-att-value='phone'/>
<input type="hidden" name='email' t-att-value='email'/>
<input id="acquirer_stripe" type="hidden" name="acquirer" t-att-value="acquirer.id"/>
<input type='hidden' name='session_id' t-att-value='session_id'/>
<input type="hidden" name="stripe_key" t-att-value="acquirer.stripe_publishable_key"/>
<script type="text/javascript">
odoo.define(function (require) {
@@ -30,29 +14,18 @@
</template>
<template id="stripe_s2s_form">
<input type="hidden" name="data_set" data-create-route="/payment/stripe/s2s/create_json_3ds"/>
<div t-attf-class="row mt8 #{'' if bootstrap_formatting else 'o_card_brand_detail'}">
<div t-att-class="'form-group col-lg-12' if bootstrap_formatting else 'form-group'">
<input type="tel" name="cc_number" id="cc_number" class="form-control" placeholder="Card number" data-is-required="true"/>
<div class="card_placeholder"></div>
<div class="visa"></div>
<input type="hidden" name="cc_brand" value=""/>
</div>
<div t-att-class="'form-group col-lg-5' if bootstrap_formatting else 'form-group'">
<input type="text" name="cc_holder_name" id="cc_holder_name" class="form-control" placeholder="Cardholder name" data-is-required="true"/>
</div>
<div t-att-class="'form-group col-lg-3' if bootstrap_formatting else 'form-group'">
<input type="text" name="cc_expiry" id="cc_expiry" class="form-control" maxlength="7" placeholder="Expires (MM / YY)" data-is-required="true"/>
</div>
<div t-att-class="'form-group col-lg-4' if bootstrap_formatting else 'form-group'">
<input type="text" name="cvc" id="cvc" class="form-control" maxlength="4" placeholder="CVC" data-is-required="true"/>
</div>
<input type="hidden" name="acquirer_id" t-att-value="id"/>
<input t-if="return_url" type="hidden" name="return_url" t-att-value="return_url"/>
<input t-if="partner_id" type="hidden" name="partner_id" t-att-value="partner_id"/>
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<div class="clearfix"/>
</div>
<input type="hidden" name="data_set" value="/payment/stripe/s2s/create_json_3ds"/>
<input type="hidden" name="acquirer_id" t-att-value="id"/>
<input type="hidden" name="stripe_publishable_key" t-att-value="acq.sudo().stripe_publishable_key"/>
<input t-if="return_url" type="hidden" name="return_url" t-att-value="return_url"/>
<input t-if="partner_id" type="hidden" name="partner_id" t-att-value="partner_id"/>
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
</template>
<template id="assets_frontend" inherit_id="web.assets_frontend">
<xpath expr="script[last()]" position="after">
<script type="text/javascript" src="/payment_stripe/static/src/js/payment_form.js"></script>
</xpath>
</template>
</data>
</odoo>