Stripe payment integration (#2867)
* [fix] setting page for stripe * checkout flow for stripe payment * controller to handle stripe checkout and authorization
This commit is contained in:
parent
e395de340e
commit
286b79bd90
8 changed files with 380 additions and 8 deletions
0
frappe/integrations/doctype/stripe_settings/__init__.py
Normal file
0
frappe/integrations/doctype/stripe_settings/__init__.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2017, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Stripe Settings', {
|
||||
refresh: function(frm) {
|
||||
|
||||
}
|
||||
});
|
||||
120
frappe/integrations/doctype/stripe_settings/stripe_settings.json
Normal file
120
frappe/integrations/doctype/stripe_settings/stripe_settings.json
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2017-03-09 17:18:29.458397",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "publishable_key",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Publishable Key",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "secret_key",
|
||||
"fieldtype": "Password",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Secret Key",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 1,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-03-09 17:19:25.087475",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Integrations",
|
||||
"name": "Stripe Settings",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 0,
|
||||
"track_seen": 0
|
||||
}
|
||||
115
frappe/integrations/doctype/stripe_settings/stripe_settings.py
Normal file
115
frappe/integrations/doctype/stripe_settings/stripe_settings.py
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2017, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe import _
|
||||
import urllib
|
||||
from frappe.utils import get_url, call_hook_method, cint
|
||||
from frappe.integrations.utils import make_get_request, make_post_request, create_request_log, create_payment_gateway
|
||||
|
||||
class StripeSettings(Document):
|
||||
supported_currencies = [
|
||||
"AED", "ALL", "ANG", "ARS", "AUD", "AWG", "BBD", "BDT", "BIF", "BMD", "BND",
|
||||
"BOB", "BRL", "BSD", "BWP", "BZD", "CAD", "CHF", "CLP", "CNY", "COP", "CRC", "CVE", "CZK", "DJF",
|
||||
"DKK", "DOP", "DZD", "EGP", "ETB", "EUR", "FJD", "FKP", "GBP", "GIP", "GMD", "GNF", "GTQ", "GYD",
|
||||
"HKD", "HNL", "HRK", "HTG", "HUF", "IDR", "ILS", "INR", "ISK", "JMD", "JPY", "KES", "KHR", "KMF",
|
||||
"KRW", "KYD", "KZT", "LAK", "LBP", "LKR", "LRD", "MAD", "MDL", "MNT", "MOP", "MRO", "MUR", "MVR",
|
||||
"MWK", "MXN", "MYR", "NAD", "NGN", "NIO", "NOK", "NPR", "NZD", "PAB", "PEN", "PGK", "PHP", "PKR",
|
||||
"PLN", "PYG", "QAR", "RUB", "SAR", "SBD", "SCR", "SEK", "SGD", "SHP", "SLL", "SOS", "STD", "SVC",
|
||||
"SZL", "THB", "TOP", "TTD", "TWD", "TZS", "UAH", "UGX", "USD", "UYU", "UZS", "VND", "VUV", "WST",
|
||||
"XAF", "XOF", "XPF", "YER", "ZAR"
|
||||
]
|
||||
|
||||
def validate(self):
|
||||
create_payment_gateway('Stripe')
|
||||
call_hook_method('payment_gateway_enabled', gateway='Stripe')
|
||||
if not self.flags.ignore_mandatory:
|
||||
self.validate_stripe_credentails()
|
||||
|
||||
def validate_stripe_credentails(self):
|
||||
if self.publishable_key and self.secret_key:
|
||||
header = {"Authorization": "Bearer {0}".format(self.get_password(fieldname="secret_key", raise_exception=False))}
|
||||
try:
|
||||
make_get_request(url="https://api.stripe.com/v1/charges", headers=header)
|
||||
except Exception:
|
||||
frappe.throw(_("Seems Publishable Key or Secret Key is wrong !!!"))
|
||||
|
||||
def validate_transaction_currency(self, currency):
|
||||
if currency not in self.supported_currencies:
|
||||
frappe.throw(_("Please select another payment method. Stripe does not support transactions in currency '{0}'").format(currency))
|
||||
|
||||
def get_payment_url(self, **kwargs):
|
||||
return get_url("./integrations/stripe_checkout?{0}".format(urllib.urlencode(kwargs)))
|
||||
|
||||
def create_request(self, data):
|
||||
self.data = frappe._dict(data)
|
||||
|
||||
try:
|
||||
self.integration_request = create_request_log(self.data, "Host", "Stripe")
|
||||
return self.create_charge_on_stripe()
|
||||
except Exception:
|
||||
frappe.log_error(frappe.get_traceback())
|
||||
return{
|
||||
"redirect_to": frappe.redirect_to_message(_('Server Error'), _("Seems issue with server's razorpay config. Don't worry, in case of failure amount will get refunded to your account.")),
|
||||
"status": 401
|
||||
}
|
||||
|
||||
def create_charge_on_stripe(self):
|
||||
headers = {"Authorization":
|
||||
"Bearer {0}".format(self.get_password(fieldname="secret_key", raise_exception=False))}
|
||||
|
||||
data = {
|
||||
"amount": cint(self.data.amount)*100,
|
||||
"currency": self.data.currency,
|
||||
"source": self.data.stripe_token_id,
|
||||
"description": self.data.description
|
||||
}
|
||||
|
||||
redirect_to = self.data.get('redirect_to') or None
|
||||
redirect_message = self.data.get('redirect_message') or None
|
||||
|
||||
try:
|
||||
resp = make_post_request(url="https://api.stripe.com/v1/charges", headers=headers, data=data)
|
||||
|
||||
if resp.get("captured") == True:
|
||||
self.integration_request.db_set('status', 'Completed', update_modified=False)
|
||||
self.flags.status_changed_to = "Completed"
|
||||
|
||||
else:
|
||||
frappe.log_error(str(resp), 'Stripe Payment not completed')
|
||||
|
||||
except:
|
||||
frappe.log_error(frappe.get_traceback())
|
||||
# failed
|
||||
pass
|
||||
|
||||
status = frappe.flags.integration_request.status_code
|
||||
|
||||
if self.flags.status_changed_to == "Completed":
|
||||
if self.data.reference_doctype and self.data.reference_docname:
|
||||
custom_redirect_to = None
|
||||
try:
|
||||
custom_redirect_to = frappe.get_doc(self.data.reference_doctype,
|
||||
self.data.reference_docname).run_method("on_payment_authorized", self.flags.status_changed_to)
|
||||
except Exception:
|
||||
frappe.log_error(frappe.get_traceback())
|
||||
|
||||
if custom_redirect_to:
|
||||
redirect_to = custom_redirect_to
|
||||
|
||||
redirect_url = 'payment-success'
|
||||
else:
|
||||
redirect_url = 'payment-failed'
|
||||
|
||||
if redirect_to:
|
||||
redirect_url += '?' + urllib.urlencode({'redirect_to': redirect_to})
|
||||
if redirect_message:
|
||||
redirect_url += '&' + urllib.urlencode({'redirect_message': redirect_message})
|
||||
|
||||
return {
|
||||
"redirect_to": redirect_url,
|
||||
"status": status
|
||||
}
|
||||
|
|
@ -8,15 +8,17 @@ import json, urlparse
|
|||
from frappe.utils import get_request_session
|
||||
from frappe import _
|
||||
|
||||
def make_get_request(url, auth=None, data=None):
|
||||
def make_get_request(url, auth=None, headers=None, data=None):
|
||||
if not auth:
|
||||
auth = ''
|
||||
if not data:
|
||||
data = {}
|
||||
if not headers:
|
||||
headers = {}
|
||||
|
||||
try:
|
||||
s = get_request_session()
|
||||
frappe.flags.integration_request = s.get(url, data={}, auth=auth)
|
||||
frappe.flags.integration_request = s.get(url, data={}, auth=auth, headers=headers)
|
||||
frappe.flags.integration_request.raise_for_status()
|
||||
return frappe.flags.integration_request.json()
|
||||
|
||||
|
|
@ -24,20 +26,23 @@ def make_get_request(url, auth=None, data=None):
|
|||
frappe.log_error(frappe.get_traceback())
|
||||
raise exc
|
||||
|
||||
def make_post_request(url, auth=None, data=None):
|
||||
def make_post_request(url, auth=None, headers=None, data=None):
|
||||
if not auth:
|
||||
auth = ''
|
||||
if not data:
|
||||
data = {}
|
||||
if not headers:
|
||||
headers = {}
|
||||
|
||||
try:
|
||||
s = get_request_session()
|
||||
res = s.post(url, data=data, auth=auth)
|
||||
res.raise_for_status()
|
||||
frappe.flags.integration_request = s.post(url, data=data, auth=auth, headers=headers)
|
||||
frappe.flags.integration_request.raise_for_status()
|
||||
|
||||
if res.headers.get("content-type") == "text/plain; charset=utf-8":
|
||||
return urlparse.parse_qs(res.text)
|
||||
if frappe.flags.integration_request.headers.get("content-type") == "text/plain; charset=utf-8":
|
||||
return urlparse.parse_qs(frappe.flags.integration_request.text)
|
||||
|
||||
return res.json()
|
||||
return frappe.flags.integration_request.json()
|
||||
except Exception, exc:
|
||||
frappe.log_error()
|
||||
raise exc
|
||||
|
|
|
|||
47
frappe/templates/includes/integrations/stripe_checkout.js
Normal file
47
frappe/templates/includes/integrations/stripe_checkout.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
$(document).ready(function(){
|
||||
(function(e){
|
||||
var handler = StripeCheckout.configure({
|
||||
key: "{{ publishable_key }}",
|
||||
token: function(token) {
|
||||
// You can access the token ID with `token.id`.
|
||||
// Get the token ID to your server-side code for use.
|
||||
stripe.make_payment_log(token, {{ frappe.form_dict|json }}, "{{ reference_doctype }}", "{{ reference_docname }}");
|
||||
}
|
||||
});
|
||||
|
||||
handler.open({
|
||||
name: "{{payer_name}}",
|
||||
description: "{{description}}",
|
||||
amount: cint("{{ amount }}" * 100), // 2000 paise = INR 20
|
||||
email: "{{payer_email}}",
|
||||
currency: "{{currency}}"
|
||||
});
|
||||
|
||||
})();
|
||||
})
|
||||
|
||||
frappe.provide('stripe');
|
||||
|
||||
stripe.make_payment_log = function(token, data, doctype, docname){
|
||||
$('.stripe-loading').addClass('hidden');
|
||||
$('.stripe-confirming').removeClass('hidden');
|
||||
frappe.call({
|
||||
method:"frappe.templates.pages.integrations.stripe_checkout.make_payment",
|
||||
freeze:true,
|
||||
headers: {"X-Requested-With": "XMLHttpRequest"},
|
||||
args: {
|
||||
"stripe_token_id": token.id,
|
||||
"data": JSON.stringify(data),
|
||||
"reference_doctype": doctype,
|
||||
"reference_docname": docname
|
||||
},
|
||||
callback: function(r){
|
||||
if (r.message && r.message.status == 200) {
|
||||
window.location.href = r.message.redirect_to
|
||||
}
|
||||
else if (r.message && ([401,400,500].indexOf(r.message.status) > -1)) {
|
||||
window.location.href = r.message.redirect_to
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
28
frappe/templates/pages/integrations/stripe_checkout.html
Normal file
28
frappe/templates/pages/integrations/stripe_checkout.html
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
{% extends "templates/web.html" %}
|
||||
|
||||
{% block title %} Payment {% endblock %}
|
||||
|
||||
{%- block header -%}{% endblock %}
|
||||
|
||||
{% block script %}
|
||||
<script src="https://checkout.stripe.com/checkout.js"></script>
|
||||
<script>{% include "templates/includes/integrations/stripe_checkout.js" %}</script>
|
||||
{% endblock %}
|
||||
|
||||
{%- block page_content -%}
|
||||
|
||||
<p class='lead text-center centered'>
|
||||
<span class='stripe-loading'>Loading Payment System</span>
|
||||
<span class='stripe-confirming hidden'>Confirming Payment</span>
|
||||
</p>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block style %}
|
||||
<style>
|
||||
header, footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
{% endblock %}
|
||||
49
frappe/templates/pages/integrations/stripe_checkout.py
Normal file
49
frappe/templates/pages/integrations/stripe_checkout.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt, cint
|
||||
import json
|
||||
|
||||
no_cache = 1
|
||||
no_sitemap = 1
|
||||
|
||||
expected_keys = ('amount', 'title', 'description', 'reference_doctype', 'reference_docname',
|
||||
'payer_name', 'payer_email', 'order_id', 'currency')
|
||||
|
||||
def get_context(context):
|
||||
context.no_cache = 1
|
||||
context.publishable_key = get_api_key()
|
||||
|
||||
# all these keys exist in form_dict
|
||||
if not (set(expected_keys) - set(frappe.form_dict.keys())):
|
||||
for key in expected_keys:
|
||||
context[key] = frappe.form_dict[key]
|
||||
|
||||
context['amount'] = flt(context['amount'])
|
||||
|
||||
else:
|
||||
frappe.redirect_to_message(_('Some information is missing'),
|
||||
_('Looks like someone sent you to an incomplete URL. Please ask them to look into it.'))
|
||||
frappe.local.flags.redirect_location = frappe.local.response.location
|
||||
raise frappe.Redirect
|
||||
|
||||
def get_api_key():
|
||||
publishable_key = frappe.db.get_value("Stripe Settings", None, "publishable_key")
|
||||
if cint(frappe.form_dict.get("use_sandbox")):
|
||||
publishable_key = frappe.conf.sandbox_publishable_key
|
||||
|
||||
return publishable_key
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def make_payment(stripe_token_id, data, reference_doctype=None, reference_docname=None):
|
||||
data = json.loads(data)
|
||||
|
||||
data.update({
|
||||
"stripe_token_id": stripe_token_id
|
||||
})
|
||||
|
||||
data = frappe.get_doc("Stripe Settings").create_request(data)
|
||||
frappe.db.commit()
|
||||
return data
|
||||
Loading…
Add table
Reference in a new issue