xpost 0.1.22 → 0.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/TODO.md +28 -1
  4. data/lib/.DS_Store +0 -0
  5. data/lib/xpost.rb +8 -6
  6. data/lib/xpost/.DS_Store +0 -0
  7. data/lib/xpost/address.rb +77 -0
  8. data/lib/xpost/authentication.rb +5 -0
  9. data/lib/xpost/configuration.rb +15 -0
  10. data/lib/xpost/item.rb +44 -0
  11. data/lib/xpost/order.rb +137 -0
  12. data/lib/xpost/orders.rb +53 -12
  13. data/lib/xpost/statuses.rb +29 -0
  14. data/lib/xpost/version.rb +1 -1
  15. data/sample/sample_essential_order_details.json +64 -0
  16. data/sample/sample_xpost_order-confirmed.json +317 -0
  17. data/sample/sample_xpost_order-for-pickup.json +379 -0
  18. data/sample/sample_xpost_order-pending.json +306 -0
  19. data/sample/webhook_sample.json +105 -0
  20. data/spec/factories/addresses.rb +46 -28
  21. data/spec/factories/items.rb +10 -10
  22. data/spec/factories/orders.rb +39 -16
  23. data/spec/{address_spec.rb → order/address/address_spec.rb} +0 -0
  24. data/spec/order/cancel_spec.rb +37 -0
  25. data/spec/order/confirm_spec.rb +38 -0
  26. data/spec/order/create_spec.rb +97 -0
  27. data/spec/order/for_pickup_spec.rb +42 -0
  28. data/spec/order/get_shipping_label_spec.rb +60 -0
  29. data/spec/{item_spec.rb → order/items/item_spec.rb} +5 -5
  30. data/spec/order/order_spec.rb +97 -0
  31. data/spec/orders/{orders_find_spec.rb → find_spec.rb} +3 -2
  32. data/spec/orders/{orders_track_spec.rb → track_spec.rb} +2 -2
  33. data/spec/spec_helper.rb +1 -0
  34. data/spec/xpost_spec.rb +27 -0
  35. data/xpost.gemspec +3 -0
  36. metadata +64 -12
  37. data/lib/xpost/models.rb +0 -5
  38. data/lib/xpost/models/address.rb +0 -38
  39. data/lib/xpost/models/item.rb +0 -31
  40. data/lib/xpost/models/order.rb +0 -54
  41. data/spec/line_item_spec.rb +0 -16
  42. data/spec/orders/order_create_spec.rb +0 -46
  43. data/spec/orders/order_for_pickup_spec.rb +0 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4832cf045a12e9925e7035322a468229f9595f21
4
- data.tar.gz: 24efb647cb62555faad2579014427586e848095a
3
+ metadata.gz: b5fc760526e20467ede81eed604c4a37f44e1117
4
+ data.tar.gz: 3d0a6e0037d1ffe31f52e60c47e0fae285e77c6f
5
5
  SHA512:
