@@ -1116,7 +1116,7 @@ def as_json(obj, indent=1): | |||||
return json.dumps(obj, indent=indent, sort_keys=True, default=json_handler) | return json.dumps(obj, indent=indent, sort_keys=True, default=json_handler) | ||||
def are_emails_muted(): | def are_emails_muted(): | ||||
return flags.mute_emails or conf.get("mute_emails") or False | |||||
return flags.mute_emails or int(conf.get("mute_emails") or 0) or False | |||||
def get_test_records(doctype): | def get_test_records(doctype): | ||||
"""Returns list of objects from `test_records.json` in the given doctype's folder.""" | """Returns list of objects from `test_records.json` in the given doctype's folder.""" | ||||
@@ -52,7 +52,7 @@ def build_docs(context, app, docs_version="current", target=None, local=False, w | |||||
or "docs.py" in source_path): | or "docs.py" in source_path): | ||||
_build_docs_once(site, app, docs_version, target, local, only_content_updated=True) | _build_docs_once(site, app, docs_version, target, local, only_content_updated=True) | ||||
apps_path = frappe.get_app_path("frappe", "..", "..") | |||||
apps_path = frappe.get_app_path(app, "..", "..") | |||||
start_watch(apps_path, handler=trigger_make) | start_watch(apps_path, handler=trigger_make) | ||||
def _build_docs_once(site, app, docs_version, target, local, only_content_updated=False): | def _build_docs_once(site, app, docs_version, target, local, only_content_updated=False): | ||||
@@ -156,8 +156,8 @@ def get_data(): | |||||
}, | }, | ||||
{ | { | ||||
"type": "doctype", | "type": "doctype", | ||||
"name": "Email Group Member", | |||||
"description": _("Email Group Member List"), | |||||
"name": "Auto Email Report", | |||||
"description": _("Setup Reports to be emailed at regular intervals"), | |||||
}, | }, | ||||
] | ] | ||||
}, | }, | ||||
@@ -41,10 +41,10 @@ and much more out of the box.</p> | |||||
<p>Frappe also has a plug-in architecture that can be used to build plugins | <p>Frappe also has a plug-in architecture that can be used to build plugins | ||||
to ERPNext.</p> | to ERPNext.</p> | ||||
<p>Frappe Framework was designed to build <a href="https://erpnext.com">ERPNext</a>, open source | |||||
<p>Frappe Framework was designed to build <a href="https://erpnext.com" rel="nofollow">ERPNext</a>, open source | |||||
ERP for managing small and medium sized businesses.</p> | ERP for managing small and medium sized businesses.</p> | ||||
<p><a href="https://frappe.github.io/frappe/user/">Get started with the Tutorial</a></p> | |||||
<p><a href="https://frappe.github.io/frappe/user/" rel="nofollow">Get started with the Tutorial</a></p> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
@@ -49,7 +49,7 @@ frappe.ui.form.on('Auto Email Report', { | |||||
&& frappe.query_reports[frm.doc.report].filters) { | && frappe.query_reports[frm.doc.report].filters) { | ||||
// make a table to show filters | // make a table to show filters | ||||
var table = $('<table class="table table-bordered" style="cursor:pointer;"><thead>\ | |||||
var table = $('<table class="table table-bordered" style="cursor:pointer; margin:0px;"><thead>\ | |||||
<tr><th style="width: 50%">'+__('Filter')+'</th><th>'+__('Value')+'</th></tr>\ | <tr><th style="width: 50%">'+__('Filter')+'</th><th>'+__('Value')+'</th></tr>\ | ||||
</thead><tbody></tbody></table>').appendTo(wrapper); | </thead><tbody></tbody></table>').appendTo(wrapper); | ||||
$('<p class="text-muted small">' + __("Click table to edit") + '</p>').appendTo(wrapper); | $('<p class="text-muted small">' + __("Click table to edit") + '</p>').appendTo(wrapper); | ||||
@@ -43,17 +43,43 @@ | |||||
"bold": 0, | "bold": 0, | ||||
"collapsible": 0, | "collapsible": 0, | ||||
"columns": 0, | "columns": 0, | ||||
"fieldname": "report_type", | |||||
"fieldtype": "Read Only", | |||||
"fieldname": "user", | |||||
"fieldtype": "Link", | |||||
"hidden": 0, | "hidden": 0, | ||||
"ignore_user_permissions": 0, | "ignore_user_permissions": 0, | ||||
"ignore_xss_filter": 0, | "ignore_xss_filter": 0, | ||||
"in_filter": 0, | "in_filter": 0, | ||||
"in_list_view": 0, | "in_list_view": 0, | ||||
"label": "Report Type", | |||||
"label": "Based on Permissions For User", | |||||
"length": 0, | |||||
"no_copy": 0, | |||||
"options": "User", | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"print_hide_if_no_value": 0, | |||||
"read_only": 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": "enabled", | |||||
"fieldtype": "Check", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"label": "Enabled", | |||||
"length": 0, | "length": 0, | ||||
"no_copy": 0, | "no_copy": 0, | ||||
"options": "report.report_type", | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"precision": "", | "precision": "", | ||||
"print_hide": 0, | "print_hide": 0, | ||||
@@ -70,24 +96,22 @@ | |||||
"bold": 0, | "bold": 0, | ||||
"collapsible": 0, | "collapsible": 0, | ||||
"columns": 0, | "columns": 0, | ||||
"fieldname": "user", | |||||
"fieldtype": "Link", | |||||
"fieldname": "column_break_4", | |||||
"fieldtype": "Column Break", | |||||
"hidden": 0, | "hidden": 0, | ||||
"ignore_user_permissions": 0, | "ignore_user_permissions": 0, | ||||
"ignore_xss_filter": 0, | "ignore_xss_filter": 0, | ||||
"in_filter": 0, | "in_filter": 0, | ||||
"in_list_view": 0, | "in_list_view": 0, | ||||
"label": "For User", | |||||
"length": 0, | "length": 0, | ||||
"no_copy": 0, | "no_copy": 0, | ||||
"options": "User", | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"precision": "", | "precision": "", | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | "print_hide_if_no_value": 0, | ||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 1, | |||||
"reqd": 0, | |||||
"search_index": 0, | "search_index": 0, | ||||
"set_only_once": 0, | "set_only_once": 0, | ||||
"unique": 0 | "unique": 0 | ||||
@@ -97,16 +121,17 @@ | |||||
"bold": 0, | "bold": 0, | ||||
"collapsible": 0, | "collapsible": 0, | ||||
"columns": 0, | "columns": 0, | ||||
"fieldname": "enabled", | |||||
"fieldtype": "Check", | |||||
"fieldname": "report_type", | |||||
"fieldtype": "Read Only", | |||||
"hidden": 0, | "hidden": 0, | ||||
"ignore_user_permissions": 0, | "ignore_user_permissions": 0, | ||||
"ignore_xss_filter": 0, | "ignore_xss_filter": 0, | ||||
"in_filter": 0, | "in_filter": 0, | ||||
"in_list_view": 0, | "in_list_view": 0, | ||||
"label": "Enabled", | |||||
"label": "Report Type", | |||||
"length": 0, | "length": 0, | ||||
"no_copy": 0, | "no_copy": 0, | ||||
"options": "report.report_type", | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"precision": "", | "precision": "", | ||||
"print_hide": 0, | "print_hide": 0, | ||||
@@ -228,17 +253,16 @@ | |||||
"bold": 0, | "bold": 0, | ||||
"collapsible": 0, | "collapsible": 0, | ||||
"columns": 0, | "columns": 0, | ||||
"fieldname": "frequency", | |||||
"fieldtype": "Select", | |||||
"fieldname": "email_to", | |||||
"fieldtype": "Small Text", | |||||
"hidden": 0, | "hidden": 0, | ||||
"ignore_user_permissions": 0, | "ignore_user_permissions": 0, | ||||
"ignore_xss_filter": 0, | "ignore_xss_filter": 0, | ||||
"in_filter": 0, | "in_filter": 0, | ||||
"in_list_view": 0, | "in_list_view": 0, | ||||
"label": "Frequency", | |||||
"label": "Email To", | |||||
"length": 0, | "length": 0, | ||||
"no_copy": 0, | "no_copy": 0, | ||||
"options": "Daily\nWeekly\nMonthly", | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"precision": "", | "precision": "", | ||||
"print_hide": 0, | "print_hide": 0, | ||||
@@ -279,6 +303,58 @@ | |||||
"set_only_once": 0, | "set_only_once": 0, | ||||
"unique": 0 | "unique": 0 | ||||
}, | }, | ||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"columns": 0, | |||||
"fieldname": "column_break_13", | |||||
"fieldtype": "Column Break", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"length": 0, | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"columns": 0, | |||||
"fieldname": "frequency", | |||||
"fieldtype": "Select", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 1, | |||||
"label": "Frequency", | |||||
"length": 0, | |||||
"no_copy": 0, | |||||
"options": "Daily\nWeekly\nMonthly", | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | |||||
"report_hide": 0, | |||||
"reqd": 1, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | { | ||||
"allow_on_submit": 0, | "allow_on_submit": 0, | ||||
"bold": 0, | "bold": 0, | ||||
@@ -309,16 +385,16 @@ | |||||
{ | { | ||||
"allow_on_submit": 0, | "allow_on_submit": 0, | ||||
"bold": 0, | "bold": 0, | ||||
"collapsible": 0, | |||||
"collapsible": 1, | |||||
"columns": 0, | "columns": 0, | ||||
"fieldname": "email_to", | |||||
"fieldtype": "Small Text", | |||||
"fieldname": "section_break_15", | |||||
"fieldtype": "Section Break", | |||||
"hidden": 0, | "hidden": 0, | ||||
"ignore_user_permissions": 0, | "ignore_user_permissions": 0, | ||||
"ignore_xss_filter": 0, | "ignore_xss_filter": 0, | ||||
"in_filter": 0, | "in_filter": 0, | ||||
"in_list_view": 0, | "in_list_view": 0, | ||||
"label": "Email To", | |||||
"label": "Message", | |||||
"length": 0, | "length": 0, | ||||
"no_copy": 0, | "no_copy": 0, | ||||
"permlevel": 0, | "permlevel": 0, | ||||
@@ -327,7 +403,7 @@ | |||||
"print_hide_if_no_value": 0, | "print_hide_if_no_value": 0, | ||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 1, | |||||
"reqd": 0, | |||||
"search_index": 0, | "search_index": 0, | ||||
"set_only_once": 0, | "set_only_once": 0, | ||||
"unique": 0 | "unique": 0 | ||||
@@ -344,7 +420,7 @@ | |||||
"ignore_xss_filter": 0, | "ignore_xss_filter": 0, | ||||
"in_filter": 0, | "in_filter": 0, | ||||
"in_list_view": 0, | "in_list_view": 0, | ||||
"label": "Description", | |||||
"label": "Message", | |||||
"length": 0, | "length": 0, | ||||
"no_copy": 0, | "no_copy": 0, | ||||
"permlevel": 0, | "permlevel": 0, | ||||
@@ -369,7 +445,7 @@ | |||||
"issingle": 0, | "issingle": 0, | ||||
"istable": 0, | "istable": 0, | ||||
"max_attachments": 0, | "max_attachments": 0, | ||||
"modified": "2016-09-14 02:00:21.618956", | |||||
"modified": "2016-09-15 01:32:57.454880", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Email", | "module": "Email", | ||||
"name": "Auto Email Report", | "name": "Auto Email Report", | ||||
@@ -67,8 +67,13 @@ class AutoEmailReport(Document): | |||||
def send(self): | def send(self): | ||||
data = self.get_report_content() | data = self.get_report_content() | ||||
message = '<p>{0}</p>'.format(_('{0} generated on {1}').format(self.name, | |||||
frappe.utils.format_datetime(frappe.utils.now_datetime()))) | |||||
attachments = None | |||||
message = '<p>{0}</p>'.format(_('{0} generated on {1}')\ | |||||
.format(frappe.bold(self.name), | |||||
frappe.utils.format_datetime(frappe.utils.now_datetime()))) | |||||
if self.description: | |||||
message += '<hr>' + self.description | |||||
if self.format=='HTML': | if self.format=='HTML': | ||||
message += '<hr>' + data | message += '<hr>' + data | ||||
@@ -78,6 +83,8 @@ class AutoEmailReport(Document): | |||||
'fcontent': data | 'fcontent': data | ||||
}] | }] | ||||
message += '<hr><p style="font-size: 10px;"> Edit Auto Email Report Settings: {0}</p>'.format(frappe.utils.get_link_to_form('Auto Email Report', self.name)) | |||||
frappe.sendmail( | frappe.sendmail( | ||||
recipients = self.email_to.split(), | recipients = self.email_to.split(), | ||||
subject = self.name, | subject = self.name, | ||||
@@ -11,52 +11,49 @@ from frappe.utils import get_url, call_hook_method | |||||
from frappe.integration_broker.integration_controller import IntegrationController | from frappe.integration_broker.integration_controller import IntegrationController | ||||
""" | """ | ||||
1. Get paypal controller, | |||||
# Integrating PayPal | |||||
### 1. Validate Currency Support | |||||
Example: | |||||
from frappe.integration_broker.doctype.integration_service.integration_service import get_integration_controller | from frappe.integration_broker.doctype.integration_service.integration_service import get_integration_controller | ||||
controller = get_integration_controller("PayPal", setup=False) | controller = get_integration_controller("PayPal", setup=False) | ||||
controller().validate_transaction_currency(currency) | |||||
### 2. Redirect for payment | |||||
2. check whether transaction currency supported by payment gateway, | |||||
Controller().validate_transaction_currency(currency) | |||||
Example: | |||||
3. Get checkout url via controller or api, this will redirect you to payment page of particular gateway. | |||||
payment_details = { | |||||
"amount": 600, | |||||
"title": "Payment for bill : 111", | |||||
"description": "payment via cart", | |||||
"reference_doctype": "Payment Request", | |||||
"reference_docname": "PR0001", | |||||
"payer_email": "NuranVerkleij@example.com", | |||||
"payer_name": "Nuran Verkleij", | |||||
"order_id": "111", | |||||
"currency": "USD" | |||||
} | |||||
payment_details = { | |||||
"amount": 600, | |||||
"title": "Payment for bill : 111", | |||||
"description": "payment via cart", | |||||
"reference_doctype": "Payment Request", | |||||
"reference_docname": "PR0001", | |||||
"payer_email": "NuranVerkleij@example.com", | |||||
"payer_name": "Nuran Verkleij", | |||||
"order_id": "111", | |||||
"currency": "USD" | |||||
} | |||||
# redirect the user to this url | |||||
url = controller().get_payment_url(**payment_details) | |||||
Via API | |||||
--------- | |||||
Get payement url via get_checkout_url(**kwargs) | |||||
example: | |||||
from frappe.integration.paypal import get_checkout_url | |||||
get_checkout_url(**payment_details) | |||||
### 3. On Completion of Payment | |||||
Via Controller | |||||
--------------- | |||||
Get payment url via get_payment_url(**kwargs) | |||||
Write a method for `on_payment_authorized` in the reference doctype | |||||
example: | |||||
controller().get_payment_url(**payment_details) | |||||
Example: | |||||
4.To handle a callback of payment, you need to write `on_payment_authorized` | |||||
in reference document. | |||||
def on_payment_authorized(payment_status): | |||||
# your code to handle callback | |||||
example: | |||||
def on_payment_authorized(payment_satus): | |||||
"your code to handle callback" | |||||
##### Note: | |||||
parameter description | |||||
--------------------- | |||||
payment_satus - payment gateway will put payment status on callback. | |||||
payment_status - payment gateway will put payment status on callback. | |||||
For paypal payment status parameter is one from: [Completed, Cancelled, Failed] | For paypal payment status parameter is one from: [Completed, Cancelled, Failed] | ||||
""" | """ | ||||
@@ -83,27 +80,27 @@ class Controller(IntegrationController): | |||||
"reqd": 1 | "reqd": 1 | ||||
} | } | ||||
] | ] | ||||
js = "assets/frappe/js/integrations/paypal.js" | js = "assets/frappe/js/integrations/paypal.js" | ||||
supported_currencies = ["AUD", "BRL", "CAD", "CZK", "DKK", "EUR", "HKD", "HUF", "ILS", "JPY", "MYR", "MXN", | supported_currencies = ["AUD", "BRL", "CAD", "CZK", "DKK", "EUR", "HKD", "HUF", "ILS", "JPY", "MYR", "MXN", | ||||
"TWD", "NZD", "NOK", "PHP", "PLN", "GBP", "RUB", "SGD", "SEK", "CHF", "THB", "TRY", "USD"] | "TWD", "NZD", "NOK", "PHP", "PLN", "GBP", "RUB", "SGD", "SEK", "CHF", "THB", "TRY", "USD"] | ||||
def enable(self, parameters, use_test_account=0): | def enable(self, parameters, use_test_account=0): | ||||
call_hook_method('payment_gateway_enabled', gateway=self.service_name) | call_hook_method('payment_gateway_enabled', gateway=self.service_name) | ||||
self.parameters = parameters | self.parameters = parameters | ||||
self.validate_paypal_credentails(use_test_account) | self.validate_paypal_credentails(use_test_account) | ||||
def get_settings(self): | def get_settings(self): | ||||
return frappe._dict(self.parameters) | return frappe._dict(self.parameters) | ||||
def validate_transaction_currency(self, currency): | def validate_transaction_currency(self, currency): | ||||
if currency not in self.supported_currencies: | if currency not in self.supported_currencies: | ||||
frappe.throw(_("Please select another payment method. {0} does not support transactions in currency '{1}'").format(self.service_name, currency)) | frappe.throw(_("Please select another payment method. {0} does not support transactions in currency '{1}'").format(self.service_name, currency)) | ||||
def get_paypal_params_and_url(self, use_test_account): | def get_paypal_params_and_url(self, use_test_account): | ||||
paypal_settings = frappe._dict(self.get_settings()) | paypal_settings = frappe._dict(self.get_settings()) | ||||
params = { | params = { | ||||
"USER": paypal_settings.api_username, | "USER": paypal_settings.api_username, | ||||
"PWD": paypal_settings.api_password, | "PWD": paypal_settings.api_password, | ||||
@@ -111,9 +108,9 @@ class Controller(IntegrationController): | |||||
"VERSION": "98", | "VERSION": "98", | ||||
"METHOD": "GetPalDetails" | "METHOD": "GetPalDetails" | ||||
} | } | ||||
api_url = "https://api-3t.sandbox.paypal.com/nvp" if use_test_account else "https://api-3t.paypal.com/nvp" | api_url = "https://api-3t.sandbox.paypal.com/nvp" if use_test_account else "https://api-3t.paypal.com/nvp" | ||||
return params, api_url | return params, api_url | ||||
def validate_paypal_credentails(self, use_test_account): | def validate_paypal_credentails(self, use_test_account): | ||||
@@ -134,7 +131,7 @@ class Controller(IntegrationController): | |||||
self.parameters = json.loads(custom_settings_json) | self.parameters = json.loads(custom_settings_json) | ||||
response = self.execute_set_express_checkout(kwargs["amount"], kwargs["currency"], use_test_account) | response = self.execute_set_express_checkout(kwargs["amount"], kwargs["currency"], use_test_account) | ||||
if use_test_account: | if use_test_account: | ||||
return_url = "https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token={0}" | return_url = "https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token={0}" | ||||
else: | else: | ||||
@@ -144,9 +141,9 @@ class Controller(IntegrationController): | |||||
"token": response.get("TOKEN")[0], | "token": response.get("TOKEN")[0], | ||||
"correlation_id": response.get("CORRELATIONID")[0] | "correlation_id": response.get("CORRELATIONID")[0] | ||||
}) | }) | ||||
self.integration_request = self.create_request(kwargs, "Remote", self.service_name, response.get("TOKEN")[0]) | self.integration_request = self.create_request(kwargs, "Remote", self.service_name, response.get("TOKEN")[0]) | ||||
return return_url.format(kwargs["token"]) | return return_url.format(kwargs["token"]) | ||||
def execute_set_express_checkout(self, amount, currency, use_test_account): | def execute_set_express_checkout(self, amount, currency, use_test_account): | ||||
@@ -159,7 +156,7 @@ class Controller(IntegrationController): | |||||
"returnUrl": get_url("/api/method/frappe.integrations.paypal.get_express_checkout_details"), | "returnUrl": get_url("/api/method/frappe.integrations.paypal.get_express_checkout_details"), | ||||
"cancelUrl": get_url("/payment-cancel") | "cancelUrl": get_url("/payment-cancel") | ||||
}) | }) | ||||
params = urlencode(params) | params = urlencode(params) | ||||
response = self.post_request(url, data=params.encode("utf-8")) | response = self.post_request(url, data=params.encode("utf-8")) | ||||
@@ -172,7 +169,7 @@ class Controller(IntegrationController): | |||||
def get_express_checkout_details(token): | def get_express_checkout_details(token): | ||||
use_test_account, custom_settings_json = frappe.db.get_value("Integration Service", "PayPal", ["use_test_account", "custom_settings_json"]) | use_test_account, custom_settings_json = frappe.db.get_value("Integration Service", "PayPal", ["use_test_account", "custom_settings_json"]) | ||||
Controller.parameters = json.loads(custom_settings_json) | Controller.parameters = json.loads(custom_settings_json) | ||||
params, url = Controller().get_paypal_params_and_url(use_test_account) | params, url = Controller().get_paypal_params_and_url(use_test_account) | ||||
params.update({ | params.update({ | ||||
"METHOD": "GetExpressCheckoutDetails", | "METHOD": "GetExpressCheckoutDetails", | ||||
@@ -188,12 +185,12 @@ def get_express_checkout_details(token): | |||||
http_status_code=frappe.ValidationError.http_status_code) | http_status_code=frappe.ValidationError.http_status_code) | ||||
return | return | ||||
update_integration_request_status(token, { | update_integration_request_status(token, { | ||||
"payerid": response.get("PAYERID")[0], | "payerid": response.get("PAYERID")[0], | ||||
"payer_email": response.get("EMAIL")[0] | "payer_email": response.get("EMAIL")[0] | ||||
}, "Authorized") | }, "Authorized") | ||||
frappe.local.response["type"] = "redirect" | frappe.local.response["type"] = "redirect" | ||||
frappe.local.response["location"] = get_url( \ | frappe.local.response["location"] = get_url( \ | ||||
"/api/method/frappe.integrations.paypal.confirm_payment?token={0}".format(token)) | "/api/method/frappe.integrations.paypal.confirm_payment?token={0}".format(token)) | ||||
@@ -202,7 +199,7 @@ def get_express_checkout_details(token): | |||||
def confirm_payment(token): | def confirm_payment(token): | ||||
redirect = True | redirect = True | ||||
status_changed_to, redirect_to = None, None | status_changed_to, redirect_to = None, None | ||||
use_test_account = frappe.db.get_value("Integration Service", "PayPal", "use_test_account") | use_test_account = frappe.db.get_value("Integration Service", "PayPal", "use_test_account") | ||||
integration_request = frappe.get_doc("Integration Request", token) | integration_request = frappe.get_doc("Integration Request", token) | ||||
@@ -10,52 +10,53 @@ from frappe.utils import get_url, call_hook_method | |||||
from frappe.integration_broker.integration_controller import IntegrationController | from frappe.integration_broker.integration_controller import IntegrationController | ||||
""" | """ | ||||
1. Get razorpay controller, | |||||
# Integrating RazorPay | |||||
### Validate Currency | |||||
Example: | |||||
from frappe.integration_broker.doctype.integration_service.integration_service import get_integration_controller | from frappe.integration_broker.doctype.integration_service.integration_service import get_integration_controller | ||||
controller = get_integration_controller("Razorpay", setup=False) | |||||
2. check whether transaction currency supported by payment gateway, | |||||
Controller().validate_transaction_currency(currency) | |||||
controller = get_integration_controller("Razorpay", setup=False) | |||||
controller().validate_transaction_currency(currency) | |||||
3. Get checkout url via controller or api, this will redirect you to payment page of particular gateway. | |||||
### 2. Redirect for payment | |||||
payment_details = { | |||||
"amount": 600, | |||||
"title": "Payment for bill : 111", | |||||
"description": "payment via cart", | |||||
"reference_doctype": "Payment Request", | |||||
"reference_docname": "PR0001", | |||||
"payer_email": "NuranVerkleij@example.com", | |||||
"payer_name": "Nuran Verkleij", | |||||
"order_id": "111", | |||||
"currency": "INR" | |||||
} | |||||
Example: | |||||
Via API | |||||
--------- | |||||
Get payement url via get_checkout_url(**kwargs) | |||||
payment_details = { | |||||
"amount": 600, | |||||
"title": "Payment for bill : 111", | |||||
"description": "payment via cart", | |||||
"reference_doctype": "Payment Request", | |||||
"reference_docname": "PR0001", | |||||
"payer_email": "NuranVerkleij@example.com", | |||||
"payer_name": "Nuran Verkleij", | |||||
"order_id": "111", | |||||
"currency": "INR" | |||||
} | |||||
example: | |||||
from frappe.integration.razorpay import get_checkout_url | from frappe.integration.razorpay import get_checkout_url | ||||
get_checkout_url(**payment_details) | |||||
Via Controller | |||||
--------------- | |||||
Get payment url via get_payment_url(**kwargs) | |||||
# Redirect the user to this url | |||||
url = controller().get_payment_url(**payment_details) | |||||
### 3. On Completion of Payment | |||||
example: | |||||
controller().get_payment_url(**payment_details) | |||||
Write a method for `on_payment_authorized` in the reference doctype | |||||
4.To handle a callback of payment, you need to write `on_payment_authorized` | |||||
in reference document. | |||||
Example: | |||||
example: | |||||
def on_payment_authorized(payment_satus): | |||||
"your code to handle callback" | |||||
def on_payment_authorized(payment_status): | |||||
# this method will be called when payment is complete | |||||
parameter description | |||||
--------------------- | |||||
payment_satus - payment gateway will put payment status on callback. For razorpay payment status is Authorized | |||||
##### Notes: | |||||
payment_status - payment gateway will put payment status on callback. | |||||
For razorpay payment status is Authorized | |||||
""" | """ | ||||
@@ -75,7 +76,7 @@ class Controller(IntegrationController): | |||||
'reqd': 1 | 'reqd': 1 | ||||
} | } | ||||
] | ] | ||||
# do also changes in razorpay.js scheduler job helper | # do also changes in razorpay.js scheduler job helper | ||||
scheduled_jobs = [ | scheduled_jobs = [ | ||||
{ | { | ||||
@@ -84,11 +85,11 @@ class Controller(IntegrationController): | |||||
] | ] | ||||
} | } | ||||
] | ] | ||||
js = "assets/frappe/js/integrations/razorpay.js" | js = "assets/frappe/js/integrations/razorpay.js" | ||||
supported_currencies = ["INR"] | supported_currencies = ["INR"] | ||||
def enable(self, parameters, use_test_account=0): | def enable(self, parameters, use_test_account=0): | ||||
call_hook_method('payment_gateway_enabled', gateway='Razorpay') | call_hook_method('payment_gateway_enabled', gateway='Razorpay') | ||||
self.parameters = parameters | self.parameters = parameters | ||||
@@ -103,23 +104,23 @@ class Controller(IntegrationController): | |||||
auth=(razorpay_settings.api_key, razorpay_settings.api_secret)) | auth=(razorpay_settings.api_key, razorpay_settings.api_secret)) | ||||
except Exception: | except Exception: | ||||
frappe.throw(_("Seems API Key or API Secret is wrong !!!")) | frappe.throw(_("Seems API Key or API Secret is wrong !!!")) | ||||
def validate_transaction_currency(self, currency): | def validate_transaction_currency(self, currency): | ||||
if currency not in self.supported_currencies: | if currency not in self.supported_currencies: | ||||
frappe.throw(_("Please select another payment method. {0} does not support transactions in currency '{1}'").format(self.service_name, currency)) | frappe.throw(_("Please select another payment method. {0} does not support transactions in currency '{1}'").format(self.service_name, currency)) | ||||
def get_payment_url(self, **kwargs): | def get_payment_url(self, **kwargs): | ||||
return get_url("./integrations/razorpay_checkout?{0}".format(urllib.urlencode(kwargs))) | return get_url("./integrations/razorpay_checkout?{0}".format(urllib.urlencode(kwargs))) | ||||
def get_settings(self): | def get_settings(self): | ||||
if hasattr(self, "parameters"): | if hasattr(self, "parameters"): | ||||
return frappe._dict(self.parameters) | return frappe._dict(self.parameters) | ||||
custom_settings_json = frappe.db.get_value("Integration Service", "Razorpay", "custom_settings_json", debug=1) | custom_settings_json = frappe.db.get_value("Integration Service", "Razorpay", "custom_settings_json", debug=1) | ||||
if custom_settings_json: | if custom_settings_json: | ||||
return frappe._dict(json.loads(custom_settings_json)) | return frappe._dict(json.loads(custom_settings_json)) | ||||
def create_request(self, data): | def create_request(self, data): | ||||
self.data = frappe._dict(data) | self.data = frappe._dict(data) | ||||
@@ -133,29 +134,29 @@ class Controller(IntegrationController): | |||||
"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.")), | "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 | "status": 401 | ||||
} | } | ||||
def authorize_payment(self): | def authorize_payment(self): | ||||
""" | """ | ||||
An authorization is performed when user’s payment details are successfully authenticated by the bank. | An authorization is performed when user’s payment details are successfully authenticated by the bank. | ||||
The money is deducted from the customer’s account, but will not be transferred to the merchant’s account | The money is deducted from the customer’s account, but will not be transferred to the merchant’s account | ||||
until it is explicitly captured by merchant. | until it is explicitly captured by merchant. | ||||
""" | """ | ||||
settings = self.get_settings() | settings = self.get_settings() | ||||
if self.integration_request.status != "Authorized": | if self.integration_request.status != "Authorized": | ||||
resp = self.get_request("https://api.razorpay.com/v1/payments/{0}" | resp = self.get_request("https://api.razorpay.com/v1/payments/{0}" | ||||
.format(self.data.razorpay_payment_id), auth=(settings.api_key, | .format(self.data.razorpay_payment_id), auth=(settings.api_key, | ||||
settings.api_secret)) | settings.api_secret)) | ||||
if resp.get("status") == "authorized": | if resp.get("status") == "authorized": | ||||
self.integration_request.db_set('status', 'Authorized', update_modified=False) | self.integration_request.db_set('status', 'Authorized', update_modified=False) | ||||
self.flags.status_changed_to = "Authorized" | self.flags.status_changed_to = "Authorized" | ||||
if self.flags.status_changed_to == "Authorized": | if self.flags.status_changed_to == "Authorized": | ||||
if self.data.reference_doctype and self.data.reference_docname: | if self.data.reference_doctype and self.data.reference_docname: | ||||
redirect_to = frappe.get_doc(self.data.reference_doctype, self.data.reference_docname).run_method("on_payment_authorized", self.flags.status_changed_to) | redirect_to = frappe.get_doc(self.data.reference_doctype, self.data.reference_docname).run_method("on_payment_authorized", self.flags.status_changed_to) | ||||
return { | return { | ||||
"redirect_to": redirect_to or "payment-success", | "redirect_to": redirect_to or "payment-success", | ||||
"status": 200 | "status": 200 | ||||
@@ -180,7 +181,7 @@ def capture_payment(is_sandbox=False, sanbox_response=None): | |||||
data = json.loads(doc.data) | data = json.loads(doc.data) | ||||
resp = controller.post_request("https://api.razorpay.com/v1/payments/{0}/capture".format(data.get("razorpay_payment_id")), | resp = controller.post_request("https://api.razorpay.com/v1/payments/{0}/capture".format(data.get("razorpay_payment_id")), | ||||
auth=(settings["api_key"], settings["api_secret"]), data={"amount": data.get("amount")}) | auth=(settings["api_key"], settings["api_secret"]), data={"amount": data.get("amount")}) | ||||
if resp.get("status") == "captured": | if resp.get("status") == "captured": | ||||
frappe.db.set_value("Integration Request", doc.name, "status", "Completed") | frappe.db.set_value("Integration Request", doc.name, "status", "Completed") | ||||
@@ -573,3 +573,9 @@ a.edit:visited, | |||||
.page-content-wrapper > .row .col-sm-4 { | .page-content-wrapper > .row .col-sm-4 { | ||||
display: none; | display: none; | ||||
} | } | ||||
.screenshot { | |||||
border: 2px solid #d1d8dd; | |||||
box-shadow: 1px 1px 7px rgba(0, 0, 0, 0.15); | |||||
margin: 15px 0px; | |||||
max-width: 100%; | |||||
} |
@@ -58,6 +58,10 @@ frappe.views.ListView = Class.extend({ | |||||
add_field(this.meta.title_field); | add_field(this.meta.title_field); | ||||
} | } | ||||
// endabled / disabled | |||||
if(frappe.meta.has_field(this.doctype, 'enabled')) { add_field('enabled'); }; | |||||
if(frappe.meta.has_field(this.doctype, 'disabled')) { add_field('disabled'); }; | |||||
// add workflow field (as priority) | // add workflow field (as priority) | ||||
this.workflow_state_fieldname = frappe.workflow.get_state_fieldname(this.doctype); | this.workflow_state_fieldname = frappe.workflow.get_state_fieldname(this.doctype); | ||||
if(this.workflow_state_fieldname) { | if(this.workflow_state_fieldname) { | ||||
@@ -113,8 +117,8 @@ frappe.views.ListView = Class.extend({ | |||||
this.columns.push(name_column); | this.columns.push(name_column); | ||||
this.total_colspans = this.columns[0].colspan; | this.total_colspans = this.columns[0].colspan; | ||||
if(frappe.model.is_submittable(this.doctype) | |||||
|| this.settings.get_indicator || this.workflow_state_fieldname) { | |||||
if(frappe.has_indicator(this.doctype)) { | |||||
// indicator | // indicator | ||||
this.columns.push({ | this.columns.push({ | ||||
colspan: this.settings.colwidths && this.settings.colwidths.indicator || 3, | colspan: this.settings.colwidths && this.settings.colwidths.indicator || 3, | ||||
@@ -1,5 +1,17 @@ | |||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | ||||
frappe.has_indicator = function(doctype) { | |||||
// returns true if indicator is present | |||||
if(frappe.model.is_submittable(this.doctype)) { | |||||
return true; | |||||
} else if(this.settings.get_indicator || this.workflow_state_fieldname) { | |||||
return true; | |||||
} else if(frappe.meta.has_field(doctype, 'enabled') || frappe.meta.has_field(doctype, 'disabled')) { | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
frappe.get_indicator = function(doc, doctype) { | frappe.get_indicator = function(doc, doctype) { | ||||
if(doc.__unsaved) { | if(doc.__unsaved) { | ||||
return [__("Not Saved"), "orange"]; | return [__("Not Saved"), "orange"]; | ||||
@@ -38,10 +50,12 @@ frappe.get_indicator = function(doc, doctype) { | |||||
} | } | ||||
} | } | ||||
// draft if document is submittable | |||||
if(is_submittable && doc.docstatus==0 && !settings.has_indicator_for_draft) { | if(is_submittable && doc.docstatus==0 && !settings.has_indicator_for_draft) { | ||||
return [__("Draft"), "red", "docstatus,=,0"]; | return [__("Draft"), "red", "docstatus,=,0"]; | ||||
} | } | ||||
// cancelled | |||||
if(is_submittable && doc.docstatus==2) { | if(is_submittable && doc.docstatus==2) { | ||||
return [__("Cancelled"), "red", "docstatus,=,2"]; | return [__("Cancelled"), "red", "docstatus,=,2"]; | ||||
} | } | ||||
@@ -51,11 +65,31 @@ frappe.get_indicator = function(doc, doctype) { | |||||
if(indicator) return indicator; | if(indicator) return indicator; | ||||
} | } | ||||
// if submittable | |||||
if(is_submittable && doc.docstatus==1) { | if(is_submittable && doc.docstatus==1) { | ||||
return [__("Submitted"), "blue", "docstatus,=,1"]; | return [__("Submitted"), "blue", "docstatus,=,1"]; | ||||
} | } | ||||
// based on status | |||||
if(doc.status) { | if(doc.status) { | ||||
return [__(doc.status), frappe.utils.guess_colour(doc.status)]; | return [__(doc.status), frappe.utils.guess_colour(doc.status)]; | ||||
} | } | ||||
// based on enabled | |||||
if(frappe.meta.has_field(doctype, 'enabled')) { | |||||
if(doc.enabled) { | |||||
return [__('Enabled'), 'blue', 'enabled=1']; | |||||
} else { | |||||
return [__('Disabled'), 'grey', 'enabled=0']; | |||||
} | |||||
} | |||||
// based on disabled | |||||
if(frappe.meta.has_field(doctype, 'disabled')) { | |||||
if(doc.disabled) { | |||||
return [__('Disabled'), 'grey', 'disabled=1']; | |||||
} else { | |||||
return [__('Enabled'), 'blue', 'disabled=0']; | |||||
} | |||||
} | |||||
} | } |
@@ -395,3 +395,11 @@ a.edit, a.edit:hover, a.edit:focus, a.edit:visited, .edit-container .icon { | |||||
display: none; | display: none; | ||||
} | } | ||||
} | } | ||||
.screenshot { | |||||
border: 2px solid @border-color; | |||||
box-shadow: 1px 1px 7px rgba(0,0,0,0.15); | |||||
margin: 15px 0px; | |||||
max-width: 100%; | |||||
} | |||||
@@ -8,6 +8,8 @@ frappe.utils.autodoc | |||||
Inspect elements of a given module and return its objects | Inspect elements of a given module and return its objects | ||||
""" | """ | ||||
from __future__ import unicode_literals | |||||
import inspect, importlib, re, frappe | import inspect, importlib, re, frappe | ||||
from frappe.model.document import get_controller | from frappe.model.document import get_controller | ||||
@@ -42,7 +44,7 @@ def automodule(name): | |||||
return { | return { | ||||
"members": filter(None, attributes), | "members": filter(None, attributes), | ||||
"docs": getattr(obj, "__doc__", "") | |||||
"docs": get_obj_doc(obj) | |||||
} | } | ||||
installed = None | installed = None | ||||
@@ -89,11 +91,11 @@ def get_class_info(class_obj, module_name): | |||||
"type": "class", | "type": "class", | ||||
"bases": [b.__module__ + "." + b.__name__ for b in class_obj.__bases__], | "bases": [b.__module__ + "." + b.__name__ for b in class_obj.__bases__], | ||||
"members": filter(None, members), | "members": filter(None, members), | ||||
"docs": parse(getattr(class_obj, "__doc__", "")) | |||||
"docs": parse(get_obj_doc(class_obj)) | |||||
} | } | ||||
def get_function_info(value): | def get_function_info(value): | ||||
docs = getattr(value, "__doc__") | |||||
docs = get_obj_doc(value) | |||||
return { | return { | ||||
"name": value.__name__, | "name": value.__name__, | ||||
"type": "function", | "type": "function", | ||||
@@ -108,8 +110,6 @@ def parse(docs): | |||||
if not docs: | if not docs: | ||||
return "" | return "" | ||||
docs = strip_leading_tabs(docs) | |||||
if ":param" in docs: | if ":param" in docs: | ||||
out, title_set = [], False | out, title_set = [], False | ||||
for line in docs.splitlines(): | for line in docs.splitlines(): | ||||
@@ -154,3 +154,10 @@ def strip_leading_tabs(docs): | |||||
def automodel(doctype): | def automodel(doctype): | ||||
"""return doctype template""" | """return doctype template""" | ||||
pass | pass | ||||
def get_obj_doc(obj): | |||||
'''Return `__doc__` of the given object as unicode''' | |||||
doc = getattr(obj, "__doc__", "") or '' | |||||
if not isinstance(doc, unicode): | |||||
doc = unicode(doc, 'utf-8') | |||||
return doc |
@@ -205,7 +205,7 @@ class setup_docs(object): | |||||
context = {"name": self.app + "." + module_name} | context = {"name": self.app + "." + module_name} | ||||
context.update(self.app_context) | context.update(self.app_context) | ||||
f.write(frappe.render_template("templates/autodoc/pymodule.html", | f.write(frappe.render_template("templates/autodoc/pymodule.html", | ||||
context)) | |||||
context).encode('utf-8')) | |||||
self.update_index_txt(module_folder) | self.update_index_txt(module_folder) | ||||
@@ -272,12 +272,18 @@ class setup_docs(object): | |||||
frappe.local.flags.home_page = "index" | frappe.local.flags.home_page = "index" | ||||
from frappe.website.router import get_pages, make_toc | from frappe.website.router import get_pages, make_toc | ||||
pages = get_pages() | |||||
pages = get_pages(self.app) | |||||
# clear the user, current folder in target | # clear the user, current folder in target | ||||
shutil.rmtree(os.path.join(self.target, "user"), ignore_errors=True) | shutil.rmtree(os.path.join(self.target, "user"), ignore_errors=True) | ||||
shutil.rmtree(os.path.join(self.target, "current"), ignore_errors=True) | shutil.rmtree(os.path.join(self.target, "current"), ignore_errors=True) | ||||
def raw_replacer(matchobj): | |||||
if '{% raw %}' in matchobj.group(0): | |||||
return matchobj.group(0) | |||||
else: | |||||
return '{% raw %}' + matchobj.group(0) + '{% endraw %}' | |||||
cnt = 0 | cnt = 0 | ||||
for path, context in pages.iteritems(): | for path, context in pages.iteritems(): | ||||
print "Writing {0}".format(path) | print "Writing {0}".format(path) | ||||
@@ -336,7 +342,7 @@ class setup_docs(object): | |||||
context.base_template_path = "templates/autodoc/base_template.html" | context.base_template_path = "templates/autodoc/base_template.html" | ||||
if '<code>' in context.source: | if '<code>' in context.source: | ||||
context.source = re.sub('\<code\>(.*)\</code\>', '<code>{% raw %}\g<1>{% endraw %}</code>', context.source) | |||||
context.source = re.sub('\<code\>(.*)\</code\>', raw_replacer, context.source) | |||||
html = frappe.render_template(context.source, context) | html = frappe.render_template(context.source, context) | ||||
@@ -110,13 +110,17 @@ def get_page_info_from_doctypes(path=None): | |||||
return routes | return routes | ||||
def get_pages(): | |||||
def get_pages(app=None): | |||||
'''Get all pages. Called for docs / sitemap''' | '''Get all pages. Called for docs / sitemap''' | ||||
pages = {} | pages = {} | ||||
frappe.local.flags.in_get_all_pages = True | frappe.local.flags.in_get_all_pages = True | ||||
folders = frappe.local.flags.web_pages_folders or ('www', 'templates/pages') | folders = frappe.local.flags.web_pages_folders or ('www', 'templates/pages') | ||||
apps = frappe.local.flags.web_pages_apps or frappe.get_installed_apps() | |||||
if app: | |||||
apps = [app] | |||||
else: | |||||
apps = frappe.local.flags.web_pages_apps or frappe.get_installed_apps() | |||||
for app in apps: | for app in apps: | ||||
app_path = frappe.get_app_path(app) | app_path = frappe.get_app_path(app) | ||||