Просмотр исходного кода

Merge pull request #8808 from scmmishra/razorpay-orders

feat: New order API for client side checkout handling
pull/2/head
Shivam Mishra 5 лет назад
committed by GitHub
Родитель
Сommit
3ca238b51f
2 измененных файлов: 233 добавлений и 31 удалений
  1. +89
    -9
      payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py
  2. +144
    -22
      payments/public/js/razorpay.js

+ 89
- 9
payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py Просмотреть файл

@@ -180,6 +180,33 @@ 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/

# convert rupees to paisa
kwargs['amount'] *= 100

# Create integration log
integration_request = create_request_log(kwargs, "Host", "Razorpay")

# Setup payment options
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:
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.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"))

def create_request(self, data):
self.data = frappe._dict(data)

@@ -213,6 +240,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
@@ -222,14 +253,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')

@@ -329,6 +352,63 @@ 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_api_key():
controller = frappe.get_doc("Razorpay Settings")
return controller.api_key

@frappe.whitelist(allow_guest=True)
def get_order(doctype, docname):
# 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:
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(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)

# 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(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(params, integration.status)

def convert_rupee_to_paisa(**kwargs):
for addon in kwargs.get('addons'):
addon['item']['amount'] *= 100
@@ -382,4 +462,4 @@ def validate_payment_callback(data):
_throw()

def handle_subscription_notification(doctype, docname):
call_hook_method("handle_subscription_notification", doctype=doctype, docname=docname)
call_hook_method("handle_subscription_notification", doctype=doctype, docname=docname)

+ 144
- 22
payments/public/js/razorpay.js Просмотреть файл

@@ -1,26 +1,148 @@
frappe.provide("frappe.integration_service")
/* HOW-TO

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"
Razorpay Payment

1. Include checkout script in your code
<script type="text/javascript" src="/assets/js/checkout.min.js"></script>

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 = {
"name": "<CHECKOUT MODAL TITLE>",
"description": "<CHECKOUT MODAL DESCRIPTION>",
"image": "<CHECKOUT MODAL LOGO>",
"prefill": {
"name": "<CUSTOMER NAME>",
"email": "<CUSTOMER EMAIL>",
"contact": "<CUSTOMER PHONE>"
},
"theme": {
"color": "<MODAL COLOR>"
},
"doctype": "<REFERENCE DOCTYPE>",
"docname": "<REFERENCE DOCNAME"
};

razorpay = new frappe.checkout.razorpay(options)
razorpay.on_open = () => {
<SCRIPT TO RUN WHEN MODAL OPENS>
}
razorpay.on_success = () => {
<SCRIPT TO RUN ON PAYMENT SUCCESS>
}
razorpay.on_fail = () => {
<SCRIPT TO RUN ON PAYMENT FAILURE>
}
razorpay.init() // Creates the order and opens the modal
}
}

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");
});
*/

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.get_key(),
() => this.make_order(),
() => this.prepare_options(),
() => this.setup_handler(),
() => this.show()
]);
}

show() {
this.razorpay = new Razorpay(this.options);
this.razorpay.once('ready', (response) => {
this.on_open && this.on_open(response);
})
this.razorpay.open();
}

get_key() {
return new Promise(resolve => {
frappe.call("frappe.integrations.doctype.razorpay_settings.razorpay_settings.get_api_key").then(res => {
this.key = res.message;
resolve(true);
})
});
}

make_order() {
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);
})
});
}

order_success(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
}
})
}

order_fail(response) {
frappe.call( "frappe.integrations.doctype.razorpay_settings.razorpay_settings.order_payment_failure", {
integration_request: this.order.integration_request,
params: response
})
}

prepare_options() {
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,
"modal": this.modal
};
}

setup_handler() {
this.options.handler = (response) => {
if (response.error) {
this.order_fail(response);
this.on_fail && this.on_fail(response);
}
else if (response.razorpay_payment_id) {
this.order_success(response);
this.on_success && this.on_success(response);
}
}
}
}
})
});

Загрузка…
Отмена
Сохранить