From 9b9365ac027cf6b429dd80de0b2f48aa0a79c314 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 13 Nov 2019 15:42:33 +0530 Subject: [PATCH 01/16] feat: added orders API --- .../razorpay_settings/razorpay_settings.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py index 26e0de3..ca825a4 100644 --- a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py +++ b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py @@ -180,6 +180,23 @@ class RazorpaySettings(Document): integration_request = create_request_log(kwargs, "Host", "Razorpay") return get_url("./integrations/razorpay_checkout?token={0}".format(integration_request.name)) + def create_order(self, **kwargs): + # Creating Orders https://razorpay.com/docs/api/orders/ + # + # kwargs = { + # "amount": 3000, + # "currency": "INR", + # "receipt": "rcptid_11234", + # "payment_capture": 1 + # } + integration_request = create_request_log(kwargs, "Host", "Razorpay") + if self.api_key and self.api_secret: + try: + return make_post_request("https://api.razorpay.com/v1/orders", auth=(self.api_key, self.get_password(fieldname="api_secret", raise_exception=False)), data=kwargs) + except Exception: + frappe.log(frappe.get_traceback()) + frappe.throw(_("Could not create razorpay order")) + def create_request(self, data): self.data = frappe._dict(data) From 4b24937e399918dc69f1d830c92d592b0e220d85 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 13 Nov 2019 17:37:02 +0530 Subject: [PATCH 02/16] fix: fixed post request --- .../doctype/razorpay_settings/razorpay_settings.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py index ca825a4..16fe763 100644 --- a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py +++ b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py @@ -190,9 +190,15 @@ class RazorpaySettings(Document): # "payment_capture": 1 # } integration_request = create_request_log(kwargs, "Host", "Razorpay") + payment_options = { + "amount": kwargs.get('amount'), + "currency": kwargs.get('currency', 'INR'), + "receipt": kwargs.get('receipt'), + "payment_capture": kwargs.get('payment_capture') + } if self.api_key and self.api_secret: try: - return make_post_request("https://api.razorpay.com/v1/orders", auth=(self.api_key, self.get_password(fieldname="api_secret", raise_exception=False)), data=kwargs) + return make_post_request("https://api.razorpay.com/v1/orders", auth=(self.api_key, self.get_password(fieldname="api_secret", raise_exception=False)), data=payment_options) except Exception: frappe.log(frappe.get_traceback()) frappe.throw(_("Could not create razorpay order")) From 297292164b43ef002ec5feee80bfa27b4c548a49 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 13 Nov 2019 19:31:11 +0530 Subject: [PATCH 03/16] feat: updated client API --- .../razorpay_settings/razorpay_settings.py | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py index 16fe763..2a0f14f 100644 --- a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py +++ b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py @@ -198,7 +198,9 @@ class RazorpaySettings(Document): } if self.api_key and self.api_secret: try: - return make_post_request("https://api.razorpay.com/v1/orders", auth=(self.api_key, self.get_password(fieldname="api_secret", raise_exception=False)), data=payment_options) + order = make_post_request("https://api.razorpay.com/v1/orders", auth=(self.api_key, self.get_password(fieldname="api_secret", raise_exception=False)), data=payment_options) + order['integration_request'] = integration_request + return order except Exception: frappe.log(frappe.get_traceback()) frappe.throw(_("Could not create razorpay order")) @@ -353,6 +355,22 @@ def capture_payment(is_sandbox=False, sanbox_response=None): doc.error = frappe.get_traceback() frappe.log_error(doc.error, '{0} Failed'.format(doc.name)) +@frappe.whitelist(allow_guest=True) +def get_order(doctype, docname): + return frappe.get_doc(doctype, docname).run_method("get_razorpay_order") + +@frappe.whitelist(allow_guest=True) +def order_payment_success(self, integration_request, params): + print("SUCCESSSSSSS ------------------") + integration = frappe.get_doc("Integration Request", integration_request) + integration.update_status(update_dict) + +@frappe.whitelist(allow_guest=True) +def order_payment_failure(self, integration_request, params): + print("FAILLLLLL ------------------") + integration = frappe.get_doc("Integration Request", integration_request) + integration.update_status(update_dict) + def convert_rupee_to_paisa(**kwargs): for addon in kwargs.get('addons'): addon['item']['amount'] *= 100 From f15e4d7b9416ef361f689c2ea35a80748d29dc19 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 13 Nov 2019 19:31:23 +0530 Subject: [PATCH 04/16] feat: created client API for razorpay --- payments/public/js/razorpay.js | 170 ++++++++++++++++++++++++++++----- 1 file changed, 147 insertions(+), 23 deletions(-) diff --git a/payments/public/js/razorpay.js b/payments/public/js/razorpay.js index 0885826..57dbdd8 100644 --- a/payments/public/js/razorpay.js +++ b/payments/public/js/razorpay.js @@ -1,26 +1,150 @@ -frappe.provide("frappe.integration_service") +// frappe.provide("frappe.integration_service") -frappe.integration_service.razorpay = { - load: function(frm) { - new frappe.integration_service.Razorpay(frm) - }, - scheduler_job_helper: function(){ - return { - "Every few minutes": "Check and capture new payments" +// frappe.integration_service.razorpay = { +// load: function(frm) { +// new frappe.integration_service.Razorpay(frm) +// }, +// scheduler_job_helper: function(){ +// return { +// "Every few minutes": "Check and capture new payments" +// } +// } +// } + +// frappe.integration_service.Razorpay = Class.extend({ +// init:function(frm){ +// this.frm = frm; +// this.frm.toggle_display("use_test_account", false); +// this.show_logs(); +// }, +// show_logs: function(){ +// this.frm.add_custom_button(__("Show Log"), function(frm){ +// frappe.route_options = {"integration_request_service": "Razorpay"}; +// frappe.set_route("List", "Integration Request"); +// }); +// } +// }) +// + +// function make_payment(order, ticket) { +// var options = { +// "key": "rzp_test_lExD7NVL1JoKJJ", // Enter the Key ID generated from the Dashboard +// "amount": order.amount_due, // Amount is in currency subunits. Default currency is INR. Hence, 50000 refers to 50000 paise or INR 500. +// "currency": order.currency, +// "name": "IndiaOS", +// "description": "Conference Ticket", +// "image": "http://indiaos.in/assets/indiaos/img/icons/favicon.ico", +// "order_id": order.id, +// "prefill": { +// "name": ticket.full_name, +// "email": ticket.email, +// "contact": '7506056962' +// }, +// "theme": { +// "color": "#F6E05E" +// } +// }; + +// options.handler = function (response){ +// if (response.error) { +// openModal(); +// fail('payment-failed'); +// } + +// if (response.razorpay_payment_id) { +// openModal(); +// success(); + +// } +// } + +// var razorpay = new Razorpay(options); +// razorpay.once('ready', function(response) { +// modalClose(); +// }) + +// razorpay.open(); + +frappe.provide("frappe.checkout") + +frappe.require('https://checkout.razorpay.com/v1/checkout.js').then(() => { + frappe.checkout.razorpay = class RazorpayCheckout { + constructor(opts) { + Object.assign(this, opts); + } + + init() { + frappe.run_serially([ + () => this.makeOrder(), + () => this.prepareOptions(), + () => this.setupHandler(), + () => this.show() + ]) + } + + show(callback=null) { + let razorpay = new Razorpay(this.options); + razorpay.once('ready', function(response) { + this.onOpen && this.onOpen(response); + }) + razorpay.open(); + } + + makeOrder() { + return new Promise(resolve => { + frappe.call( "frappe.integrations.doctype.razorpay_settings.razorpay_settings.get_order", { + doctype: this.doctype, + docname: this.docname + }).then(res => { + this.order = res.message + resolve(true); + }) + }); + } + + orderSuccess(response) { + frappe.call( "frappe.integrations.doctype.razorpay_settings.razorpay_settings.order_payment_success", { + integration_request: this.order.integration_request, + params: { + razorpay_payment_id: response.razorpay_payment_id, + razorpay_order_id: response.razorpay_order_id, + razorpay_signature: response.razorpay_signature + } + }) + } + + orderFail() { + frappe.call( "frappe.integrations.doctype.razorpay_settings.razorpay_settings.order_payment_failure", { + integration_request: this.order.integration_request, + params: response + }) + } + + prepareOptions() { + this.options = { + "key": this.key, + "amount": this.order.amount_due, + "currency": this.order.currency, + "name": this.name, + "description": this.description, + "image": this.image, + "order_id": this.order.id, + "prefill": this.prefill, + "theme": this.theme + }; + } + + setupHandler() { + this.options.handler = (response) => { + if (response.error) { + this.orderFail(response); + this.onFail && this.onFail(response); + } + if (response.razorpay_payment_id) { + this.orderSuccess(response); + this.onSuccess && this.onSuccess(response); + } + } } } -} - -frappe.integration_service.Razorpay = Class.extend({ - init:function(frm){ - this.frm = frm; - this.frm.toggle_display("use_test_account", false); - this.show_logs(); - }, - show_logs: function(){ - this.frm.add_custom_button(__("Show Log"), function(frm){ - frappe.route_options = {"integration_request_service": "Razorpay"}; - frappe.set_route("List", "Integration Request"); - }); - } -}) +}) \ No newline at end of file From 3f34ec1088551d29ab78f3d1aed4ceb9404493d2 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 13 Nov 2019 19:35:55 +0530 Subject: [PATCH 05/16] style: removed commented code --- payments/public/js/razorpay.js | 67 ---------------------------------- 1 file changed, 67 deletions(-) diff --git a/payments/public/js/razorpay.js b/payments/public/js/razorpay.js index 57dbdd8..3bc841d 100644 --- a/payments/public/js/razorpay.js +++ b/payments/public/js/razorpay.js @@ -1,70 +1,3 @@ -// frappe.provide("frappe.integration_service") - -// frappe.integration_service.razorpay = { -// load: function(frm) { -// new frappe.integration_service.Razorpay(frm) -// }, -// scheduler_job_helper: function(){ -// return { -// "Every few minutes": "Check and capture new payments" -// } -// } -// } - -// frappe.integration_service.Razorpay = Class.extend({ -// init:function(frm){ -// this.frm = frm; -// this.frm.toggle_display("use_test_account", false); -// this.show_logs(); -// }, -// show_logs: function(){ -// this.frm.add_custom_button(__("Show Log"), function(frm){ -// frappe.route_options = {"integration_request_service": "Razorpay"}; -// frappe.set_route("List", "Integration Request"); -// }); -// } -// }) -// - -// function make_payment(order, ticket) { -// var options = { -// "key": "rzp_test_lExD7NVL1JoKJJ", // Enter the Key ID generated from the Dashboard -// "amount": order.amount_due, // Amount is in currency subunits. Default currency is INR. Hence, 50000 refers to 50000 paise or INR 500. -// "currency": order.currency, -// "name": "IndiaOS", -// "description": "Conference Ticket", -// "image": "http://indiaos.in/assets/indiaos/img/icons/favicon.ico", -// "order_id": order.id, -// "prefill": { -// "name": ticket.full_name, -// "email": ticket.email, -// "contact": '7506056962' -// }, -// "theme": { -// "color": "#F6E05E" -// } -// }; - -// options.handler = function (response){ -// if (response.error) { -// openModal(); -// fail('payment-failed'); -// } - -// if (response.razorpay_payment_id) { -// openModal(); -// success(); - -// } -// } - -// var razorpay = new Razorpay(options); -// razorpay.once('ready', function(response) { -// modalClose(); -// }) - -// razorpay.open(); - frappe.provide("frappe.checkout") frappe.require('https://checkout.razorpay.com/v1/checkout.js').then(() => { From 14cd6fb216a44d7fac146d4eda5d220d1766b723 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 14 Nov 2019 11:44:04 +0530 Subject: [PATCH 06/16] style: added comments and cleaned up API --- .../razorpay_settings/razorpay_settings.py | 80 +++++++++++++------ payments/public/js/razorpay.js | 55 +++++++++++++ 2 files changed, 111 insertions(+), 24 deletions(-) diff --git a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py index 2a0f14f..03153f4 100644 --- a/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py +++ b/payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py @@ -182,14 +182,14 @@ class RazorpaySettings(Document): def create_order(self, **kwargs): # Creating Orders https://razorpay.com/docs/api/orders/ - # - # kwargs = { - # "amount": 3000, - # "currency": "INR", - # "receipt": "rcptid_11234", - # "payment_capture": 1 - # } + + # convert ruppes to paisa + kwargs['amount'] *= 100 + + # Create integration log integration_request = create_request_log(kwargs, "Host", "Razorpay") + + # Setup payment otptions payment_options = { "amount": kwargs.get('amount'), "currency": kwargs.get('currency', 'INR'), @@ -199,8 +199,8 @@ class RazorpaySettings(Document): if self.api_key and self.api_secret: try: order = make_post_request("https://api.razorpay.com/v1/orders", auth=(self.api_key, self.get_password(fieldname="api_secret", raise_exception=False)), data=payment_options) - order['integration_request'] = integration_request - return order + order['integration_request'] = integration_request.name + return order # Order returned to be consumed by razorpay.js except Exception: frappe.log(frappe.get_traceback()) frappe.throw(_("Could not create razorpay order")) @@ -238,6 +238,10 @@ class RazorpaySettings(Document): self.integration_request.update_status(data, 'Authorized') self.flags.status_changed_to = "Authorized" + if resp.get("status") == "captured": + self.integration_request.update_status(data, 'Completed') + self.flags.status_changed_to = "Completed" + elif data.get('subscription_id'): if resp.get("status") == "refunded": # if subscription start date is in future then @@ -247,14 +251,6 @@ class RazorpaySettings(Document): self.integration_request.update_status(data, 'Completed') self.flags.status_changed_to = "Verified" - if resp.get("status") == "captured": - # if subscription starts immediately then - # razorpay charge the actual amount - # thus changing status to Completed - - self.integration_request.update_status(data, 'Completed') - self.flags.status_changed_to = "Completed" - else: frappe.log_error(str(resp), 'Razorpay Payment not authorized') @@ -357,19 +353,55 @@ def capture_payment(is_sandbox=False, sanbox_response=None): @frappe.whitelist(allow_guest=True) def get_order(doctype, docname): - return frappe.get_doc(doctype, docname).run_method("get_razorpay_order") + # Order returned to be consumed by razorpay.js + doc = frappe.get_doc(doctype, docname) + try: + # Do not use run_method here as it fails silently + return doc.get_razorpay_order() + except AttributeError: + error_log = frappe.log_error(frappe.get_traceback(), _("Controller method get_razorpay_order missing")) + frappe.throw(_("Could not create Razorpay order. Please contact Administrator")) @frappe.whitelist(allow_guest=True) -def order_payment_success(self, integration_request, params): - print("SUCCESSSSSSS ------------------") +def order_payment_success(integration_request, params): + """Called by razorpay.js on order payment success, the params + contains razorpay_payment_id, razorpay_order_id, razorpay_signature + that is updated in the data field of integration request + + Args: + integration_request (string): Name for integration request doc + params (string): Params to be updated for integration request. + """ + params = json.loads(params) integration = frappe.get_doc("Integration Request", integration_request) - integration.update_status(update_dict) + + # Update integration request + integration.update_status(params, integration.status) + integration.reload() + + data = json.loads(integration.data) + controller = frappe.get_doc("Razorpay Settings") + + # Update payment and integration data for payment controller object + controller.integration_request = integration + controller.data = frappe._dict(data) + + # Authorize payment + controller.authorize_payment() @frappe.whitelist(allow_guest=True) -def order_payment_failure(self, integration_request, params): - print("FAILLLLLL ------------------") +def order_payment_failure(integration_request, params): + """Called by razorpay.js on failure + + Args: + integration_request (TYPE): Description + params (TYPE): error data to be updated + """ + frappe.log_error(params, 'Razorpay Payment Failure') + + params = json.loads(params) integration = frappe.get_doc("Integration Request", integration_request) - integration.update_status(update_dict) + integration.update_status(params, integration.status) def convert_rupee_to_paisa(**kwargs): for addon in kwargs.get('addons'): diff --git a/payments/public/js/razorpay.js b/payments/public/js/razorpay.js index 3bc841d..c21fd28 100644 --- a/payments/public/js/razorpay.js +++ b/payments/public/js/razorpay.js @@ -1,3 +1,58 @@ +/* HOW-TO + +Razorpay Payment + +1. Include checkout script in your code + + +2. Create the Order controller in your backend + def get_razorpay_order(self): + controller = get_payment_gateway_controller("Razorpay") + + payment_details = { + "amount": 300, + ... + "reference_doctype": "Conference Participant", + "reference_docname": self.name, + ... + "receipt": self.name + } + + return controller.create_order(**payment_details) + +3. Inititate the payment in client using checkout API + function make_payment(ticket) { + var options = { + "key": "", + "name": "", + "description": "", + "image": "", + "prefill": { + "name": "", + "email": "", + "contact": "" + }, + "theme": { + "color": "" + }, + "doctype": "", + "docname": " { + -2. Create the Order controller in your backend +2. Create the Order controller in your backend def get_razorpay_order(self): controller = get_payment_gateway_controller("Razorpay") - + payment_details = { "amount": 300, ... @@ -39,13 +39,13 @@ Razorpay Payment }; razorpay = new frappe.checkout.razorpay(options) - razorpay.onOpen = () => { + razorpay.on_open = () => {