Browse Source
* 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 hasattrdevelop
Charles-Henri Decultot
7 years ago
committed by
Rushabh Mehta
27 changed files with 1051 additions and 1 deletions
@ -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"), |
|||
} |
|||
] |
|||
} |
|||
] |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 83 KiB |
@ -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" |
@ -0,0 +1,5 @@ |
|||
// Copyright (c) 2018, Frappe Technologies and contributors
|
|||
// For license information, please see license.txt
|
|||
|
|||
frappe.ui.form.on('GoCardless Mandate', { |
|||
}); |
@ -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 |
|||
} |
@ -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 |
@ -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() |
|||
]); |
|||
|
|||
}); |
@ -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 |
@ -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") |
@ -0,0 +1,5 @@ |
|||
// Copyright (c) 2018, Frappe Technologies and contributors
|
|||
// For license information, please see license.txt
|
|||
|
|||
frappe.ui.form.on('GoCardless Settings', { |
|||
}); |
@ -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 |
|||
} |
@ -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 |
@ -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() |
|||
]); |
|||
|
|||
}); |
@ -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 |
@ -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 |
|||
} |
|||
} |
|||
}) |
|||
|
|||
}) |
@ -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; |
|||
} |
|||
} |
|||
}); |
|||
|
|||
}); |
@ -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 %} |
@ -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'} |
@ -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 %} |
@ -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()) |
Loading…
Reference in new issue