Gocardless integration (#13008)

* GoCardless integration

* Addition of a method for determining if the email should be sent or not

* Correction for Tests

* Codacy fix

* Documents moved to ERPNext

* Codacy fix

* Codacy fixes

* Remove method where not necessary and replace with hasattr
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 57ead28..2692f3c 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -34,7 +34,7 @@
 			frappe.throw(_("Transaction currency must be same as Payment Gateway currency"))
 
 	def on_submit(self):
-		send_mail = True
+		send_mail = self.payment_gateway_validation()
 		ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
 
 		if (hasattr(ref_doc, "order_type") and getattr(ref_doc, "order_type") == "Shopping Cart") \
@@ -58,6 +58,16 @@
 			si = si.insert(ignore_permissions=True)
 			si.submit()
 
+	def payment_gateway_validation(self):
+		try:
+			controller = get_payment_gateway_controller(self.payment_gateway)
+			if hasattr(controller, 'on_payment_request_submission'):
+				return controller.on_payment_request_submission(self)
+			else:
+				return True
+		except Exception:
+			return False
+
 	def set_payment_request_url(self):
 		if self.payment_account:
 			self.payment_url = self.get_payment_url()
diff --git a/erpnext/config/erpnext_integrations.py b/erpnext/config/erpnext_integrations.py
new file mode 100644
index 0000000..22a01a6
--- /dev/null
+++ b/erpnext/config/erpnext_integrations.py
@@ -0,0 +1,22 @@
+from __future__ import unicode_literals
+from frappe import _
+
+def get_data():
+	return [
+		{
+			"label": _("Payments"),
+			"icon": "fa fa-star",
+			"items": [
+				{
+					"type": "doctype",
+					"name": "GoCardless Settings",
+					"description": _("GoCardless payment gateway settings"),
+				},
+				{
+					"type": "doctype",
+					"name": "GoCardless Mandate",
+					"description": _("GoCardless SEPA Mandate"),
+				}
+			]
+		}
+	]
diff --git a/erpnext/docs/assets/img/setup/integrations/gocardless_account.png b/erpnext/docs/assets/img/setup/integrations/gocardless_account.png
new file mode 100644
index 0000000..db6bcc9
--- /dev/null
+++ b/erpnext/docs/assets/img/setup/integrations/gocardless_account.png
Binary files differ
diff --git a/erpnext/docs/assets/img/setup/integrations/gocardless_coa.png b/erpnext/docs/assets/img/setup/integrations/gocardless_coa.png
new file mode 100644
index 0000000..17b5f59
--- /dev/null
+++ b/erpnext/docs/assets/img/setup/integrations/gocardless_coa.png
Binary files differ
diff --git a/erpnext/docs/assets/img/setup/integrations/payment_gateway_account_gocardless.png b/erpnext/docs/assets/img/setup/integrations/payment_gateway_account_gocardless.png
new file mode 100644
index 0000000..2c08e9f
--- /dev/null
+++ b/erpnext/docs/assets/img/setup/integrations/payment_gateway_account_gocardless.png
Binary files differ
diff --git a/erpnext/docs/user/manual/en/setting-up/integrations/gocardless-integration.md b/erpnext/docs/user/manual/en/setting-up/integrations/gocardless-integration.md
new file mode 100644
index 0000000..29ede4a
--- /dev/null
+++ b/erpnext/docs/user/manual/en/setting-up/integrations/gocardless-integration.md
@@ -0,0 +1,44 @@
+# Setting up GoCardless
+
+To setup GoCardless, go to `Explore > Integrations > GoCardless Settings`
+
+## Setup GoCardless
+
+To enable GoCardless in your ERPNext account, you need to configure the following parameters and Access Token and optionally (but highly recommended), a Webhooks Secret key.
+
+
+You can setup several GoCardless payment gateways if needed. The choice of payment gateway account will determine which GoCardless account is used for the payment.
+
+![GoCardless Settings](/docs/assets/img/setup/integrations/gocardless_account.png)
+
+On enabling service, the system will create a Payment Gateway record and an Account head in chart of account with account type as Bank.
+
+![GoCardless COA](/docs/assets/img/setup/integrations/gocardless_coa.png)
+
+It will also create a payment gateway account. You can change the default bank account if needed and create a template for the payment request.
+
+![Payment Gateway Account](/docs/assets/img/setup/integrations/payment_gateway_account_gocardless.png)
+
+After configuring the Payment Gateway Account, your system is able to accept online payments through GoCardless.
+
+## SEPA Payments Flow
+
+When a new payment SEPA payment in initiated, the customer is asked to enter his IBAN (or local account number) and to validate a SEPA mandate.
+
+Upon validation of the mandate, a payment request is sent to GoCardless and processed.
+
+If the customer has already a valid SEPA mandate, when instead of sending a payment request to the customer, the payment request is directly sent to GoCardless without the need for the customer to validate it.
+The customer will only receive a confirmation email from GoCardless informing him that a payment has been processed.
+
+
+## Mandate cancellation
+
+You can setup a Webhook in GoCardless to automatically disabled cancelled or expired mandates in ERPNext.
+
+The Endpoint URL of your webhook should be: https://yoursite.com/api/method/erpnext.erpnext_integrations.doctype.gocardless_settings.webhooks
+
+In this case do not forget to configure your Webhooks Secret Key in your GoCardless account settings in ERPNext.
+
+
+## Supported transaction currencies
+	"EUR", "DKK", "GBP", "SEK"
diff --git a/erpnext/erpnext_integrations/doctype/__init__.py b/erpnext/erpnext_integrations/doctype/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/__init__.py
diff --git a/erpnext/erpnext_integrations/doctype/gocardless_mandate/__init__.py b/erpnext/erpnext_integrations/doctype/gocardless_mandate/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/gocardless_mandate/__init__.py
diff --git a/erpnext/erpnext_integrations/doctype/gocardless_mandate/gocardless_mandate.js b/erpnext/erpnext_integrations/doctype/gocardless_mandate/gocardless_mandate.js
new file mode 100644
index 0000000..37f9f7b
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/gocardless_mandate/gocardless_mandate.js
@@ -0,0 +1,5 @@
+// Copyright (c) 2018, Frappe Technologies and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('GoCardless Mandate', {
+});
diff --git a/erpnext/erpnext_integrations/doctype/gocardless_mandate/gocardless_mandate.json b/erpnext/erpnext_integrations/doctype/gocardless_mandate/gocardless_mandate.json
new file mode 100644
index 0000000..edf652c
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/gocardless_mandate/gocardless_mandate.json
@@ -0,0 +1,184 @@
+{
+ "allow_copy": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "autoname": "field:mandate",
+ "beta": 0,
+ "creation": "2018-02-08 11:33:15.721919",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+  {
+   "allow_bulk_edit": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "disabled",
+   "fieldtype": "Check",
+   "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": "Disabled",
+   "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": 0,
+   "search_index": 0,
+   "set_only_once": 0,
+   "unique": 0
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "customer",
+   "fieldtype": "Link",
+   "hidden": 0,
+   "ignore_user_permissions": 0,
+   "ignore_xss_filter": 0,
+   "in_filter": 0,
+   "in_global_search": 0,
+   "in_list_view": 1,
+   "in_standard_filter": 0,
+   "label": "Customer",
+   "length": 0,
+   "no_copy": 0,
+   "options": "Customer",
+   "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_bulk_edit": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "mandate",
+   "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": "Mandate",
+   "length": 0,
+   "no_copy": 0,
+   "permlevel": 0,
+   "precision": "",
+   "print_hide": 0,
+   "print_hide_if_no_value": 0,
+   "read_only": 1,
+   "remember_last_selected_value": 0,
+   "report_hide": 0,
+   "reqd": 1,
+   "search_index": 0,
+   "set_only_once": 0,
+   "unique": 0
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "gocardless_customer",
+   "fieldtype": "Data",
+   "hidden": 0,
+   "ignore_user_permissions": 0,
+   "ignore_xss_filter": 0,
+   "in_filter": 0,
+   "in_global_search": 0,
+   "in_list_view": 1,
+   "in_standard_filter": 0,
+   "label": "GoCardless Customer",
+   "length": 0,
+   "no_copy": 0,
+   "permlevel": 0,
+   "precision": "",
+   "print_hide": 0,
+   "print_hide_if_no_value": 0,
+   "read_only": 1,
+   "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": 0,
+ "istable": 0,
+ "max_attachments": 0,
+ "modified": "2018-02-11 12:28:03.183095",
+ "modified_by": "Administrator",
+ "module": "ERPNext Integrations",
+ "name": "GoCardless Mandate",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "amend": 0,
+   "apply_user_permissions": 0,
+   "cancel": 0,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "if_owner": 0,
+   "import": 0,
+   "permlevel": 0,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "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": 1,
+ "track_seen": 0
+}
diff --git a/erpnext/erpnext_integrations/doctype/gocardless_mandate/gocardless_mandate.py b/erpnext/erpnext_integrations/doctype/gocardless_mandate/gocardless_mandate.py
new file mode 100644
index 0000000..9c9df65
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/gocardless_mandate/gocardless_mandate.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2018, Frappe Technologies and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+from frappe.model.document import Document
+
+class GoCardlessMandate(Document):
+	pass
diff --git a/erpnext/erpnext_integrations/doctype/gocardless_mandate/test_gocardless_mandate.js b/erpnext/erpnext_integrations/doctype/gocardless_mandate/test_gocardless_mandate.js
new file mode 100644
index 0000000..caa9399
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/gocardless_mandate/test_gocardless_mandate.js
@@ -0,0 +1,23 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: GoCardless Mandate", function (assert) {
+	let done = assert.async();
+
+	// number of asserts
+	assert.expect(1);
+
+	frappe.run_serially([
+		// insert a new GoCardless Mandate
+		() => frappe.tests.make('GoCardless Mandate', [
+			// values to be set
+			{key: 'value'}
+		]),
+		() => {
+			assert.equal(cur_frm.doc.key, 'value');
+		},
+		() => done()
+	]);
+
+});
diff --git a/erpnext/erpnext_integrations/doctype/gocardless_mandate/test_gocardless_mandate.py b/erpnext/erpnext_integrations/doctype/gocardless_mandate/test_gocardless_mandate.py
new file mode 100644
index 0000000..d77a352
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/gocardless_mandate/test_gocardless_mandate.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2018, Frappe Technologies and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import unittest
+
+class TestGoCardlessMandate(unittest.TestCase):
+	pass
diff --git a/erpnext/erpnext_integrations/doctype/gocardless_settings/__init__.py b/erpnext/erpnext_integrations/doctype/gocardless_settings/__init__.py
new file mode 100644
index 0000000..25784a5
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/gocardless_settings/__init__.py
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2018, Frappe Technologies and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+import json
+import hmac
+import hashlib
+
+@frappe.whitelist(allow_guest=True)
+def webhooks():
+	r = frappe.request
+	if not r:
+		return
+
+	if not authenticate_signature(r):
+		raise frappe.AuthenticationError
+
+	gocardless_events = json.loads(r.get_data()) or []
+	for event in gocardless_events["events"]:
+		set_status(event)
+
+	return 200
+def set_status(event):
+	resource_type = event.get("resource_type", {})
+
+	if resource_type == "mandates":
+		set_mandate_status(event)
+
+def set_mandate_status(event):
+	mandates = []
+	if isinstance(event["links"], (list,)):
+		for link in event["links"]:
+			mandates.append(link["mandate"])
+	else:
+		mandates.append(event["links"]["mandate"])
+
+	if event["action"] == "pending_customer_approval" or event["action"] == "pending_submission" or event["action"] == "submitted" or event["action"] == "active":
+		disabled = 0
+	else:
+		disabled = 1
+
+	for mandate in mandates:
+		frappe.db.set_value("GoCardless Mandate", mandate, "disabled", disabled)
+
+def authenticate_signature(r):
+	"""Returns True if the received signature matches the generated signature"""
+	received_signature = frappe.get_request_header("Webhook-Signature")
+
+	if not received_signature:
+		return False
+
+	for key in get_webhook_keys():
+		computed_signature = hmac.new(key.encode("utf-8"), r.get_data(), hashlib.sha256).hexdigest()
+		if hmac.compare_digest(str(received_signature), computed_signature):
+			return True
+
+	return False
+
+def get_webhook_keys():
+	def _get_webhook_keys():
+		webhook_keys = [d.webhooks_secret for d in frappe.get_all("GoCardless Settings", fields=["webhooks_secret"],) if d.webhooks_secret]
+
+		return webhook_keys
+
+	return frappe.cache().get_value("gocardless_webhooks_secret", _get_webhook_keys)
+
+def clear_cache():
+	frappe.cache().delete_value("gocardless_webhooks_secret")
diff --git a/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.js b/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.js
new file mode 100644
index 0000000..b649d9d
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.js
@@ -0,0 +1,5 @@
+// Copyright (c) 2018, Frappe Technologies and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('GoCardless Settings', {
+});
diff --git a/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.json b/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.json
new file mode 100644
index 0000000..9738106
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.json
@@ -0,0 +1,212 @@
+{
+ "allow_copy": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "autoname": "field:gateway_name",
+ "beta": 0,
+ "creation": "2018-02-06 16:11:10.028249",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+  {
+   "allow_bulk_edit": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "gateway_name",
+   "fieldtype": "Data",
+   "hidden": 0,
+   "ignore_user_permissions": 0,
+   "ignore_xss_filter": 0,
+   "in_filter": 0,
+   "in_global_search": 0,
+   "in_list_view": 1,
+   "in_standard_filter": 0,
+   "label": "Payment Gateway Name",
+   "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_bulk_edit": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "section_break_2",
+   "fieldtype": "Section Break",
+   "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,
+   "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": 0,
+   "search_index": 0,
+   "set_only_once": 0,
+   "unique": 0
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "access_token",
+   "fieldtype": "Data",
+   "hidden": 0,
+   "ignore_user_permissions": 0,
+   "ignore_xss_filter": 0,
+   "in_filter": 0,
+   "in_global_search": 0,
+   "in_list_view": 1,
+   "in_standard_filter": 0,
+   "label": "Access Token",
+   "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_bulk_edit": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "webhooks_secret",
+   "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": "Webhooks Secret",
+   "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": 0,
+   "search_index": 0,
+   "set_only_once": 0,
+   "unique": 0
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "use_sandbox",
+   "fieldtype": "Check",
+   "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": "Use Sandbox",
+   "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": 0,
+   "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": 0,
+ "istable": 0,
+ "max_attachments": 0,
+ "modified": "2018-02-12 14:18:47.209114",
+ "modified_by": "Administrator",
+ "module": "ERPNext Integrations",
+ "name": "GoCardless Settings",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "amend": 0,
+   "apply_user_permissions": 0,
+   "cancel": 0,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "if_owner": 0,
+   "import": 0,
+   "permlevel": 0,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "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": 1,
+ "track_seen": 0
+}
diff --git a/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py b/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py
new file mode 100644
index 0000000..af15cf5
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py
@@ -0,0 +1,183 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2018, Frappe Technologies and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+import gocardless_pro
+from frappe import _
+from six.moves.urllib.parse import urlencode
+from frappe.utils import get_url, call_hook_method, flt, cint
+from frappe.integrations.utils import create_request_log, create_payment_gateway
+
+class GoCardlessSettings(Document):
+	supported_currencies = ["EUR", "DKK", "GBP", "SEK"]
+
+	def validate(self):
+		self.initialize_client()
+
+	def initialize_client(self):
+		self.environment = self.get_environment()
+		try:
+			self.client = gocardless_pro.Client(
+				access_token=self.access_token,
+				environment=self.environment
+				)
+			return self.client
+		except Exception as e:
+			frappe.throw(e)
+
+	def on_update(self):
+		create_payment_gateway('GoCardless-' + self.gateway_name, settings='GoCardLess Settings', controller=self.gateway_name)
+		call_hook_method('payment_gateway_enabled', gateway='GoCardless-' + self.gateway_name)
+
+	def on_payment_request_submission(self, data):
+		if data.reference_doctype != "Fees":
+			customer_data = frappe.db.get_value(data.reference_doctype, data.reference_name, ["company", "customer_name"], as_dict=1)
+
+		data = {
+					"amount": flt(data.grand_total, data.precision("grand_total")),
+					"title": customer_data.company.encode("utf-8"),
+					"description": data.subject.encode("utf-8"),
+					"reference_doctype": data.doctype,
+					"reference_docname": data.name,
+					"payer_email": data.email_to or frappe.session.user,
+					"payer_name": customer_data.customer_name,
+					"order_id": data.name,
+					"currency": data.currency
+				}
+
+		valid_mandate = self.check_mandate_validity(data)
+		if valid_mandate is not None:
+			data.update(valid_mandate)
+
+			self.create_payment_request(data)
+			return False
+		else:
+			return True
+
+	def check_mandate_validity(self, data):
+
+		if frappe.db.exists("GoCardless Mandate", dict(customer=data.get('payer_name'), disabled=0)):
+			registered_mandate = frappe.db.get_value("GoCardless Mandate", dict(customer=data.get('payer_name'), disabled=0), 'mandate')
+			self.initialize_client()
+			mandate = self.client.mandates.get(registered_mandate)
+
+			if mandate.status=="pending_customer_approval" or mandate.status=="pending_submission" or mandate.status=="submitted" or mandate.status=="active":
+				return {"mandate": registered_mandate}
+			else:
+				return None
+		else:
+			return None
+
+	def get_environment(self):
+		if self.use_sandbox:
+			return 'sandbox'
+		else:
+			return 'live'
+
+	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/gocardless_checkout?{0}".format(urlencode(kwargs)))
+
+	def create_payment_request(self, data):
+		self.data = frappe._dict(data)
+
+		try:
+			self.integration_request = create_request_log(self.data, "Host", "GoCardless")
+			return self.create_charge_on_gocardless()
+
+		except Exception:
+			frappe.log_error(frappe.get_traceback())
+			return{
+				"redirect_to": frappe.redirect_to_message(_('Server Error'), _("There seems to be an issue with the server's GoCardless configuration. Don't worry, in case of failure, the amount will get refunded to your account.")),
+				"status": 401
+			}
+
+	def create_charge_on_gocardless(self):
+		redirect_to = self.data.get('redirect_to') or None
+		redirect_message = self.data.get('redirect_message') or None
+
+		reference_doc = frappe.get_doc(self.data.get('reference_doctype'), self.data.get('reference_docname'))
+		self.initialize_client()
+
+		try:
+			payment = self.client.payments.create(
+				params={
+					"amount" : cint(reference_doc.grand_total * 100),
+					"currency" : reference_doc.currency,
+					"links" : {
+						"mandate": self.data.get('mandate')
+					},
+					"metadata": {
+					  "reference_doctype": reference_doc.doctype,
+					  "reference_document": reference_doc.name
+					}
+				}, headers={
+					'Idempotency-Key' : self.data.get('reference_docname'),
+			})
+
+			if payment.status=="pending_submission" or payment.status=="pending_customer_approval" or payment.status=="submitted":
+				self.integration_request.db_set('status', 'Authorized', update_modified=False)
+				self.flags.status_changed_to = "Completed"
+				self.integration_request.db_set('output', payment.status, update_modified=False)
+
+			elif payment.status=="confirmed" or payment.status=="paid_out":
+				self.integration_request.db_set('status', 'Completed', update_modified=False)
+				self.flags.status_changed_to = "Completed"
+				self.integration_request.db_set('output', payment.status, update_modified=False)
+
+			elif payment.status=="cancelled" or payment.status=="customer_approval_denied" or payment.status=="charged_back":
+				self.integration_request.db_set('status', 'Cancelled', update_modified=False)
+				frappe.log_error(_("Payment Cancelled. Please check your GoCardless Account for more details"), "GoCardless Payment Error")
+				self.integration_request.db_set('error', payment.status, update_modified=False)
+			else:
+				self.integration_request.db_set('status', 'Failed', update_modified=False)
+				frappe.log_error(_("Payment Failed. Please check your GoCardless Account for more details"), "GoCardless Payment Error")
+				self.integration_request.db_set('error', payment.status, update_modified=False)
+
+		except Exception as e:
+			frappe.log_error(e, "GoCardless Payment Error")
+
+		if self.flags.status_changed_to == "Completed":
+			status = '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 = redirect_to
+		else:
+			status = 'Error'
+			redirect_url = 'payment-failed'
+
+			if redirect_message:
+				redirect_url += '&' + urlencode({'redirect_message': redirect_message})
+
+			redirect_url = get_url(redirect_url)
+
+		return {
+			"redirect_to": redirect_url,
+			"status": status
+			}
+
+def get_gateway_controller(doc):
+	payment_request = frappe.get_doc("Payment Request", doc)
+	gateway_controller = frappe.db.get_value("Payment Gateway", payment_request.payment_gateway, "gateway_controller")
+	return gateway_controller
+
+def gocardless_initialization(doc):
+	gateway_controller = get_gateway_controller(doc)
+	settings = frappe.get_doc("GoCardless Settings", gateway_controller)
+	client = settings.initialize_client()
+	return client
diff --git a/erpnext/erpnext_integrations/doctype/gocardless_settings/test_gocardless_settings.js b/erpnext/erpnext_integrations/doctype/gocardless_settings/test_gocardless_settings.js
new file mode 100644
index 0000000..b6daad8
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/gocardless_settings/test_gocardless_settings.js
@@ -0,0 +1,23 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: GoCardless Settings", function (assert) {
+	let done = assert.async();
+
+	// number of asserts
+	assert.expect(1);
+
+	frappe.run_serially([
+		// insert a new GoCardless Settings
+		() => frappe.tests.make('GoCardless Settings', [
+			// values to be set
+			{key: 'value'}
+		]),
+		() => {
+			assert.equal(cur_frm.doc.key, 'value');
+		},
+		() => done()
+	]);
+
+});
diff --git a/erpnext/erpnext_integrations/doctype/gocardless_settings/test_gocardless_settings.py b/erpnext/erpnext_integrations/doctype/gocardless_settings/test_gocardless_settings.py
new file mode 100644
index 0000000..e377f34
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/gocardless_settings/test_gocardless_settings.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2018, Frappe Technologies and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import unittest
+
+class TestGoCardlessSettings(unittest.TestCase):
+	pass
diff --git a/erpnext/templates/includes/integrations/gocardless_checkout.js b/erpnext/templates/includes/integrations/gocardless_checkout.js
new file mode 100644
index 0000000..b18d550
--- /dev/null
+++ b/erpnext/templates/includes/integrations/gocardless_checkout.js
@@ -0,0 +1,24 @@
+$(document).ready(function() {
+	var data = {{ frappe.form_dict | json }};
+	var doctype = "{{ reference_doctype }}"
+	var docname = "{{ reference_docname }}"
+
+	frappe.call({
+		method: "erpnext.templates.pages.integrations.gocardless_checkout.check_mandate",
+		freeze: true,
+		headers: {
+			"X-Requested-With": "XMLHttpRequest"
+		},
+		args: {
+			"data": JSON.stringify(data),
+			"reference_doctype": doctype,
+			"reference_docname": docname
+		},
+		callback: function(r) {
+			if (r.message) {
+				window.location.href = r.message.redirect_to
+			}
+		}
+	})
+
+})
diff --git a/erpnext/templates/includes/integrations/gocardless_confirmation.js b/erpnext/templates/includes/integrations/gocardless_confirmation.js
new file mode 100644
index 0000000..fee1d2b
--- /dev/null
+++ b/erpnext/templates/includes/integrations/gocardless_confirmation.js
@@ -0,0 +1,24 @@
+$(document).ready(function() {
+	var redirect_flow_id = "{{ redirect_flow_id }}";
+	var doctype = "{{ reference_doctype }}";
+	var docname = "{{ reference_docname }}";
+
+	frappe.call({
+		method: "erpnext.templates.pages.integrations.gocardless_confirmation.confirm_payment",
+		freeze: true,
+		headers: {
+			"X-Requested-With": "XMLHttpRequest"
+		},
+		args: {
+			"redirect_flow_id": redirect_flow_id,
+			"reference_doctype": doctype,
+			"reference_docname": docname
+		},
+		callback: function(r) {
+			if (r.message) {
+				window.location.href = r.message.redirect_to;
+			}
+		}
+	});
+
+});
diff --git a/erpnext/templates/pages/integrations/__init__.py b/erpnext/templates/pages/integrations/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/templates/pages/integrations/__init__.py
diff --git a/erpnext/templates/pages/integrations/gocardless_checkout.html b/erpnext/templates/pages/integrations/gocardless_checkout.html
new file mode 100644
index 0000000..eb124ca
--- /dev/null
+++ b/erpnext/templates/pages/integrations/gocardless_checkout.html
@@ -0,0 +1,16 @@
+{% extends "templates/web.html" %}
+
+{% block title %} Payment {% endblock %}
+
+{%- block header -%}{% endblock %}
+
+{% block script %}
+<script>{% include "templates/includes/integrations/gocardless_checkout.js" %}</script>
+{% endblock %}
+
+{%- block page_content -%}
+<p class='lead text-center centered'>
+	<span class='gocardless-loading'>{{ _("Loading Payment System") }}</span>
+</p>
+
+{% endblock %}
diff --git a/erpnext/templates/pages/integrations/gocardless_checkout.py b/erpnext/templates/pages/integrations/gocardless_checkout.py
new file mode 100644
index 0000000..3c2466e
--- /dev/null
+++ b/erpnext/templates/pages/integrations/gocardless_checkout.py
@@ -0,0 +1,76 @@
+# Copyright (c) 2018, 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
+import json
+from erpnext.erpnext_integrations.doctype.gocardless_settings.gocardless_settings import gocardless_initialization, get_gateway_controller
+from frappe.utils import get_url
+
+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
+
+	# 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'])
+
+		gateway_controller = get_gateway_controller(context.reference_docname)
+		context['header_img'] = frappe.db.get_value("GoCardless Settings", gateway_controller, "header_img")
+
+	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
+
+@frappe.whitelist(allow_guest=True)
+def check_mandate(data, reference_doctype, reference_docname):
+	data = json.loads(data)
+
+	client = gocardless_initialization(reference_docname)
+
+	payer = frappe.get_doc("Customer", data["payer_name"])
+
+	if payer.customer_type == "Individual" and payer.customer_primary_contact is not None:
+		primary_contact = frappe.get_doc("Contact", payer.customer_primary_contact)
+		prefilled_customer = {
+			"company_name": payer.name,
+			"given_name": primary_contact.first_name,
+			"family_name": primary_contact.last_name,
+		}
+		if primary_contact.email_id is not None:
+			prefilled_customer.update({"email": primary_contact.email_id})
+		else:
+			prefilled_customer.update({"email": frappe.session.user})
+
+	else:
+		prefilled_customer = {
+			"company_name": payer.name,
+			"email": frappe.session.user
+		}
+
+	success_url = get_url("./integrations/gocardless_confirmation?reference_doctype=" + reference_doctype + "&reference_docname=" + reference_docname)
+
+	try:
+		redirect_flow = client.redirect_flows.create(params={
+							"description": _("Pay {0} {1}".format(data['amount'], data['currency'])),
+							"session_token": frappe.session.user,
+							"success_redirect_url": success_url,
+							"prefilled_customer": prefilled_customer
+						})
+
+		return {"redirect_to": redirect_flow.redirect_url}
+
+	except Exception as e:
+		frappe.log_error(e, "GoCardless Payment Error")
+		return {"redirect_to": '/integrations/payment-failed'}
diff --git a/erpnext/templates/pages/integrations/gocardless_confirmation.html b/erpnext/templates/pages/integrations/gocardless_confirmation.html
new file mode 100644
index 0000000..1baf23b
--- /dev/null
+++ b/erpnext/templates/pages/integrations/gocardless_confirmation.html
@@ -0,0 +1,16 @@
+{% extends "templates/web.html" %}
+
+{% block title %} Payment {% endblock %}
+
+{%- block header -%}{% endblock %}
+
+{% block script %}
+<script>{% include "templates/includes/integrations/gocardless_confirmation.js" %}</script>
+{% endblock %}
+
+{%- block page_content -%}
+<p class='lead text-center centered'>
+	<span class='gocardless-loading'>{{ _("Payment Confirmation") }}</span>
+</p>
+
+{% endblock %}
diff --git a/erpnext/templates/pages/integrations/gocardless_confirmation.py b/erpnext/templates/pages/integrations/gocardless_confirmation.py
new file mode 100644
index 0000000..fc564c3
--- /dev/null
+++ b/erpnext/templates/pages/integrations/gocardless_confirmation.py
@@ -0,0 +1,85 @@
+# Copyright (c) 2018, 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 erpnext.erpnext_integrations.doctype.gocardless_settings.gocardless_settings import gocardless_initialization, get_gateway_controller
+
+no_cache = 1
+no_sitemap = 1
+
+expected_keys = ('redirect_flow_id', 'reference_doctype', 'reference_docname')
+
+def get_context(context):
+	context.no_cache = 1
+
+	# 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]
+
+	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
+
+@frappe.whitelist(allow_guest=True)
+def confirm_payment(redirect_flow_id, reference_doctype, reference_docname):
+
+	client = gocardless_initialization(reference_docname)
+
+	try:
+		redirect_flow = client.redirect_flows.complete(
+			redirect_flow_id,
+			params={
+				"session_token": frappe.session.user
+		})
+
+		data = {
+			"mandate": redirect_flow.links.mandate,
+			"customer": redirect_flow.links.customer,
+			"redirect_to": redirect_flow.confirmation_url,
+			"redirect_message": "Mandate successfully created",
+			"reference_doctype": reference_doctype,
+			"reference_docname": reference_docname
+		}
+
+		try:
+			create_mandate(data)
+		except Exception as e:
+			frappe.log_error(e, "GoCardless Mandate Registration Error")
+
+		gateway_controller = get_gateway_controller(reference_docname)
+		frappe.get_doc("GoCardless Settings", gateway_controller).create_payment_request(data)
+
+		return {"redirect_to": redirect_flow.confirmation_url}
+
+	except Exception as e:
+		frappe.log_error(e, "GoCardless Payment Error")
+		return {"redirect_to": '/integrations/payment-failed'}
+
+
+def create_mandate(data):
+	data = frappe._dict(data)
+	frappe.logger().debug(data)
+
+	mandate = data.get('mandate')
+
+	if frappe.db.exists("GoCardless Mandate", mandate):
+		return
+
+	else:
+		reference_doc = frappe.db.get_value(data.get('reference_doctype'), data.get('reference_docname'), ["reference_doctype", "reference_name"], as_dict=1)
+		erpnext_customer = frappe.db.get_value(reference_doc.reference_doctype, reference_doc.reference_name, ["customer_name"], as_dict=1)
+
+		try:
+			frappe.get_doc({
+			"doctype": "GoCardless Mandate",
+			"mandate": mandate,
+			"customer": erpnext_customer.customer_name,
+			"gocardless_customer": data.get('customer')
+			}).insert(ignore_permissions=True)
+
+		except Exception:
+			frappe.log_error(frappe.get_traceback())
diff --git a/requirements.txt b/requirements.txt
index f21cd8f..c5641f5 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,3 +4,4 @@
 googlemaps
 python-stdnum
 braintree
+gocardless_pro