huginn_bigcommerce_order_agent 1.0.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 37b3f2f6cb0a72462c5ac574ceca9c2c43dc62f73141c36d4f9fded17baa91e9
4
+ data.tar.gz: d366ade42a3d0eba1cc01f164dc6a2b88f5d9a61f2ee4537706e5fc716179202
5
+ SHA512:
6
+ metadata.gz: deaa0ce3da5b2ffe1acf67b1fdd722016905d2f0382ab01a9a5302518552e20c63d17bb512619242b02695313e237dc39a61ffc61fecefc4bb7acecc1a588d7e
7
+ data.tar.gz: 9c500ed4653260221828b4ea07557cfbf04e4ed1868ec3763a23f44bd11363e7d7f1c1f75ad9f2fb36171f19ab084c2e072b721c92bfa02e8befa822a8bec2ba
data/LICENSE.txt ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2021 5 Stones
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,65 @@
1
+
2
+ module BigcommerceOrderAgent
3
+ module Client
4
+ class AbstractClient
5
+ @client = nil
6
+ @uri_base = ''
7
+
8
+ def initialize(store_hash, client_id, access_token)
9
+ @endpoint = 'https://api.bigcommerce.com'
10
+ @headers = {
11
+ 'X-Auth-Client' => client_id,
12
+ 'X-Auth-Token' => access_token,
13
+ 'Content-Type' => 'application/json',
14
+ 'Accept' => 'application/json'
15
+ }
16
+ @store_hash = store_hash
17
+ end
18
+
19
+ def self.uri_base
20
+ @uri_base
21
+ end
22
+
23
+ def uri_base
24
+ self.class.uri_base
25
+ end
26
+
27
+ def client
28
+ if !@client
29
+ @client = Faraday.new({ url: @endpoint, headers: @headers }) do |conn|
30
+ conn.use Faraday::Response::RaiseError
31
+ conn.response :logger, nil, { headers: true, bodies: true }
32
+ conn.response :json, :content_type => 'application/json'
33
+ conn.adapter Faraday.default_adapter
34
+ end
35
+ end
36
+
37
+ return @client
38
+ end
39
+
40
+ def uri(params = {}, path = nil)
41
+ params[:api_version] = 'v2' unless params[:api_version].present?
42
+ u = "/stores/#{@store_hash}/#{uri_base}"
43
+
44
+ if path
45
+ u = u + "/#{path}"
46
+ end
47
+
48
+ params.each do |key,val|
49
+ u = u.gsub(":#{key.to_s}", val.to_s)
50
+ end
51
+
52
+ # remove params that weren't provided
53
+ u = u.gsub(/(\/\:[^\/]+)/, '')
54
+
55
+ return u
56
+ end
57
+
58
+ def index(params = {})
59
+ response = client.get(uri, params)
60
+ return response.body['data']
61
+ end
62
+
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,22 @@
1
+ module BigcommerceOrderAgent
2
+ module Client
3
+ class Customer < AbstractClient
4
+ @uri_base = 'v3/customers'
5
+
6
+ # The v3 customer endpoint only returns a customer array. In the context of
7
+ # this agent, however, we only want one customer, so we intentionally return
8
+ # data[0]
9
+ def get(id, params = {})
10
+ begin
11
+ response = client.get(uri, { 'id:in' => id, 'include' => 'attributes,formfields' })
12
+ return response.body['data'][0]
13
+ rescue Faraday::Error::ClientError => e
14
+ raise BigcommerceApiError.new(
15
+ 'get order', "Failed to get order #{id}", { order_id: id }, e
16
+ )
17
+ end
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,83 @@
1
+ module BigcommerceOrderAgent
2
+ module Client
3
+ class Order < AbstractClient
4
+ @uri_base = ':api_version/orders/:order_id'
5
+
6
+ def get(id, params = {})
7
+ order = nil
8
+
9
+ begin
10
+ response = client.get(uri({ order_id: id }), params)
11
+ order = response.body
12
+ rescue Faraday::Error => e
13
+ raise BigcommerceApiError.new('get order', { order_id: id }, e)
14
+ end
15
+
16
+ if order.present?
17
+ order[:products] = get_products(id)
18
+ order[:coupons] = get_coupons(id)
19
+ order[:shipping_addresses] = get_shipping_addresses(id)
20
+ order[:transactions] = get_transactions(id)
21
+ order[:shipments] = get_shipments(id)
22
+
23
+ transactions = get_transactions(id)
24
+ end
25
+
26
+ return order
27
+ end
28
+
29
+ #---------- Sub Record Queries ----------#
30
+
31
+ # Returns the order's line items
32
+ def get_products(order_id, params = {})
33
+ begin
34
+ response = client.get(uri({ order_id: order_id }, 'products'), params)
35
+ return response.body
36
+ rescue Faraday::Error => e
37
+ raise BigcommerceApiError.new('get order products', { order_id: order_id }, e)
38
+ end
39
+ end
40
+
41
+ # Returns an array of shipping addresses attached to the order
42
+ def get_shipping_addresses(order_id, params = {})
43
+ begin
44
+ response = client.get(uri({ order_id: order_id }, 'shipping_addresses'), params)
45
+ return response.body
46
+ rescue Faraday::Error => e
47
+ raise BigcommerceApiError.new('get order shipping address', { order_id: order_id }, e)
48
+ end
49
+ end
50
+
51
+ # Returns order shipments
52
+ def get_shipments(order_id, params = {})
53
+ begin
54
+ response = client.get(uri({ order_id: order_id }, 'shipments'), params)
55
+ return response.body
56
+ rescue Faraday::Error => e
57
+ raise BigcommerceApiError.new('get order shipments', { order_id: order_id }, e)
58
+ end
59
+ end
60
+
61
+ # Returns order-level promotions
62
+ def get_coupons(order_id, params = {})
63
+ begin
64
+ response = client.get(uri({ order_id: order_id }, 'coupons'), params)
65
+ return response.body
66
+ rescue Faraday::Error => e
67
+ raise BigcommerceApiError.new('get order coupons', { order_id: order_id }, e
68
+ )
69
+ end
70
+ end
71
+
72
+ # Returns order transactions. NOTE:
73
+ def get_transactions(order_id, params = {})
74
+ begin
75
+ response = client.get(uri({ api_version: 'v3', order_id: order_id }, 'transactions'), params)
76
+ return response.body['data']
77
+ rescue Faraday::Error => e
78
+ raise BigcommerceApiError.new('get order transactions', { order_id: order_id }, e)
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,10 @@
1
+ class BigcommerceApiError < StandardError
2
+ attr_reader :scope, :data, :original_error
3
+
4
+ def initialize(scope, data, original_error)
5
+ @scope = scope
6
+ @data = data
7
+ @original_error = original_error
8
+ super(original_error.message)
9
+ end
10
+ end
@@ -0,0 +1,12 @@
1
+ require 'huginn_agent'
2
+ require 'json'
3
+
4
+ # load clients
5
+ HuginnAgent.load 'client/abstract_client'
6
+ HuginnAgent.load 'client/customer'
7
+ HuginnAgent.load 'client/order'
8
+
9
+ # load errors
10
+ HuginnAgent.load 'errors/bigcommerce_api_error'
11
+
12
+ HuginnAgent.register 'huginn_bigcommerce_order_agent/bigcommerce_order_agent'
@@ -0,0 +1,180 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Agents
6
+ class BigcommerceOrderAgent < Agent
7
+ include WebRequestConcern
8
+
9
+ can_dry_run!
10
+ default_schedule 'never'
11
+
12
+ # TODO: Provide a more detailed agent description. Including details of
13
+ # each option and how that option is used
14
+ description <<-MD
15
+ Used to fetch order data from the BigCommerce API.
16
+
17
+ The BigCommerce API does not provide a way to quickly fetch the full product
18
+ record in a single request. The `PostAgent` provided by Huginn will do the
19
+ trick, but it requires 6 agents just to _fetch_ the data, and formatters to
20
+ ensure the `body` data in the payload is not lost on each subsequent request.
21
+
22
+ This agent is designed to consolidate that process into a single, no-frills
23
+ execution. We fetch the data, and return it as is in a single JSON object.
24
+ It's up to the end user to decide what to do next.
25
+
26
+
27
+ ### **Data Structures:**
28
+
29
+ * [Orders](https://developer.bigcommerce.com/api-reference/store-management/orders/orders/getanorder#responses)
30
+ * [Customers](https://developer.bigcommerce.com/api-reference/store-management/customers-v3/customers/customersget#responses)
31
+ * [Order Products](https://developer.bigcommerce.com/api-reference/store-management/orders/order-products/getallorderproducts#responses)
32
+ * [Shipping Addresses](https://developer.bigcommerce.com/api-reference/store-management/orders/order-shipping-addresses/getallshippingaddresses#responses)
33
+ * [Order Shipments](https://developer.bigcommerce.com/api-reference/store-management/orders/order-shipments/getallordershipments#responses)
34
+ * [Order Coupons](https://developer.bigcommerce.com/api-reference/store-management/orders/order-coupons/getallordercoupons#responses)
35
+ * [Transactions](https://developer.bigcommerce.com/api-reference/store-management/order-transactions/transactions/gettransactions#responses)
36
+
37
+ ### **Options:**
38
+ * store_hash - required
39
+ * client_id - required
40
+ * access_token - required
41
+ * order_id - required
42
+ * output_mode - not required ('clean' or 'merge', defaults to 'clean')
43
+
44
+
45
+ ### **Agent Payloads:**
46
+
47
+ **Success Payload:**
48
+
49
+ ```
50
+ {
51
+ order: {
52
+ [...],
53
+ customer: { ... },
54
+ products: { ... },
55
+ coupons: { ... },
56
+ shipping_addresses: { ... },
57
+ transactions: { ... }
58
+ },
59
+ status: 200,
60
+ }
61
+ ```
62
+
63
+ **Error Payload:**
64
+
65
+ ```
66
+ {
67
+ status: 5XX | 4XX,
68
+ scope: string,
69
+ response_body: {
70
+ status: number,
71
+ code: number,
72
+ title: string,
73
+ type: url,
74
+ errors: { ... }
75
+ },
76
+ request_data: { ... },
77
+ }
78
+ ```
79
+ MD
80
+
81
+ def default_options
82
+ {
83
+ 'store_hash' => '',
84
+ 'client_id' => '',
85
+ 'access_token' => '',
86
+ 'output_mode' => 'clean',
87
+ 'order_id' => '',
88
+ }
89
+ end
90
+
91
+ def validate_options
92
+ unless options['store_hash'].present?
93
+ errors.add(:base, 'store_hash is a required field')
94
+ end
95
+
96
+ unless options['client_id'].present?
97
+ errors.add(:base, 'client_id is a required field')
98
+ end
99
+
100
+ unless options['access_token'].present?
101
+ errors.add(:base, 'access_token is a required field')
102
+ end
103
+
104
+ unless options['order_id'].present?
105
+ errors.add(:base, 'order_id is a required field')
106
+ end
107
+
108
+ if options['output_mode'].present? && !options['output_mode'].to_s.include?('{') && !%[clean merge].include?(options['output_mode'].to_s)
109
+ errors.add(:base, "if provided, output_mode must be 'clean' or 'merge'")
110
+ end
111
+ end
112
+
113
+ def working?
114
+ received_event_without_error?
115
+ end
116
+
117
+ def check
118
+ initialize_clients
119
+ handle interpolated['payload'].presence || {}
120
+ end
121
+
122
+ def receive(incoming_events)
123
+ initialize_clients
124
+ incoming_events.each do |event|
125
+ handle(event)
126
+ end
127
+ end
128
+
129
+ def handle(event)
130
+ data = event.payload
131
+ order_id = data['id']
132
+ new_event = interpolated['output_mode'].to_s == 'merge' ? data.dup : {}
133
+
134
+ if (order_id.blank?)
135
+ create_event payload: new_event.merge(
136
+ status: 500,
137
+ message: "'#{order_id}' is not a valid order id",
138
+ )
139
+ end
140
+
141
+ begin
142
+ order = @order_client.get(order_id)
143
+ order[:customer] = @customer_client.get(order['customer_id'])
144
+
145
+ create_event payload: new_event.merge(
146
+ order: order,
147
+ status: 200,
148
+ )
149
+
150
+ rescue BigcommerceApiError => e
151
+ faraday_error = e.original_error
152
+
153
+ create_event payload: new_event.merge(
154
+ status: faraday_error.response[:status],
155
+ scope: e.scope,
156
+ response: faraday_error.response[:body],
157
+ request_data: e.data,
158
+ )
159
+ end
160
+
161
+ end
162
+
163
+ private
164
+
165
+ def initialize_clients
166
+ @order_client = initialize_client(:Order)
167
+ @customer_client = initialize_client(:Customer)
168
+ end
169
+
170
+ def initialize_client(class_name)
171
+ klass = ::BigcommerceOrderAgent::Client.const_get(class_name.to_sym)
172
+ return klass.new(
173
+ interpolated['store_hash'],
174
+ interpolated['client_id'],
175
+ interpolated['access_token']
176
+ )
177
+ end
178
+
179
+ end
180
+ end
@@ -0,0 +1,13 @@
1
+ # require 'rails_helper'
2
+ # require 'huginn_agent/spec_helper'
3
+ #
4
+ # describe Agents::BigcommerceOrderAgent do
5
+ # before(:each) do
6
+ # @valid_options = Agents::BigcommerceOrderAgent.new.default_options
7
+ # @checker = Agents::BigcommerceOrderAgent.new(:name => "BigcommerceOrderAgent", :options => @valid_options)
8
+ # @checker.user = users(:bob)
9
+ # @checker.save!
10
+ # end
11
+ #
12
+ # pending "add specs here"
13
+ # end
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: huginn_bigcommerce_order_agent
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - 5 Stones
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-03-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: huginn_agent
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: A no-frills agent to consolidate API requests and return BigCommerce
56
+ Order data as a single JSON object.
57
+ email:
58
+ - it@weare5stones.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - LICENSE.txt
64
+ - lib/client/abstract_client.rb
65
+ - lib/client/customer.rb
66
+ - lib/client/order.rb
67
+ - lib/errors/bigcommerce_api_error.rb
68
+ - lib/huginn_bigcommerce_order_agent.rb
69
+ - lib/huginn_bigcommerce_order_agent/bigcommerce_order_agent.rb
70
+ - spec/bigcommerce_order_agent_spec.rb
71
+ homepage: https://github.com/5-stones/huginn_bigcommerce_order_agent
72
+ licenses: []
73
+ metadata: {}
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubygems_version: 3.0.3
90
+ signing_key:
91
+ specification_version: 4
92
+ summary: Agent that fetches order data from BigCommerce.
93
+ test_files:
94
+ - spec/bigcommerce_order_agent_spec.rb