6
- metadata.gz: 3868d624c43c26be929683674d5b075508df083b8565bef86e9202c9700fe18e2b130dfd96a55f85ece99c807f457bb3ed2a524fd2c1e829db818a901183bf31
7
- data.tar.gz: c9125a136750f4bc695d2b7b5709a5fe5e65fa8cd9dbfe77b014ba88981405c42e6ae88a4823d3cdd5fa939b803632c398d86f585e1dbf4ec646c2cc06f311ef
6
+ metadata.gz: 5fb6703120e57fd644c1be8b56bcdade905c741dd77fce32609491cbe0e1391bb19b7641b2d3c375301fa98d67defddaf13a65e11cf53da647a1f5168abca3b7
7
+ data.tar.gz: da7122f6a75884e3a154509ddc46831d19c3f9946200ae8f67d4645ff32f1986c68e09558528cc1f146a11631769e980065e34c63c3863ff36ccdbadcca552a1
data/.gitignore CHANGED
@@ -7,4 +7,5 @@ gemfiles/vendor/*
7
7
  tmp/
8
8
  spec/reports/
9
9
 
10
- .rspec_status
10
+ .rspec_status
11
+ .env
data/TODO.md CHANGED
@@ -1,5 +1,32 @@
1
1
 
2
-
3
2
  # xpost To-Do's
4
3
 
4
+ - it should be able to track order statuses on Orders.all (???)
5
+
6
+ # Bonus To-Do's
7
+
8
+ - it should find a way to remove the (@auth_token) requirement from all the methods
9
+ - and error out if an @auth_token is missing from the config
10
+ - OR
11
+ - it should just generate auth_tokens all the time since the api_key + secret_keys should be present
12
+ - and it should make sure that any method call will error out if a config isnt present
13
+
14
+ - it should accept shopify order data
15
+
16
+ # xpost Done
5
17
 
18
+ - it returns the shipping labels for a single order
19
+ - it returns all the shipping labels for an array of tracking numbers (no need to check for for-pickup)
20
+ - link: orders/download/awb/zebra
21
+ - return S3 link of AWB (airwaybills)
22
+ - return S3 link of POD (proof of deliveries)
23
+ - it should be able to mark an order for_pickup
24
+ - it should be able to cancel an existing order
25
+ - it should view an existing order
26
+ - it should be able to confirm an existing order
27
+ - it should be able to create an order
28
+ - figure out line per line why the json file isn't reading the sub array
29
+ - Xpost::Order.to_json
30
+ - base details
31
+ - delivery address
32
+ - items
Binary file
@@ -1,13 +1,15 @@
1
+
1
2
  module Xpost
2
3
  autoload :Configuration, 'xpost/configuration'
3
4
  autoload :Authentication, 'xpost/authentication'
4
- autoload :Orders, 'xpost/orders'
5
+ autoload :Statuses, 'xpost/statuses'
5
6
 
6
- module Models
7
- autoload :Address, 'xpost/models/address'
8
- autoload :Item, 'xpost/models/item'
9
- autoload :Order, 'xpost/models/order'
10
- end
7
+ autoload :Orders, 'xpost/orders' # Generic xpost order methods (find, track, etc.)
8
+ autoload :Order, 'xpost/order' # ActiveModel-type of Order class (Order.create, etc.)
9
+ # autoload :OrderSerializer, 'serializers/order_serializer'
10
+
11
+ autoload :Address, 'xpost/address'
12
+ autoload :Item, 'xpost/item'
11
13
 
12
14
  class << self
13
15
  attr_accessor :configuration
Binary file
@@ -0,0 +1,77 @@
1
+ require 'active_model'
2
+ require 'set'
3
+
4
+ module Xpost
5
+ class Address
6
+ include ActiveModel::Model
7
+
8
+ REQUIRED_ADDRESS_PARAMETERS = Set[:name, :line_1, :city, :state, :postal_code, :country]
9
+ OPTIONAL_ADDRESS_PARAMETERS = Set[:company, :title, :email, :phone_number, :mobile_number, :fax_number,
10
+ :district, :line_2, :remarks]
11
+
12
+ attr_accessor :name, :line_1, :city, :state, :postal_code, :country
13
+ attr_accessor :company, :title, :email, :phone_number, :mobile_number, :fax_number, :district, :line_2, :remarks
14
+
15
+ validates_presence_of :name, :line_1, :city, :state, :postal_code, :country
16
+
17
+ def initialize(options = {})
18
+ # (?) Mr., Mrs., Ms., etc.
19
+ @title = options[:title]
20
+ @name = options[:name]
21
+ @email = options[:email]
22
+ @phone_number = options[:phone_number]
23
+ @mobile_number = options[:mobile_number]
24
+ @fax_number = options[:fax_number]
25
+ @company = options[:company]
26
+
27
+ # Address Line 1
28
+ @line_1 = options[:line_1]
29
+ # Address Line 1
30
+ @line_2 = options[:line_2]
31
+ # City
32
+ @city = options[:city]
33
+ # District (NCR's Four Districts) (Optional)
34
+ @district = options[:district]
35
+ # State / Province
36
+ @state = options[:state]
37
+ # Postal Code / ZIP Code
38
+ @postal_code = options[:postal_code]
39
+ # Country
40
+ @country = options[:country]
41
+
42
+ @remarks = options[:remarks]
43
+ end
44
+
45
+ def attributes
46
+ {
47
+ title: self.title,
48
+ name: self.name,
49
+ email: self.email,
50
+ phone_number: self.phone_number,
51
+ mobile_number: self.mobile_number,
52
+ fax_number: self.fax_number,
53
+ company: self.company,
54
+ line_1: self.line_1,
55
+ line_2: self.line_2,
56
+ city: self.city,
57
+ district: self.district,
58
+ state: self.state,
59
+ postal_code: self.postal_code,
60
+ country: self.country,
61
+ remarks: self.remarks
62
+ }
63
+ end
64
+
65
+ def self.sample_address
66
+ address = Xpost::Address.new(
67
+ name: "Bobby Axelrod",
68
+ line_1: "EDSA Shangri-La",
69
+ city: "Mandaluyong",
70
+ state: "Metro Manila",
71
+ postal_code: "1650",
72
+ country: "PH"
73
+ )
74
+ address
75
+ end
76
+ end
77
+ end
@@ -10,6 +10,11 @@ module Xpost
10
10
  token = "#{header}.#{payload}.#{signature}"
11
11
  end
12
12
 
13
+ def self.sample_auth_token
14
+ config = Xpost::Configuration.sample_configuration
15
+ Xpost::Authentication.authenticate(config.api_key, config.secret_key)
16
+ end
17
+
13
18
  private
14
19
 
15
20
  def self.header
@@ -8,5 +8,20 @@ module Xpost
8
8
  @staging_url
9
9
  @production_url
10
10
  end
11
+
12
+ def self.sample_configuration
13
+ api_key_line = File.readlines('.env').first.chomp
14
+ secret_key_line = File.readlines('.env').last.chomp
15
+ api_key = api_key_line.split('=').last
16
+ secret_key = secret_key_line.split('=').last
17
+
18
+ sample_configuration = Xpost.configure do |config|
19
+ config.api_key = api_key
20
+ config.secret_key = secret_key
21
+ config.staging_url = "https://api.staging.lbcx.ph/v1"
22
+ config.production_url = "https://api.staging.lbcx.ph/v1"
23
+ end
24
+ Xpost.configuration
25
+ end
11
26
  end
12
27
  end
@@ -0,0 +1,44 @@
1
+ require 'active_model'
2
+ require 'set'
3
+
4
+ module Xpost
5
+ class Item
6
+ include ActiveModel::Model
7
+
8
+ ITEM_TYPES = Set[:product, :shipping, :tax, :fee, :insurance, :discount]
9
+
10
+ attr_accessor :product_type, :description, :amount, :quantity, :metadata
11
+
12
+ validates_presence_of :product_type, :description, :amount
13
+ validate :check_item_type
14
+
15
+ def initialize(options = {})
16
+ # Product Type
17
+ @product_type = options.key?(:product_type) ? options[:product_type] : "product"
18
+ # Product Description
19
+ @description = options[:description]
20
+ # (Single) Product Amount
21
+ @amount = options[:amount]
22
+ # Product Quantity
23
+ @quantity = options.key?(:quantity) ? options[:quantity].to_i : 1
24
+ # Additional Product Data (For third-party purposes)
25
+ @metadata = options[:metadata]
26
+ end
27
+
28
+ def check_item_type
29
+ if self.product_type.present? && !ITEM_TYPES.include?(self.product_type.to_sym)
30
+ errors.add(:type, "wrong item type")
31
+ end
32
+ end
33
+
34
+ def attributes
35
+ {
36
+ type: self.product_type,
37
+ description: self.description,
38
+ amount: self.amount,
39
+ quantity: self.quantity,
40
+ metadata: self.metadata
41
+ }
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,137 @@
1
+ require 'active_model'
2
+ require 'json'
3
+ require 'set'
4
+
5
+ module Xpost
6
+ class Order
7
+ include ActiveModel::Model
8
+
9
+ CONFIRMABLE_PARAMETERS = Set[:status, :email, :buyer_name, :contact_number, :delivery_address, :items,
10
+ :shipment, :total, :currency, :payment_method, :payment_provider]
11
+ FOR_PICKUP_PARAMETERS = Set[:pickup_address, :pickup_at]
12
+
13
+ attr_accessor :email, :buyer_name, :contact_number, :reference_id
14
+ attr_accessor :total, :currency, :payment_method, :payment_provider
15
+ attr_accessor :delivery_address, :pickup_address, :pickup_at
16
+ attr_accessor :status, :items, :shipment
17
+
18
+ validates_presence_of :email, :buyer_name, :contact_number
19
+ validates_presence_of :delivery_address
20
+
21
+ def initialize(options = {})
22
+ @status = options[:status] || "pending"
23
+ @reference_id = options[:reference_id]
24
+ @email = options[:email]
25
+ @buyer_name = options[:buyer_name]
26
+ @contact_number = options[:contact_number]
27
+ @delivery_address = options[:delivery_address]
28
+ @items = options[:items] || []
29
+ @shipment = options[:shipment]
30
+ @total = options[:total]
31
+ @currency = options[:currency]
32
+ @payment_method = options[:payment_method]
33
+ @payment_provider = options[:payment_provider]
34
+ @pickup_address = options[:pickup_address]
35
+ @pickup_at = options[:pickup_at]
36
+ end
37
+
38
+ def self.sample_order
39
+ o = Xpost::Order.new
40
+ o.email = "john@me.com"
41
+ o.buyer_name = "John Doe"
42
+ o.contact_number = "+6391712345678"
43
+ o.shipment = "small-pouch"
44
+ o.total = 1000
45
+ o.currency = "PHP"
46
+ o.payment_method = "cod"
47
+ o.payment_provider = "lbcx"
48
+ o.add_item(Xpost::Item.new(
49
+ type: "product",
50
+ description: "red shirt",
51
+ amount: 1000,
52
+ quantity: 1,
53
+ metadata: "Small"
54
+ ))
55
+ o.delivery_address = Xpost::Address.new(
56
+ name: "Bobby Axelrod",
57
+ line_1: "BGC Corporate Centre",
58
+ line_2: "5th floor",
59
+ city: "Taguig",
60
+ state: "Metro Manila",
61
+ postal_code: "1634",
62
+ country: "PH",
63
+ company: "Axe Capital",
64
+ title: "CEO",
65
+ email: "bobby@axe.com",
66
+ phone_number: "12345678",
67
+ mobile_number: "091712345678",
68
+ fax_number: "12345678",
69
+ district: "BGC",
70
+ remarks: "Send ASAP"
71
+ )
72
+ o
73
+ end
74
+
75
+ def confirmable?
76
+ self.valid? && self.has_delivery_address? && self.has_payment_method? && self.has_items?
77
+ end
78
+
79
+ # https://docs.quadx.xyz/#5f38afd0-66bb-468f-ab4e-993d4745a257
80
+ # Sets the order status to canceled. An order can be canceled provided they have not passed the cutoff date and time as specified in the contract.
81
+ def cancellable?
82
+ true
83
+ end
84
+
85
+ # https://docs.quadx.xyz/#c831dc25-2de4-36c5-c321-8e9db0fc2105
86
+ # The following fields can be updated provided they have not passed the cutoff date and time as specified in the contract.
87
+ def updatable?
88
+ true
89
+ end
90
+
91
+ def has_delivery_address?
92
+ self.delivery_address.present?
93
+ end
94
+
95
+ def has_payment_method?
96
+ self.total.present? && self.currency.present? && self.payment_method.present? && self.payment_provider.present?
97
+ end
98
+
99
+ def has_items?
100
+ self.items.present? ? self.items.count > 0 : false
101
+ end
102
+
103
+ def add_item(item)
104
+ self.items << item if item.valid?
105
+ end
106
+
107
+ def order_items
108
+ items = []
109
+ self.items.each do |item|
110
+ items << item.attributes
111
+ end
112
+ items
113
+ end
114
+
115
+ def attributes
116
+ {
117
+ reference_id: self.reference_id,
118
+ email: self.email,
119
+ buyer_name: self.buyer_name,
120
+ contact_number: self.contact_number,
121
+ shipment: self.shipment,
122
+ total: self.total,
123
+ currency: self.currency,
124
+ payment_method: self.payment_method,
125
+ payment_provider: self.payment_provider,
126
+ items: self.order_items,
127
+ delivery_address: self.delivery_address.attributes,
128
+ pickup_address: self.pickup_address.nil? ? nil : self.pickup_address.attributes,
129
+ pickup_at: self.pickup_at
130
+ }
131
+ end
132
+
133
+ def to_json
134
+ self.attributes.to_json
135
+ end
136
+ end
137
+ end
@@ -6,17 +6,28 @@ require 'typhoeus'
6
6
  # order = Order.create({...})
7
7
  # order.confirm / order.cancel
8
8
  # order.for_pickup(pickup_address, pickup_date)
9
- # order.update({...})
10
9
 
11
10
  module Xpost
12
11
  class Orders
13
12
  # cant actually get all orders since its paginated
14
- def self.all(auth_token)
15
- all_orders_url = "orders"
13
+ def self.all(auth_token, status = nil)
14
+ valid_xpost_statuses = ["pending", "confirmed", "for_pickup", "picked_up",
15
+ "failed_pickup", "failed_delivery", "claimed",
16
+ "return_in_transit", "returned", "failed_return",
17
+ "out_for_delivery", "for_consolidation", "consolidated",
18
+ "for_dropoff", "dropped_off", "for_transfer", "for_acceptance",
19
+ "accepted", "canceled", "in_transit", "delivered"]
20
+
21
+ if valid_xpost_statuses.include?(status)
22
+ all_orders_url = "orders?status=#{status}"
23
+ else
24
+ all_orders_url = "orders"
25
+ end
16
26
 
17
27
  get_url(all_orders_url, headers: auth_header(auth_token))
18
28
  end
19
29
 
30
+ # gets all the details of an order
20
31
  def self.find(auth_token, tracking_number)
21
32
  find_order_url = "orders/#{tracking_number}"
22
33
 
@@ -24,6 +35,7 @@ module Xpost
24
35
  res = res[:message] == "Not Found" ? "Not Found" : res
25
36
  end
26
37
 
38
+ # only gets the order tat (turnaround time) info
27
39
  def self.track(auth_token, tracking_number)
28
40
  track_order_url = "orders/#{tracking_number}"
29
41
 
@@ -31,16 +43,27 @@ module Xpost
31
43
  res = res[:message] == "Not Found" ? "Not Found" : { timeline: res[:tat], events: res[:events] }
32
44
  end
33
45
 
34
- def self.create(auth_token, options = {})
35
- raise "Missing order data" if options[:order_data].empty?
46
+ def self.exists?(auth_token, tracking_number)
47
+ find_order_url = "orders/#{tracking_number}"
48
+ res = get_url(find_order_url, headers: auth_header(auth_token))
49
+ res[:status] != 404 && res[:message] != "Not Found"
50
+ end
51
+
52
+ def self.create(auth_token, order = nil, shipment = nil, reference_id = nil)
53
+ raise "Missing order data" if order.nil?
54
+ raise "Missing shipment data" if shipment.nil?
55
+
56
+ order.shipment = shipment
57
+ order.reference_id = reference_id
36
58
 
37
- create_order_url = "orders"
38
- order_data = options[:order_data]
39
-
40
- res = post_url(create_order_url, data: order_data, headers: auth_header(auth_token))
59
+ res = post_url("orders", data: order, headers: auth_header(auth_token))
41
60
  end
42
61
 
43
- def self.confirm(auth_token, tracking_number, payment_reference_id)
62
+ def self.confirm(auth_token, tracking_number, payment_reference_id = nil)
63
+ # TODO: these checks are expensive (potentially three back and forth requests)
64
+ raise "Order not found" unless Xpost::Orders.exists?(auth_token, tracking_number)
65
+ raise "Order isn't pending" if Xpost::Orders.find(auth_token, tracking_number)[:status] != "pending"
66
+
44
67
  confirm_order_url = "orders/#{tracking_number}/confirm"
45
68
 
46
69
  res = put_url(confirm_order_url, data: payment_reference_id, headers: auth_header(auth_token))
@@ -49,21 +72,39 @@ module Xpost
49
72
 
50
73
  def self.cancel(auth_token, tracking_number)
51
74
  cancel_order_url = "orders/#{tracking_number}/cancel"
75
+ raise "Order isn't pending" if Xpost::Orders.find(auth_token, tracking_number)[:status] != "pending"
52
76
 
53
77
  res = put_url(cancel_order_url, headers: auth_header(auth_token))
54
78
  res = res[:message] == "Not Found" ? "Not Found" : res
55
79
  end
56
80
 
57
81
  def self.for_pickup(auth_token, tracking_number, pickup_address)
58
- pickup_address = Xpost::Models::Address.new(pickup_address)
82
+ raise "Order isn't confirmed" if Xpost::Orders.find(auth_token, tracking_number)[:status] != "confirmed"
59
83
  for_pickup_url = "orders/#{tracking_number}/for-pickup"
60
84
 
61
- raise "Invalid address" unless pickup_address.valid?
85
+ raise "Missing pickup address" if pickup_address.nil?
86
+ raise "Invalid pickup address" unless pickup_address.valid?
87
+ pickup_address = { pickup_address: pickup_address.attributes }
62
88
 
63
89
  res = put_url(for_pickup_url, data: pickup_address, headers: auth_header(auth_token))
64
90
  res = res[:message] == "Not Found" ? "Not Found" : res
65
91
  end
66
92
 
93
+ def self.get_shipping_labels(auth_token, tracking_number)
94
+ if tracking_number.kind_of?(Array)
95
+ shipping_labels_url = "orders/download/awb/zebra"
96
+
97
+ res = post_url(shipping_labels_url, data: tracking_number, headers: auth_header(auth_token))
98
+ res = res[:message] == "Not Found" ? "Not Found" : res
99
+ else
100
+ raise "Order isn't for_pickup" if Xpost::Orders.find(auth_token, tracking_number)[:status] != "for_pickup"
101
+ shipping_label_url = "orders/#{tracking_number}/awb?type=zebra"
102
+
103
+ res = get_url(shipping_label_url, headers: auth_header(auth_token))
104
+ res = res[:message] == "Not Found" ? "Not Found" : res
105
+ end
106
+ end
107
+
67
108
  private
68
109
 
69
110
  def self.base_url