Last active
January 21, 2018 19:59
-
-
Save gauravsoti1/c2dc7e709401e89be5cf5701c917dd97 to your computer and use it in GitHub Desktop.
all about creating and cancelling shopify orders on delhivery
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#Making a table shopify_orders to store some data which helps us keep the shopify and delhivery data in sync | |
class CreateShopifyOrders < ActiveRecord::Migration | |
def change | |
create_table :shopify_orders do |t| | |
t.string :order_id | |
t.string :order_name | |
t.string :sub_order_id | |
t.string :fulfillment_id | |
t.string :godam_status | |
t.string :tracking_number | |
t.timestamps | |
end | |
add_index :shopify_orders, :order_id | |
add_index :shopify_orders, :sub_order_id | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This gist shows the complete flow of creating a shopify order on delhivery | |
This code is part of a rails project | |
shopify_controller.rb contains: | |
Shopify's webhook for order create and order cancellation | |
Delhivery Webhook for getting the tracking number which we use to update the tracking number in shopify order | |
sample_shopify_order_response.rb contains: | |
this is just a dummy file to show you what kind of response shopify gives | |
delhivery_service.rb contains: | |
All the helper methods to access the delhivery api easily | |
also contains function to convert shopify response into response that delhivery's order create api understands | |
shopify_service.rb contains: | |
All the helper methods to access the shopify api easily | |
213412242421_create_shopify_orders.rb | |
Database migration to save shopify orders and delhivery data to keep both in sync | |
routes.rb contains: | |
routes for shopify | |
# Sample curl request for creating and cancelling orders at delhivery is also provided | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#sample delhivery order create curl request, copied from postman | |
curl -X POST \ | |
https://sandbox-api-godam.delhivery.com/order/create/ \ | |
-H 'authorization: Token <your token here>' \ | |
-H 'cache-control: no-cache' \ | |
-H 'content-type: application/json' \ | |
-H 'postman-token: <your postman token>' \ | |
-H 'version: V2' \ | |
-d '[ | |
{ | |
"order_number": 215650567215, | |
"order_date": "2018-01-17T17:47:36+05:30", | |
"consignee": { | |
"name": "First Second", | |
"state": "DL", | |
"city": "New Delhi", | |
"address2": "test", | |
"address1": "sdlkflsakdl", | |
"country": "India", | |
"pincode": "110016", | |
"phone1": 9999999999, | |
"phone2": 0, | |
"email": null, | |
"gstin": null | |
}, | |
"sub_orders": [ | |
{ | |
"sub_order_number": 374233928735, | |
"access_key": "204c189ac25c82442b500ad59ad55ae4", | |
"fulfillment_center": "LLDCDE", | |
"gstin": null, | |
"expected_ship_date": null, | |
"dispatch_after_date": null, | |
"product_details": { | |
"description": "Dummy Product", | |
"hsn_code": 2040134963247, | |
"name": "Dummy Product", | |
"number": "273636098095", | |
"quantity": "1", | |
"sku": "Dummy Product" | |
}, | |
"shipment_details": { | |
"courier": "Delhivery", | |
"packing_slip_link": "", | |
"payment_mode": "COD", | |
"shipment_number": "100573085743", | |
"shipping_level": "", | |
"sorting_code": "26360", | |
"waybill_number": "" | |
}, | |
"invoice_details": { | |
"advance_payment": 0, | |
"cgst_amount": 76.27, | |
"cgst_percentage": 9.0, | |
"cod_amount": "1050.00", | |
"cst_percentage": 0, | |
"discount": 0, | |
"gross_value": "1000.00", | |
"igst_amount": 0, | |
"igst_percentage": 0, | |
"imei": "null", | |
"invoice_date": null, | |
"invoice_link": "", | |
"invoice_number": "", | |
"mrp": "1000.00", | |
"net_amount": "1000.00", | |
"reference_number": null, | |
"round_off": 0, | |
"sgst_amount": 76.27, | |
"sgst_percentage": 9.0, | |
"shipping_price": 50, | |
"tax_percentage": 0, | |
"total_cst": 0, | |
"total_price": "1050.00", | |
"total_taxes": "152.54", | |
"total_vat": 0, | |
"unit_price": 0, | |
"unit_taxes": 0, | |
"vat_percentage": 0 | |
} | |
} | |
] | |
} | |
]' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class DelhiveryService | |
# body is the sample input data that delhivery understands for order creation | |
def self.create_order(body) | |
puts "body for creating delhivery order = " | |
puts body.to_json | |
api_end_point = DELHIVERY_API_ENDPOINT + "/order/create/" | |
url = URI(api_end_point) | |
http = Net::HTTP.new(url.host, url.port) | |
http.use_ssl = true | |
http.verify_mode = OpenSSL::SSL::VERIFY_NONE | |
request = Net::HTTP::Post.new(url) | |
request["content-type"] = 'application/json' | |
request["authorization"] = "Token " + DELHIVERY_TOKEN | |
request["version"] = 'V2' | |
request["cache-control"] = 'no-cache' | |
request.body = "#{body.to_json}" | |
response = http.request(request) | |
response_message = response.read_body | |
response_message = JSON.parse response_message | |
# puts "request response:::::::::::::::::" | |
puts response_message.inspect | |
return response_message["acknowledged"] == true | |
end | |
def self.cancel_order(order_id, sub_order_id) | |
url = URI(DELHIVERY_ENDPOINT + "/oms/api/update/CAN/?client_store=#{DELHIVERY_CLIENT_STORE}&fulfillment_center=#{DELHIVERY_FULFILLMENT_CENTRE}&version=2014.09&order_id=#{order_id}&sub_order_id=#{sub_order_id}") | |
http = Net::HTTP.new(url.host, url.port) | |
http.use_ssl = true | |
http.verify_mode = OpenSSL::SSL::VERIFY_NONE | |
request = Net::HTTP::Put.new(url) | |
request["content_type"] = 'application/x-www-form-urlencoded' | |
request["accept"] = 'application/json' | |
request["authorization"] = "Token " + DELHIVERY_TOKEN | |
response = http.request(request) | |
# Fail message: {"status"=>"Fail", "message"=>"No SubOrder against Order to cancel"} | |
return response | |
end | |
def self.create_delhivery_data_from_shopify_data(shr) | |
delhivery_data = [] | |
delhivery_data << get_order_details(shr) | |
return delhivery_data | |
end | |
def self.create_last_order_from_shopify | |
orders = ShopifyService.get_orders(Date.yesterday, Date.tomorrow) | |
order = orders["orders"].first | |
data = DelhiveryService.create_delhivery_data_from_shopify_data(order) | |
x = DelhiveryService.create_order data | |
puts x.to_json #debugging response | |
return x | |
end | |
private | |
def self.get_order_details(shr) | |
puts "getting order details" | |
customer = shr["customer"] | |
shipping_address = shr["shipping_address"] | |
products = shr["line_items"] | |
shipping_array = shr["shipping_lines"] | |
phone_number = shipping_address["phone"].gsub(/\s/,"").to_i rescue 9999999999 | |
order_object = { | |
"order_number": shr["name"], | |
"order_date": shr["created_at"] ,#"2017-07-20T11:14:13.695Z" | |
"consignee": { | |
"name": shipping_address["first_name"] + " " + shipping_address["last_name"], | |
"state": shipping_address["province_code"], | |
"city": shipping_address["city"], | |
"address2": "", | |
"address1": shipping_address["address1"], | |
"country": shipping_address["country"], | |
"pincode": shipping_address["zip"], | |
"phone1": phone_number , | |
"phone2": 0, | |
"email": customer["email"], | |
"gstin": "" | |
} | |
} | |
sub_orders = [] | |
shr["line_items"].each_with_index do |sub_order,i| | |
sub_orders << get_sub_order_details((shr["shipping_lines"])[0], sub_order, shipping_address, shr) | |
end | |
order_object["sub_orders"] = sub_orders | |
return order_object | |
end | |
#TODO: store access_key in constants | |
def self.get_sub_order_details(shipping_line, sub_order, shipping_address, shr) | |
puts "getting sub_order details" | |
tax_array = sub_order["tax_lines"] | |
tax_helper_object = ShopifyHelper::Tax.new(tax_array) | |
shipping_code = shipping_line["code"] rescue "" | |
{ | |
"sub_order_number": sub_order["id"], | |
"access_key": DELHIVERY_ACCESS_KEY, | |
"fulfillment_center": DELHIVERY_FULFILLMENT_CENTRE, | |
"gstin": "07GGJCPG323A1ZR", | |
"expected_ship_date": nil, | |
"dispatch_after_date": nil, | |
"product_details": { | |
"description": sub_order["name"], | |
"hsn_code": "2314", | |
"name": sub_order["name"], | |
"number": sub_order["sku"].to_s, | |
"quantity": sub_order["quantity"].to_s, | |
"sku": sub_order["sku"] | |
}, | |
"shipment_details": { | |
"courier": "Delhivery", | |
"packing_slip_link": "", | |
"payment_mode": shr["gateway"] == "cash_on_delivery" ? "COD" : "PREPAID", | |
"shipment_number": shr["name"], | |
"shipping_level": "", | |
"sorting_code": shipping_code, | |
"waybill_number": "" | |
}, | |
"invoice_details": { | |
"advance_payment": shr["gateway"]== "cash_on_delivery" ? 0 : shr["total_price"], | |
"cgst_amount": tax_helper_object.get_amount("CGST"), | |
"cgst_percentage": tax_helper_object.get_percentage("CGST") , | |
"cod_amount": shr["gateway"]== "cash_on_delivery" ? shr["total_price"] : 0, | |
"cst_percentage": 0, | |
"discount": shr["total_discounts"].to_f, | |
"gross_value": (sub_order["price"].to_f * sub_order["quantity"].to_i).to_s, | |
"igst_amount": tax_helper_object.get_amount("IGST"), | |
"igst_percentage": tax_helper_object.get_percentage("IGST"), | |
"imei": "null", | |
"invoice_date": shr["created_at"], | |
"invoice_link": "", | |
"invoice_number": shr["name"], | |
"mrp": sub_order["price"].to_s, | |
"net_amount": ((sub_order["price"].to_f * sub_order["quantity"].to_i) + (shipping_line)["price"].to_i ).to_s, | |
"reference_number": nil, | |
"round_off": 0, | |
"sgst_amount": tax_helper_object.get_amount("SGST"), | |
"sgst_percentage": tax_helper_object.get_percentage("SGST"), | |
"shipping_price": (shipping_line)["price"].to_i, | |
"tax_percentage": 0, | |
"total_cst": 0, | |
"total_price": shr["total_price"], | |
"total_taxes": shr["total_tax"], | |
"total_vat": 0, | |
"unit_price": 0, | |
"unit_taxes": 0, | |
"vat_percentage": 0 | |
} | |
} | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# config/environments/development.rb | |
#this file contains the constants used in the code | |
# ------------- shopify credentials ----------------- | |
SHOPIFY_ADMIN_USERNAME = "<your shopify private app admin username>" | |
SHOPIFY_ADMIN_PASSWORD = "<your shopify private app admin password>" | |
SHOPIFY_SHARED_SECRET = "<your shopify private app shared secret>" # can be used to verify source in the webhook method | |
SHOPIFY_ENDPOINT = "<your shopify admin api endpoint>" # Example: https://testwebsite-staging.myshopify.com/admin | |
DELHIVERY_TOKEN = "<your delhivery token here>" | |
DELHIVERY_ACCESS_KEY = "<your delhivery access key>" | |
# this end point is used for order creation | |
DELHIVERY_API_ENDPOINT = "https://sandbox-api-godam.delhivery.com" | |
# this end point is used for order cancellation | |
DELHIVERY_ENDPOINT = "https://sandbox-godam.delhivery.com" | |
DELHIVERY_FULFILLMENT_CENTRE = "<your delhivery fulfillment center>" # will be provided by delhivery | |
DELHIVERY_CLIENT_STORE = "<your delhivery client store>" # will be provided by delhivery | |
# ---------------------------------------------------- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# ------------- shopify ecommerce ---------------- | |
get "/shopify/api/pincode_eta", to: "shopify#get_eta_from_pincode" | |
post "/shopify/orders/create", to: "shopify#order_create_response" | |
post "/shopify/orders/tracking_response", to: "shopify#tracking_response" | |
post "/shopify/orders/cancellation_response", to: "shopify#cancellation_response" | |
# ------------------------------------------------ | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Sample curl request for order cancelling at delhivery | |
curl -X PUT \ | |
'https://sandbox-godam.delhivery.com/oms/api/update/CAN/?client_store=%3Cyour_client_store%3E&fulfillment_center=%3Cyour_fulfillment_center%3E&version=2014.09&order_id=TEST1016EPOPXO&sub_order_id=278508305711' \ | |
-H 'accept: application/json' \ | |
-H 'authorization: Token <your_token>' \ | |
-H 'cache-control: no-cache' \ | |
-H 'content_type: application/x-www-form-urlencoded' \ | |
-H 'postman-token: 813dfd35-be76-46b6-1578-347f91ed4042' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# this file contains the sample delhivery response with the tracking number | |
# this response is the case for only one type sub_order | |
{ | |
"orderlines": [ | |
{ | |
"status": "PAK", | |
"courier": "Delhivery", | |
"weight": "0.0", | |
"order_id": "TEST1021", | |
"waybill": "1623110010010", #tracking number | |
"order_line_id": "326642412079", #sub_order_id | |
"invoice_date": "2018-01-19T19:41:40.000+05:30", | |
"fulfillment_center": "DELLL2", # Delhivery provides you this | |
"client_store": "my delhivery client store", # Delhivery provides you this | |
"products": [ | |
{ | |
"status": "PAK", | |
"prod_sku": "1032010100017", | |
"prod_num": "1032010100017", | |
"prod_qty": 2, | |
"imei": "", | |
"prod_name": "Test Product" | |
} | |
] | |
} | |
], | |
"controller": "shopify", | |
"action": "tracking_response", | |
"shopify": { | |
"orderlines": [ | |
{ | |
"status": "PAK", | |
"courier": "Delhivery", | |
"weight": "0.0", | |
"order_id": "TEST1021", | |
"waybill": "1623110010010", | |
"order_line_id": "326642412079", | |
"invoice_date": "2018-01-19T19:41:40.000+05:30", | |
"fulfillment_center": "DELLL2", | |
"client_store": "my delhivery client store", | |
"products": [ | |
{ | |
"status": "PAK", | |
"prod_sku": "1032010100017", | |
"prod_num": "1032010100017", | |
"prod_qty": 2, | |
"imei": "", | |
"prod_name": "Test Product" | |
} | |
] | |
} | |
] | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# this file contains sample shopify response on order creation | |
# I don't understand why shopify repeats the same data that it gives inside the shopify object as well | |
{ | |
"id": 14959607852, | |
"email": "gauravsobti1@gmail.com", | |
"closed_at": null, | |
"created_at": "2018-01-12T16:22:21+05:30", | |
"updated_at": "2018-01-12T16:22:26+05:30", | |
"number": 18, | |
"note": null, | |
"token": "029507bae69esd0f30800c178d305daa", | |
"gateway": "cash_on_delivery", | |
"test": false, | |
"total_price": "1638.00", | |
"subtotal_price": "1598.00", | |
"total_weight": 1000, | |
"total_tax": "243.76", | |
"taxes_included": true, | |
"currency": "INR", | |
"financial_status": "pending", | |
"confirmed": true, | |
"total_discounts": "0.00", | |
"total_line_items_price": "1598.00", | |
"cart_token": "dcaa2b5adce3f175d89d68sddd31085a", | |
"buyer_accepts_marketing": true, | |
"name": "#TEST1018", | |
"referring_site": "", | |
"landing_site": "/password", | |
"cancelled_at": null, | |
"cancel_reason": null, | |
"total_price_usd": "25.25", | |
"checkout_token": "d9fc9075d045aba644e5b476102883e5", | |
"reference": null, | |
"user_id": null, | |
"location_id": null, | |
"source_identifier": null, | |
"source_url": null, | |
"processed_at": "2018-01-12T16:22:21+05:30", | |
"device_id": null, | |
"phone": null, | |
"customer_locale": "en", | |
"app_id": 580111, | |
"browser_ip": null, | |
"landing_site_ref": null, | |
"order_number": 1018, | |
"discount_codes": null, | |
"note_attributes": null, | |
"payment_gateway_names": [ | |
"cash_on_delivery" | |
], | |
"processing_method": "offsite", | |
"checkout_id": 26242777132, | |
"source_name": "web", | |
"fulfillment_status": null, | |
"tax_lines": [ | |
{ | |
"title": "CGST", | |
"price": "121.88", | |
"rate": 0.09 | |
}, | |
{ | |
"title": "SGST", | |
"price": "121.88", | |
"rate": 0.09 | |
} | |
], | |
"tags": "", | |
"contact_email": "gauravsobti1@gmail.com", | |
"order_status_url": "https://myshopifywebsite.com/26619678/orders/0f30800c178d305daa029507bae69e39/authenticate?key=d11a01f61ef45cb04334a3f49124e7c6", | |
"line_items": [ | |
{ | |
"id": 31644384492, | |
"variant_id": 244313756972, | |
"title": "My Shopify Dummy Product", | |
"quantity": 2, | |
"price": "799.00", | |
"sku": "", | |
"variant_title": "", | |
"vendor": "My Company Name", | |
"fulfillment_service": "manual", | |
"product_id": 45838550316, | |
"requires_shipping": true, | |
"taxable": true, | |
"gift_card": false, | |
"name": "My Shopify Dummy Product", | |
"variant_inventory_management": "shopify", | |
"properties": null, | |
"product_exists": true, | |
"fulfillable_quantity": 2, | |
"grams": 500, | |
"total_discount": "0.00", | |
"fulfillment_status": null, | |
"tax_lines": [ | |
{ | |
"title": "CGST", | |
"price": "121.88", | |
"rate": 0.09 | |
}, | |
{ | |
"title": "SGST", | |
"price": "121.88", | |
"rate": 0.09 | |
} | |
], | |
"origin_location": { | |
"id": 3812294700, | |
"country_code": "IN", | |
"province_code": "DL", | |
"name": "myshopifywebsite", | |
"address1": "some address", | |
"address2": "", | |
"city": "New Delhi", | |
"zip": "110026" | |
}, | |
"destination_location": { | |
"id": 9329723658, | |
"country_code": "IN", | |
"province_code": "DL", | |
"name": "Gaurav Sobti", | |
"address1": "baskh ask sdjkhkjsh ", | |
"address2": "", | |
"city": "New Delhi", | |
"zip": "110026" | |
} | |
} | |
], | |
"shipping_lines": [ | |
{ | |
"id": 11529682988, | |
"title": "Standard Shipping (Cash on Delivery)", | |
"price": "40.00", | |
"code": "23549", | |
"source": "Cash on Delivery app", | |
"phone": null, | |
"requested_fulfillment_service_id": null, | |
"delivery_category": null, | |
"carrier_identifier": "fb5772303f6273ceb1a596e2b039d02d", | |
"discounted_price": "40.00", | |
"tax_lines": null | |
} | |
], | |
"billing_address": { | |
"first_name": "Gaurav", | |
"address1": "baskh ask sdjkhkjsh ", | |
"phone": "83768 80048", | |
"city": "New Delhi", | |
"zip": "110015", | |
"province": "Delhi", | |
"country": "India", | |
"last_name": "Sobti", | |
"address2": "", | |
"company": null, | |
"latitude": 27.1589251, | |
"longitude": 76.0457614, | |
"name": "Gaurav Sobti", | |
"country_code": "IN", | |
"province_code": "DL" | |
}, | |
"shipping_address": { | |
"first_name": "Gaurav", | |
"address1": "baskh ask sdjkhkjsh ", | |
"phone": "83768 80048", | |
"city": "New Delhi", | |
"zip": "110015", | |
"province": "Delhi", | |
"country": "India", | |
"last_name": "Sobti", | |
"address2": "", | |
"company": null, | |
"latitude": 27.1589251, | |
"longitude": 76.0457614, | |
"name": "Gaurav Sobti", | |
"country_code": "IN", | |
"province_code": "DL" | |
}, | |
"fulfillments": null, | |
"client_details": { | |
"browser_ip": "50.74.5.106", | |
"accept_language": "en-US,en;q=0.9,pt;q=0.8", | |
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36", | |
"session_hash": "2198d2ef3dfb91062d5852cb5f6dd9c7", | |
"browser_width": 1440, | |
"browser_height": 803 | |
}, | |
"refunds": null, | |
"customer": { | |
"id": 41743155244, | |
"email": "gauravsobti1@gmail.com", | |
"accepts_marketing": true, | |
"created_at": "2018-01-11T18:25:06+05:30", | |
"updated_at": "2018-01-12T16:22:21+05:30", | |
"first_name": "Gaurav", | |
"last_name": "Sobti", | |
"orders_count": 3, | |
"state": "invited", | |
"total_spent": "0.00", | |
"last_order_id": 12958607852, | |
"note": null, | |
"verified_email": true, | |
"multipass_identifier": null, | |
"tax_exempt": false, | |
"phone": null, | |
"tags": "", | |
"last_order_name": "#TEST1018", | |
"default_address": { | |
"id": 58670359804, | |
"customer_id": 30743154244, | |
"first_name": "Gaurav", | |
"last_name": "Sobti", | |
"company": null, | |
"address1": "baskh ask sdjkhkjsh ", | |
"address2": "", | |
"city": "New Delhi", | |
"province": "Delhi", | |
"country": "India", | |
"zip": "110026", | |
"phone": "99998 80099", | |
"name": "Gaurav Sobti", | |
"province_code": "DL", | |
"country_code": "IN", | |
"country_name": "India", | |
"default": true | |
} | |
}, | |
"shopify": { | |
"id": 14959607852, | |
"email": "gauravsobti1@gmail.com", | |
"closed_at": null, | |
"created_at": "2018-01-12T16:22:21+05:30", | |
"updated_at": "2018-01-12T16:22:26+05:30", | |
"number": 18, | |
"note": null, | |
"token": "029507bae69esd0f30800c178d305daa", | |
"gateway": "cash_on_delivery", | |
"test": false, | |
"total_price": "1638.00", | |
"subtotal_price": "1598.00", | |
"total_weight": 1000, | |
"total_tax": "243.76", | |
"taxes_included": true, | |
"currency": "INR", | |
"financial_status": "pending", | |
"confirmed": true, | |
"total_discounts": "0.00", | |
"total_line_items_price": "1598.00", | |
"cart_token": "dcaa2b5adce3f175d89d68sddd31085a", | |
"buyer_accepts_marketing": true, | |
"name": "#TEST1018", | |
"referring_site": "", | |
"landing_site": "/password", | |
"cancelled_at": null, | |
"cancel_reason": null, | |
"total_price_usd": "25.25", | |
"checkout_token": "d9fc9075d045aba644e5b476102883e5", | |
"reference": null, | |
"user_id": null, | |
"location_id": null, | |
"source_identifier": null, | |
"source_url": null, | |
"processed_at": "2018-01-12T16:22:21+05:30", | |
"device_id": null, | |
"phone": null, | |
"customer_locale": "en", | |
"app_id": 580111, | |
"browser_ip": null, | |
"landing_site_ref": null, | |
"order_number": 1018, | |
"discount_codes": null, | |
"note_attributes": null, | |
"payment_gateway_names": [ | |
"cash_on_delivery" | |
], | |
"processing_method": "offsite", | |
"checkout_id": 26242575132, | |
"source_name": "web", | |
"fulfillment_status": null, | |
"tax_lines": [ | |
{ | |
"title": "CGST", | |
"price": "121.88", | |
"rate": 0.09 | |
}, | |
{ | |
"title": "SGST", | |
"price": "121.88", | |
"rate": 0.09 | |
} | |
], | |
"tags": "", | |
"contact_email": "gauravsobti1@gmail.com", | |
"order_status_url": "https://myshopifywebsite.com/26619678/orders/0f30800c178d305daa029507bae69e39/authenticate?key=d11a01f61ef45cb04334a3f49124e7c6", | |
"line_items": [ | |
{ | |
"id": 31644384492, | |
"variant_id": 244313756972, | |
"title": "My Shopify Dummy Product", | |
"quantity": 2, | |
"price": "799.00", | |
"sku": "", | |
"variant_title": "", | |
"vendor": "My Company Name", | |
"fulfillment_service": "manual", | |
"product_id": 45838550316, | |
"requires_shipping": true, | |
"taxable": true, | |
"gift_card": false, | |
"name": "My Shopify Dummy Product", | |
"variant_inventory_management": "shopify", | |
"properties": null, | |
"product_exists": true, | |
"fulfillable_quantity": 2, | |
"grams": 500, | |
"total_discount": "0.00", | |
"fulfillment_status": null, | |
"tax_lines": [ | |
{ | |
"title": "CGST", | |
"price": "121.88", | |
"rate": 0.09 | |
}, | |
{ | |
"title": "SGST", | |
"price": "121.88", | |
"rate": 0.09 | |
} | |
], | |
"origin_location": { | |
"id": 3812294700, | |
"country_code": "IN", | |
"province_code": "DL", | |
"name": "myshopifywebsite", | |
"address1": "some address", | |
"address2": "", | |
"city": "New Delhi", | |
"zip": "110026" | |
}, | |
"destination_location": { | |
"id": 9329723658, | |
"country_code": "IN", | |
"province_code": "DL", | |
"name": "Gaurav Sobti", | |
"address1": "baskh ask sdjkhkjsh ", | |
"address2": "", | |
"city": "New Delhi", | |
"zip": "110026" | |
} | |
} | |
], | |
"shipping_lines": [ | |
{ | |
"id": 11529682988, | |
"title": "Standard Shipping (Cash on Delivery)", | |
"price": "40.00", | |
"code": "23549", | |
"source": "Cash on Delivery app", | |
"phone": null, | |
"requested_fulfillment_service_id": null, | |
"delivery_category": null, | |
"carrier_identifier": "fb5772303f6273ceb1a596e2b039d02d", | |
"discounted_price": "40.00", | |
"tax_lines": null | |
} | |
], | |
"billing_address": { | |
"first_name": "Gaurav", | |
"address1": "baskh ask sdjkhkjsh ", | |
"phone": "83768 80048", | |
"city": "New Delhi", | |
"zip": "110015", | |
"province": "Delhi", | |
"country": "India", | |
"last_name": "Sobti", | |
"address2": "", | |
"company": null, | |
"latitude": 27.1589251, | |
"longitude": 76.0457614, | |
"name": "Gaurav Sobti", | |
"country_code": "IN", | |
"province_code": "DL" | |
}, | |
"shipping_address": { | |
"first_name": "Gaurav", | |
"address1": "baskh ask sdjkhkjsh ", | |
"phone": "83768 80048", | |
"city": "New Delhi", | |
"zip": "110015", | |
"province": "Delhi", | |
"country": "India", | |
"last_name": "Sobti", | |
"address2": "", | |
"company": null, | |
"latitude": 27.1589251, | |
"longitude": 76.0457614, | |
"name": "Gaurav Sobti", | |
"country_code": "IN", | |
"province_code": "DL" | |
}, | |
"fulfillments": null, | |
"client_details": { | |
"browser_ip": "50.74.5.106", | |
"accept_language": "en-US,en;q=0.9,pt;q=0.8", | |
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36", | |
"session_hash": "2198d2ef3dfb91062d5852cb5f6dd9c7", | |
"browser_width": 1440, | |
"browser_height": 803 | |
}, | |
"refunds": null, | |
"customer": { | |
"id": 41743155244, | |
"email": "gauravsobti1@gmail.com", | |
"accepts_marketing": true, | |
"created_at": "2018-01-11T18:25:06+05:30", | |
"updated_at": "2018-01-12T16:22:21+05:30", | |
"first_name": "Gaurav", | |
"last_name": "Sobti", | |
"orders_count": 3, | |
"state": "invited", | |
"total_spent": "0.00", | |
"last_order_id": 12958607852, | |
"note": null, | |
"verified_email": true, | |
"multipass_identifier": null, | |
"tax_exempt": false, | |
"phone": null, | |
"tags": "", | |
"last_order_name": "#TEST1018", | |
"default_address": { | |
"id": 58670359804, | |
"customer_id": 30743154244, | |
"first_name": "Gaurav", | |
"last_name": "Sobti", | |
"company": null, | |
"address1": "baskh ask sdjkhkjsh ", | |
"address2": "", | |
"city": "New Delhi", | |
"province": "Delhi", | |
"country": "India", | |
"zip": "110026", | |
"phone": "99998 80099", | |
"name": "Gaurav Sobti", | |
"province_code": "DL", | |
"country_code": "IN", | |
"country_name": "India", | |
"default": true | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ShopifyController < ApplicationController | |
skip_before_filter :verify_authenticity_token, only:[:order_create_response,:tracking_response,:cancellation_response] | |
# shopify webhook for order create | |
# We store order_id, order_name, and sub_order_ids in our database to maintain data authenticity | |
# This function parses shopify response and creates order at delhivery | |
# We store all the sub_order_id separately to handle the multiple product type case | |
# because multiple sub_orders can have different types of shipping fulfillments, one might reach early and one late | |
def order_create_response | |
puts params.to_json | |
order_id = params["id"].to_s | |
sub_orders = params["line_items"] | |
sub_orders.each do |so| | |
shopify_order = ShopifyOrder.find_or_initialize_by( order_id: order_id, sub_order_id: so["id"].to_s ) | |
shopify_order.order_name = params["name"] | |
shopify_order.save | |
end | |
data = DelhiveryService.create_delhivery_data_from_shopify_data(params) | |
is_success = DelhiveryService.create_order(data) | |
if is_success | |
ShopifyOrder.where(order_id: order_id).update_all(godam_status: "submitted") | |
else | |
Bugsnag.notify("error creating an order on delhivery for shopify order no #{order_id}") | |
end | |
render json: {message: "Success"}, status: 200 and return | |
end | |
#called by shopify when an order is cancelled | |
#we then call the delhivery api and cancel the order there as well | |
#in our shopify website frontend, we handle the scenario where cancellation is not allowed if the order has already been shipped | |
def cancellation_response | |
sub_orders = params["line_items"] | |
sub_order_id = sub_orders[0]["id"].to_s | |
order_id = params["id"].to_s | |
order_name = params["name"] | |
shopify_order = ShopifyOrder.find_by(order_id: order_id, sub_order_id: sub_order_id) | |
# if order not cancelled | |
if shopify_order.present? | |
if shopify_order.godam_status != "cancelled" | |
response = DelhiveryService.cancel_order(order_name, sub_order_id) | |
response_body = JSON.parse(response.read_body) | |
if response.code == "200" | |
shopify_order.update(godam_status: "cancelled") | |
else | |
Bugsnag.notify("Couldn't cancel Shopify order: #{order_name} on delhivery because: " + response_body["message"]) | |
end | |
else | |
puts "Order #{order_name} already cancelled" | |
end | |
else | |
Bugsnag.notify("Shopify order: #{order_name} not present") | |
end | |
render json: {message: "success"}, status: 200 and return | |
end | |
# This function is called by delhivery service with the tracking number, which we use to update the shopify order | |
# If the tracking number is present in response, we create shopify fulfillment agianst the order | |
# Which in turn notifies the user regarding shipping status and gives the user tracking number to track the order | |
def tracking_response | |
puts params.to_json | |
orderlines = params | |
orderlines["orderlines"].each do |sub_order| | |
order_name = sub_order["order_id"].to_s | |
tracking_number = sub_order["waybill"] | |
sub_order_id = sub_order["order_line_id"].to_s | |
godam_status = sub_order["status"] | |
if tracking_number.present? | |
shopify_order = ShopifyOrder.find_by(order_name: order_name, sub_order_id: sub_order_id) | |
if shopify_order.present? | |
shopify_order.tracking_number = tracking_number | |
shopify_order.godam_status = godam_status | |
shopify_order.save | |
if !shopify_order.fulfillment_id.present? | |
fulfillment_id = update_tracking_number(shopify_order.order_id, sub_order_id, tracking_number) | |
if fulfillment_id.present? | |
shopify_order.fulfillment_id = fulfillment_id | |
shopify_order.save | |
end | |
else | |
puts "fulfillment for order_id = #{shopify_order.order_id} also exists and its id = #{shopify_order.fulfillment_id}" | |
end | |
else | |
Bugsnag.notify("Couldn't find shopify order with order name = #{order_name} and sub_order_id = #{sub_order_id}") | |
end | |
end | |
end | |
render json: {message: "Successfully received response"}, status: 200 and return | |
end | |
private | |
def update_tracking_number(order_id, sub_order_id, tracking_number) | |
fulfillment_id = ShopifyService.update_tracking_number(order_id, tracking_number, "Delhivery", sub_order_id ) | |
return fulfillment_id | |
end | |
end | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module ShopifyHelper | |
class Tax | |
# "tax_lines" array inside the "line_items" array is passed here | |
# then it becomes easy to get value or a default value of a certain type of tax | |
def initialize(tax_array) | |
@tax_array = tax_array | |
end | |
def get_tax_type(tax_type) | |
@tax_array.find{|x| x["title"] == tax_type} | |
end | |
def get_percentage(tax_type) | |
(get_tax_type(tax_type)['rate'] * 100) rescue 0 | |
end | |
def get_amount(tax_type) | |
(get_tax_type(tax_type)['price'].to_f) rescue 0.0 | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ShopifyService | |
def self.update_tracking_number(order_number, tracking_number, tracking_company, sub_order_id) | |
api_end_point = SHOPIFY_ENDPOINT + "/orders/#{order_number}/fulfillments.json" | |
fulfillment_object = {} | |
fulfillment_object["tracking_number"] = tracking_number | |
fulfillment_object["tracking_company"] = tracking_company | |
fulfillment_object["line_items"] = [] | |
line_item = {} | |
line_item["id"] = sub_order_id.to_i | |
fulfillment_object["line_items"] << line_item | |
fulfillment_body = {"fulfillment": fulfillment_object } | |
response = HTTParty.post(api_end_point, :body => fulfillment_body.to_json, :headers => get_headers, :basic_auth => get_auth ) | |
if response["errors"].present? | |
Bugsnag.notify(response["errors"].to_s) | |
return nil | |
else | |
return response["fulfillment"]["id"] | |
end | |
end | |
def self.create_webhook(url,topic) | |
# topic = "orders\/create", "orders\/cancelled" | |
api_end_point = SHOPIFY_ENDPOINT + "/webhooks.json" | |
body = { | |
"webhook": { | |
"topic": topic, | |
"address": url, | |
"format": "json" | |
} | |
} | |
response = HTTParty.post(api_end_point, :body => body.to_json, :headers => get_headers, :basic_auth => get_auth ) | |
end | |
def self.get_orders(from_date, to_date, page = nil, status=nil) | |
# statuses: | |
# open - All open orders (default) | |
# closed - Show only closed orders | |
# cancelled - Show only cancelled orders | |
# any - Any order status | |
api_end_point = SHOPIFY_ENDPOINT + "/orders.json" | |
params = {} | |
params["created_at_min"] = from_date.to_formatted_s(:iso8601) | |
params["created_at_max"] = to_date.to_formatted_s(:iso8601) | |
params["status"] = status.present? ? status : "open" | |
params["page"] = page.present? ? page : 1 | |
response = HTTParty.get(api_end_point, query: params, :headers => get_headers, :basic_auth => get_auth ) | |
return response | |
end | |
def self.get_order(order_id) | |
api_end_point = SHOPIFY_ENDPOINT + "/orders/" + order_id.to_s + ".json" | |
params = {} | |
response = HTTParty.get(api_end_point, query: params, :headers => get_headers, :basic_auth => get_auth ) | |
return response | |
end | |
def self.webhooks | |
api_end_point = SHOPIFY_ENDPOINT + "/webhooks.json" | |
response = HTTParty.get(api_end_point, :body => {}, :headers => get_headers, :basic_auth => get_auth ) | |
return response | |
end | |
def self.get_fulfillments(order_id) | |
api_end_point = SHOPIFY_ENDPOINT + "/orders/#{order_id}/fulfillments.json" | |
response = HTTParty.get(api_end_point, :body => {}, :headers => get_headers, :basic_auth => get_auth ) | |
return response | |
end | |
private | |
def self.get_headers | |
headers = {} | |
headers["Content-Type"] = "application/json" | |
return headers | |
end | |
def self.get_auth | |
{username: SHOPIFY_ADMIN_USERNAME, password: SHOPIFY_ADMIN_PASSWORD} | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment