Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GCW-3407 Gogox Gem Integration and Transport APIs #1195

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,13 @@ STOCKIT_API_TOKEN=

SLACK_API_TOKEN=
SLACK_PIN_CHANNEL=

GOGOX_CLIENT_ID=
GOGOX_CLIENT_SECRET=
GOGOX_GRANT_TYPE=
GOGOX_HOST=
GOGOX_PAYMENT_METHOD=
CROSSROADS_GEOLOCATION=
CROSSROADS_STREET_ADDRESS=
CROSSROADS_CONTACT_NAME=
CROSSROADS_CONTACT_PHONE=
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
GIT
remote: [email protected]:crossroads/go_go_van_api.git
revision: 3cbc5590e2a11a7903483de276bc668be88843cb
revision: 169d3a639cecd3615ebffb6f42571c7c319f2bd6
branch: master
specs:
go_go_van_api (0.0.1)
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/ability.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class Ability
can_manage_order_messages can_manage_offer_messages can_disable_user
can_manage_stocktakes can_manage_stocktake_revisions
can_manage_package_messages can_manage_organisations can_manage_user_roles
can_manage_canned_response
can_manage_canned_response can_manage_transport_orders
].freeze

PERMISSION_NAMES.each do |permission_name|
Expand Down
7 changes: 7 additions & 0 deletions app/controllers/api/v1/ability.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ def define_abilities
offers_package_abilities
canned_response_abilities
processing_destination_abilities
transport_order_abilities
end

def processing_destination_abilities
Expand Down Expand Up @@ -397,6 +398,12 @@ def schedule_abilities
can [:index, :show], Schedule if can_read_schedule?
end

def transport_order_abilities
can [:providers, :quote, :book], TransportOrder
can [:show, :cancel], TransportOrder, offer_id: @user_offer_ids
can [:show, :cancel], TransportOrder if can_manage_transport_orders?
end

def location_abilities
if (can_manage_locations? || @api_user)
can %i[index create destroy], Location
Expand Down
77 changes: 77 additions & 0 deletions app/controllers/api/v1/transports_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
module Api
module V1
class TransportsController < Api::V1::ApiController

skip_authorization_check only: :update_hook
load_and_authorize_resource :transport_order, parent: false, except: [:update_hook]

api :GET, '/v1/transports/providers', "List all GoodCity Tranports Options."
def providers
render json: TransportProvider.all.cached_json
end

api :POST, '/v1/transports/quote', "Get provider quote"
param :provider, String, desc: "Provider selected for transport"
param :vehicle_type, String, desc: "Transport vehicle-type"
param :offer_id, String, desc: "Id of the offer"
swatijadhav marked this conversation as resolved.
Show resolved Hide resolved
param :schedule_at, String, desc: "Scheduled time for delivery"
param :district_id, String, desc: "Id of the district"
def quote
swatijadhav marked this conversation as resolved.
Show resolved Hide resolved
order_price = TransportService.new(transport_params.to_h).quotation
render json: order_price
end

api :POST, '/v1/transports/book', "Book transport"
param :provider, String, desc: "Provider selected for transport"
param :vehicle_type, String, desc: "Transport vehicle-type"
param :offer_id, String, desc: "Id of the offer"
param :schedule_at, String, desc: "Scheduled time for delivery"
param :district_id, String, desc: "Id of the district"
param :pickup_contact_name, String, desc: "Contact Person Name"
param :pickup_contact_phone, String, desc: "Contact Person Mobile"
param :pickup_street_address, String, desc: "Pickup Address"
def book
order_info = TransportService.new(transport_params.to_h).book
swatijadhav marked this conversation as resolved.
Show resolved Hide resolved
render json: order_info
end

api :GET, '/v1/transports/:order_uuid', "Get GoodCity Tranport order details."
def show
order_info = TransportService.new({booking_id: params[:order_uuid], provider: transport_provider}).status
render json: order_info
end

api :POST, '/v1/transports/:order_uuid/cancel', "Cancel GoodCity Tranport order."
def cancel
order_info = TransportService.new({booking_id: params[:order_uuid], provider: transport_provider}).cancel
render json: order_info
end

api :POST, '/v1/transports/update_hook', "Webhook to update transport status"
def update_hook
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When implemented, we should use our own authorization check here. E.g. webhook can be /api/v1/transports/update_hook?shared_key=ds23f934dfs&param1=df&param2=23

Then check shared_key is as expected.

# setup ngrok and inspect response
# response details are not yet available from Gogox Provider
end

private

def transport_provider
order = TransportOrder.find_by(order_uuid: params[:order_uuid])
order.try(:transport_provider).try(:name)
end

def transport_params
set_district_id unless params["district_id"].presence
params.permit([
"scheduled_at", "district_id", "offer_id", "provider", "vehicle_type",
"pickup_street_address", "pickup_contact_name", "pickup_contact_phone"
])
end

def set_district_id
params["district_id"] = User.current_user.address.district_id
end

end
end
end
3 changes: 3 additions & 0 deletions app/models/transport_order.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class TransportOrder < ApplicationRecord
belongs_to :transport_provider
end
3 changes: 3 additions & 0 deletions app/models/transport_provider.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class TransportProvider < ApplicationRecord
include CacheableJson
end
5 changes: 5 additions & 0 deletions app/serializers/api/v1/transport_provider_serializer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module Api::V1
class TransportProviderSerializer < ApplicationSerializer
attributes :id, :name, :logo, :description, :metadata
end
end
129 changes: 129 additions & 0 deletions app/services/gogox.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
class Gogox
swatijadhav marked this conversation as resolved.
Show resolved Hide resolved

attr_accessor :params, :time, :vehicle, :district_id

VEHICLE_TYPES = ["van", "mudou", "mudou9"]

def initialize(options = {})
@params = options
@time = parse_pickup_time(options[:scheduled_at])
@vehicle = options[:vehicle_type]
@district_id = options[:district_id]
swatijadhav marked this conversation as resolved.
Show resolved Hide resolved
end

# Rsponse
# {
# "uuid" => "2f859363-5c43-4fe2-9b91-6c6c43d610d2",
# "status" => "pending",
# "vehicle_type" => "van",
# "payment_method" => "prepaid_wallet",
# "courier" => {},
# "pickup" => {
# "name" => "Swati J",
# "street_address" => "123",
# "floor_or_unit_number" => nil,
# "schedule_at" => 1609242940,
# "location" => {"lat" => 22.5029632, "lng" => 114.1277213},
# "contact" => {
# "name" => "Swati J",
# "phone_number" => "+85251111113",
# "phone_extension" => nil
# }
# },
# "destinations" => [{
# "name" => "GCAdmin User",
# "street_address" => "Santa Peak Road",
# "floor_or_unit_number" => nil,
# "location" => {"lat" => 32.3700365, "lng" => 120.9930016},
# "contact" => {
# "name" => "GCAdmin User",
# "phone_number" => "+85251111111"
# }
# }],
# "note_to_courier" => nil,
# "price" => {"amount" => 15000, "currency" => "HKD"},
# "price_breakdown" => [{"key" => "fee", "amount" => 15000}]
# }
def book
GogoxApi::Transport.new(order_attributes).order
end

# Response:
# {
# "vehicle_type" => "van",
# "estimated_price" => {"amount" => 15000, "currency" => "HKD"},
# "estimated_price_breakdown" => [{"key" => "fee", "amount" => 15000}]
# }
def quotation
GogoxApi::Transport.new(quotation_attributes).quotation
end

class << self

def transport_status(booking_id)
GogoxApi::Transport.new.status(booking_id)
end

# Response
# Response is nil on successful cancellation of GOGOX transport
def cancel_order(booking_id)
response = GogoxApi::Transport.new.cancel(booking_id)
if !response
{
order_uuid: booking_id,
status: "cancelled"
}
end
end

end

private

def order_attributes
{
'vehicle_type': vehicle_type,
"pickup_location": params[:pickup_location],
"pickup_street_address": params[:pickup_street_address],
"scheduled_at": parse_time,
"pickup_contact_name": params[:pickup_contact_name],
"pickup_contact_phone": params[:pickup_contact_phone],
"destination_location": params[:destination_location],
"destination_street_address": params[:destination_street_address],
"destination_contact_name": params[:destination_contact_name],
"destination_contact_phone": params[:destination_contact_phone]
}
end

def quotation_attributes
{
'vehicle_type': vehicle_type,
"scheduled_at": parse_time,
"pickup_location": params[:pickup_location],
"destination_location": params[:destination_location]
}
end

def vehicle_type
if vehicle.blank? || !VEHICLE_TYPES.include?(vehicle)
raise(ValueError, "vehicle should be from #{VEHICLE_TYPES.join(', ')}")
end
vehicle
end

def parse_pickup_time(time = nil)
return time if time.present?

# next available date within next 5 days
next_available_date = DateSet.new(5, 1).available_dates.first
(next_available_date.beginning_of_day + 12.hours)
end

def parse_time
@time = DateTime.parse(@time.to_s)
@time = @time.zone == "HKT" ? @time : @time.in_time_zone("Asia/Hong_Kong")
@time.to_i
end

class ValueError < StandardError; end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of just value error defining here, create a error in errors.rb
something like TransportationServiceError

end
110 changes: 110 additions & 0 deletions app/services/transport_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
class TransportService

attr_accessor :provider_name, :params, :provider, :booking_id, :user, :district_id

def initialize(options={})
@params = options
@provider_name = options && options[:provider]
swatijadhav marked this conversation as resolved.
Show resolved Hide resolved
@provider ||= Object::const_get(provider_name)
swatijadhav marked this conversation as resolved.
Show resolved Hide resolved
@booking_id = options && options[:booking_id]
swatijadhav marked this conversation as resolved.
Show resolved Hide resolved
@transport_constants = Rails.application.secrets.transport

fetch_user
fetch_district_id
end

def quotation
@provider.new(quotation_attributes).quotation
end

def book
response = @provider.new(order_attributes).book
if response[:error]
response[:error]
else
store_order_details(response)
end
end

def cancel
response = @provider.cancel_order(booking_id)
if response
update_order_details({
status: "cancelled",
order_uuid: booking_id
})
end
end

def status
response = @provider.transport_status(booking_id)
if response
update_order_details({
status: response["status"],
order_uuid: booking_id,
metadata: response
})
end
end

private

def store_order_details(response)
TransportOrder.create(
transport_provider_id: TransportProvider.find_by(name: provider_name.upcase).try(:id),
order_uuid: response["uuid"],
status: response["status"],
scheduled_at: response["pickup"]["schedule_at"],
metadata: response,
offer_id: @params[:offer_id]
)
end

def update_order_details(response)
order = TransportOrder.find_by(order_uuid: response[:order_uuid])
order.update_attributes(response)
order
end

def quotation_attributes
{
'vehicle_type': @params[:vehicle_type],
"scheduled_at": @params[:scheduled_at],
"pickup_location": pickup_location,
"destination_location": @transport_constants[:crossroads_geolocation]
}
end

def pickup_location
pickup_district = District.find(@district_id)
[pickup_district.latitude, pickup_district.longitude]
end

def order_attributes
{
'vehicle_type': @params[:vehicle_type],
"pickup_location": pickup_location,
"pickup_street_address": params[:pickup_street_address],
"scheduled_at": params[:schedule_at],
"pickup_contact_name": params[:pickup_contact_name] || @user.full_name,
"pickup_contact_phone": params[:pickup_contact_phone] || @user.mobile,
"destination_location": @transport_constants[:crossroads_geolocation],
"destination_street_address": @transport_constants[:crossroads_street_address],
"destination_contact_name": @transport_constants[:crossroads_contact_name],
"destination_contact_phone": @transport_constants[:crossroads_contact_phone]
}
end

def fetch_user
@user ||= if @params[:user_id].present?
swatijadhav marked this conversation as resolved.
Show resolved Hide resolved
User.find_by(id: @params[:user_id])
else
User.current_user
end
end

def fetch_district_id
@district_id ||= @params[:district_id].presence || @user.address.district_id
end

end
Loading