ecwid_api 0.0.2 → 0.2.3
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 +5 -5
- data/.travis.yml +8 -0
- data/README.md +123 -31
- data/ecwid_api.gemspec +10 -8
- data/lib/ecwid_api.rb +18 -29
- data/lib/ecwid_api/api.rb +12 -0
- data/lib/ecwid_api/api/base.rb +18 -0
- data/lib/ecwid_api/api/categories.rb +56 -0
- data/lib/ecwid_api/api/customers.rb +53 -0
- data/lib/ecwid_api/api/orders.rb +36 -0
- data/lib/ecwid_api/api/product_combinations.rb +48 -0
- data/lib/ecwid_api/api/product_types.rb +56 -0
- data/lib/ecwid_api/api/products.rb +148 -0
- data/lib/ecwid_api/category.rb +53 -4
- data/lib/ecwid_api/client.rb +65 -58
- data/lib/ecwid_api/customer.rb +10 -0
- data/lib/ecwid_api/entity.rb +151 -29
- data/lib/ecwid_api/error.rb +10 -0
- data/lib/ecwid_api/o_auth.rb +106 -0
- data/lib/ecwid_api/order.rb +118 -0
- data/lib/ecwid_api/order_item.rb +17 -0
- data/lib/ecwid_api/paged_ecwid_response.rb +57 -0
- data/lib/ecwid_api/paged_enumerator.rb +66 -0
- data/lib/ecwid_api/person.rb +7 -0
- data/lib/ecwid_api/product.rb +65 -0
- data/lib/ecwid_api/product_combination.rb +30 -0
- data/lib/ecwid_api/product_type.rb +18 -0
- data/lib/ecwid_api/product_type_attribute.rb +27 -0
- data/lib/ecwid_api/unpaged_ecwid_response.rb +38 -0
- data/lib/ecwid_api/version.rb +1 -1
- data/lib/ext/string.rb +9 -1
- data/spec/api/categories_spec.rb +31 -0
- data/spec/api/customers_spec.rb +20 -0
- data/spec/api/orders_spec.rb +30 -0
- data/spec/api/product_types_spec.rb +20 -0
- data/spec/api/products_spec.rb +20 -0
- data/spec/category_spec.rb +1 -6
- data/spec/client_spec.rb +4 -32
- data/spec/entity_spec.rb +120 -8
- data/spec/fixtures/categories.json +28 -22
- data/spec/fixtures/classes.json +44 -0
- data/spec/fixtures/customers.json +48 -0
- data/spec/fixtures/order.json +162 -0
- data/spec/fixtures/orders.json +303 -0
- data/spec/fixtures/products.json +141 -0
- data/spec/helpers/client.rb +34 -0
- data/spec/oauth_spec.rb +40 -0
- data/spec/order_item_spec.rb +12 -0
- data/spec/order_spec.rb +71 -0
- data/spec/paged_enumerator_spec.rb +38 -0
- data/spec/spec_helper.rb +3 -3
- metadata +93 -37
- data/lib/ecwid_api/category_api.rb +0 -62
- data/spec/category_api_spec.rb +0 -36
- data/spec/ecwid_api_spec.rb +0 -15
- data/spec/helpers/faraday.rb +0 -30
data/lib/ecwid_api/entity.rb
CHANGED
@@ -1,12 +1,87 @@
|
|
1
1
|
module EcwidApi
|
2
2
|
class Entity
|
3
|
-
# Private: Gets the
|
3
|
+
# Private: Gets the Hash of data
|
4
|
+
attr_reader :data, :new_data
|
5
|
+
protected :data, :new_data
|
6
|
+
|
4
7
|
attr_reader :client
|
5
|
-
private
|
8
|
+
private :client
|
6
9
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
+
class << self
|
11
|
+
attr_accessor :url_root
|
12
|
+
|
13
|
+
def define_accessor(attribute, &block)
|
14
|
+
if const_defined?(:Accessors, false)
|
15
|
+
mod = const_get(:Accessors)
|
16
|
+
else
|
17
|
+
mod = const_set(:Accessors, Module.new)
|
18
|
+
include mod
|
19
|
+
end
|
20
|
+
|
21
|
+
mod.module_eval do
|
22
|
+
define_method(attribute, &block)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private :define_accessor
|
27
|
+
|
28
|
+
# Public: Creates a snake_case access method from an Ecwid property name
|
29
|
+
#
|
30
|
+
# Example
|
31
|
+
#
|
32
|
+
# class Product < Entity
|
33
|
+
# ecwid_reader :id, :inStock
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# product = client.products.find(12)
|
37
|
+
# product.in_stock
|
38
|
+
#
|
39
|
+
def ecwid_reader(*attrs)
|
40
|
+
attrs.map(&:to_s).each do |attribute|
|
41
|
+
method = attribute.underscore
|
42
|
+
define_accessor(method) do
|
43
|
+
self[attribute]
|
44
|
+
end unless method_defined?(attribute.underscore)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Public: Creates a snake_case writer method from an Ecwid property name
|
49
|
+
#
|
50
|
+
# Example
|
51
|
+
#
|
52
|
+
# class Product < Entity
|
53
|
+
# ecwid_writer :inStock
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# product = client.products.find(12)
|
57
|
+
# product.in_stock = true
|
58
|
+
#
|
59
|
+
def ecwid_writer(*attrs)
|
60
|
+
attrs.map(&:to_s).each do |attribute|
|
61
|
+
method = "#{attribute.underscore}="
|
62
|
+
define_accessor(method) do |value|
|
63
|
+
@new_data[attribute] = value
|
64
|
+
end unless method_defined?(method)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Public: Creates a snake_case accessor method from an Ecwid property name
|
69
|
+
#
|
70
|
+
# Example
|
71
|
+
#
|
72
|
+
# class Product < Entity
|
73
|
+
# ecwid_accessor :inStock
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# product = client.products.find(12)
|
77
|
+
# product.in_stock
|
78
|
+
# product.in_stock = true
|
79
|
+
#
|
80
|
+
def ecwid_accessor(*attrs)
|
81
|
+
ecwid_reader(*attrs)
|
82
|
+
ecwid_writer(*attrs)
|
83
|
+
end
|
84
|
+
end
|
10
85
|
|
11
86
|
# Public: Initialize a new entity with a reference to the client and data
|
12
87
|
#
|
@@ -16,6 +91,7 @@ module EcwidApi
|
|
16
91
|
#
|
17
92
|
def initialize(data, opts={})
|
18
93
|
@client, @data = opts[:client], data
|
94
|
+
@new_data = {}
|
19
95
|
end
|
20
96
|
|
21
97
|
# Public: Returns a property of the data (actual property name)
|
@@ -31,37 +107,83 @@ module EcwidApi
|
|
31
107
|
#
|
32
108
|
# Returns the value of the property, or nil if it doesn't exist
|
33
109
|
def [](key)
|
34
|
-
data[key.to_s]
|
110
|
+
@new_data[key.to_s] || data[key.to_s]
|
35
111
|
end
|
36
112
|
|
37
|
-
# Public:
|
113
|
+
# Public: The URL of the entity
|
38
114
|
#
|
39
|
-
#
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
115
|
+
# Returns a String that is the URL of the entity
|
116
|
+
def url
|
117
|
+
url_root = self.class.url_root
|
118
|
+
raise Error.new("Please specify a url_root for the #{self.class.to_s}") unless url_root
|
119
|
+
|
120
|
+
if url_root.respond_to?(:call)
|
121
|
+
url_root = instance_exec(&url_root)
|
122
|
+
end
|
123
|
+
|
124
|
+
url_root + "/#{id}"
|
125
|
+
end
|
126
|
+
|
127
|
+
def assign_attributes(attributes)
|
128
|
+
attributes.each do |key, val|
|
129
|
+
send("#{key}=", val)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def assign_raw_attributes(attributes)
|
134
|
+
attributes.each do |key, val|
|
135
|
+
@new_data[key.to_s] = val
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def update_attributes(attributes)
|
140
|
+
assign_attributes(attributes)
|
141
|
+
save
|
142
|
+
end
|
143
|
+
|
144
|
+
def update_raw_attributes(attributes)
|
145
|
+
assign_raw_attributes(attributes)
|
146
|
+
save
|
147
|
+
end
|
148
|
+
|
149
|
+
# Public: Saves the Entity
|
46
150
|
#
|
47
|
-
#
|
151
|
+
# Saves anything stored in the @new_data hash
|
48
152
|
#
|
49
|
-
#
|
50
|
-
# return a property if it doesn't have a null value. An example of this are
|
51
|
-
# the top level categories. They don't have a parentId, so that property
|
52
|
-
# is ommitted from the API response. Calling `category.parent_id` will
|
53
|
-
# result in an "undefined method `parent_id'". However, calling `#parent_id`
|
54
|
-
# on any other category will work.
|
153
|
+
# path - the URL of the entity
|
55
154
|
#
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
155
|
+
def save
|
156
|
+
unless @new_data.empty?
|
157
|
+
client.put(url, @new_data).tap do |response|
|
158
|
+
@data.merge!(@new_data)
|
159
|
+
@new_data.clear
|
160
|
+
end
|
62
161
|
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# Public: Destroys the Entity
|
165
|
+
def destroy!
|
166
|
+
client.delete(url)
|
167
|
+
end
|
168
|
+
|
169
|
+
def to_hash
|
170
|
+
data
|
171
|
+
end
|
172
|
+
|
173
|
+
def to_json(*args)
|
174
|
+
data.to_json(*args)
|
175
|
+
end
|
176
|
+
|
177
|
+
def marshal_dump
|
178
|
+
[@data, @new_data]
|
179
|
+
end
|
180
|
+
|
181
|
+
def marshal_load(array)
|
182
|
+
@data, @new_data = *array
|
183
|
+
end
|
63
184
|
|
64
|
-
|
185
|
+
def ==(other)
|
186
|
+
data == other.data && new_data == other.new_data
|
65
187
|
end
|
66
188
|
end
|
67
|
-
end
|
189
|
+
end
|
data/lib/ecwid_api/error.rb
CHANGED
@@ -1,3 +1,13 @@
|
|
1
1
|
module EcwidApi
|
2
2
|
class Error < StandardError; end;
|
3
|
+
|
4
|
+
class ResponseError < Error
|
5
|
+
def initialize(response)
|
6
|
+
if response.respond_to?(:reason_phrase)
|
7
|
+
super "#{response.reason_phrase} (#{response.status})\n#{response.body}"
|
8
|
+
else
|
9
|
+
super "The Ecwid API responded with an error (#{response.status})"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
3
13
|
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require "cgi"
|
2
|
+
require "ostruct"
|
3
|
+
|
4
|
+
module EcwidApi
|
5
|
+
# Public: Authentication objects manage OAuth authentication with an Ecwid
|
6
|
+
# store.
|
7
|
+
#
|
8
|
+
# Examples
|
9
|
+
#
|
10
|
+
# app = EcwidApi::Authentication.new do |config|
|
11
|
+
# # see initialize for configuration
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# app.oauth_url # send the user here to authorize the app
|
15
|
+
#
|
16
|
+
# token = app.access_token(params[:code]) # this is the code they provide
|
17
|
+
# # to the redirect_uri
|
18
|
+
# token.access_token
|
19
|
+
# token.store_id # these are what you need to access the API
|
20
|
+
#
|
21
|
+
class OAuth
|
22
|
+
CONFIG = %w(client_id client_secret scope redirect_uri)
|
23
|
+
attr_accessor *CONFIG
|
24
|
+
|
25
|
+
# Public: Initializes a new Ecwid Authentication for OAuth
|
26
|
+
#
|
27
|
+
# Examples
|
28
|
+
#
|
29
|
+
# app = EcwidApi::Authentication.new do |config|
|
30
|
+
# config.client_id = "some client id"
|
31
|
+
# config.client_secret = "some client secret"
|
32
|
+
# config.scope "this_is_what_i_want_to_do oh_and_that_too"
|
33
|
+
# config.redirect_uri = "https://example.com/oauth"
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
def initialize
|
37
|
+
yield(self) if block_given?
|
38
|
+
CONFIG.each do |method|
|
39
|
+
raise Error.new("#{method} is required to initialize a new EcwidApi::Authentication") unless send(method)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Public: The URL for OAuth authorization.
|
44
|
+
#
|
45
|
+
# This is the URL that the user will need to go to to authorize the app
|
46
|
+
# with the Ecwid store.
|
47
|
+
#
|
48
|
+
def oauth_url
|
49
|
+
"https://my.ecwid.com/api/oauth/authorize?" + oauth_query
|
50
|
+
end
|
51
|
+
|
52
|
+
# Public: Obtain the access token in order to use the API
|
53
|
+
#
|
54
|
+
# code - the temporary code obtained from the authorization callback
|
55
|
+
#
|
56
|
+
# Examples
|
57
|
+
#
|
58
|
+
# token = app.access_token(params[:code])
|
59
|
+
# token.access_token # the access token that authenticates each API request
|
60
|
+
# token.store_id # the authenticated Ecwid store_id
|
61
|
+
#
|
62
|
+
# Returns an OpenStruct which responds with the information needed to
|
63
|
+
# access the API for a store.
|
64
|
+
def access_token(code)
|
65
|
+
response = connection.post("/api/oauth/token",
|
66
|
+
client_id: client_id,
|
67
|
+
client_secret: client_secret,
|
68
|
+
code: code,
|
69
|
+
redirect_uri: redirect_uri,
|
70
|
+
grant_type: "authorization_code"
|
71
|
+
)
|
72
|
+
|
73
|
+
if response.success?
|
74
|
+
OpenStruct.new(response.body)
|
75
|
+
else
|
76
|
+
raise Error.new(response.body["error_description"])
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
# Private: The query parameters for the OAuth authorization request
|
83
|
+
#
|
84
|
+
# Returns a String of query parameters
|
85
|
+
def oauth_query
|
86
|
+
{
|
87
|
+
client_id: client_id,
|
88
|
+
scope: scope,
|
89
|
+
response_type: "code",
|
90
|
+
redirect_uri: redirect_uri
|
91
|
+
}.map do |key, val|
|
92
|
+
"#{CGI.escape(key.to_s)}=#{CGI.escape(val.to_s)}"
|
93
|
+
end.join(?&)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Private: Returns a connection for obtaining an access token from Ecwid
|
97
|
+
#
|
98
|
+
def connection
|
99
|
+
@connection ||= Faraday.new "https://my.ecwid.com" do |conn|
|
100
|
+
conn.request :url_encoded
|
101
|
+
conn.response :json, content_type: /\bjson$/
|
102
|
+
conn.adapter Faraday.default_adapter
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module EcwidApi
|
2
|
+
# Public: This is an Ecwid Order
|
3
|
+
class Order < Entity
|
4
|
+
self.url_root = "orders"
|
5
|
+
|
6
|
+
ecwid_reader :id, :orderNumber, :vendorOrderNumber, :subtotal, :total, :email,
|
7
|
+
:paymentMethod, :paymentModule, :tax, :ipAddress,
|
8
|
+
:couponDiscount, :paymentStatus, :fulfillmentStatus,
|
9
|
+
:refererUrl, :orderComments, :volumeDiscount, :customerId,
|
10
|
+
:membershipBasedDiscount, :totalAndMembershipBasedDiscount,
|
11
|
+
:discount, :usdTotal, :globalReferer, :createDate, :updateDate,
|
12
|
+
:customerGroup, :discountCoupon, :items, :billingPerson,
|
13
|
+
:shippingPerson, :shippingOption, :additionalInfo,
|
14
|
+
:paymentParams, :discountInfo, :trackingNumber,
|
15
|
+
:paymentMessage, :extTransactionId, :affiliateId,
|
16
|
+
:creditCardStatus, :handlingFee
|
17
|
+
|
18
|
+
|
19
|
+
ecwid_writer :subtotal, :total, :email, :paymentMethod, :paymentModule,
|
20
|
+
:tax, :ipAddress, :couponDiscount, :paymentStatus,
|
21
|
+
:fulfillmentStatus, :refererUrl, :orderComments,
|
22
|
+
:volumeDiscount, :customerId, :membershipBasedDiscount,
|
23
|
+
:totalAndMembershipBasedDiscount, :discount, :globalReferer,
|
24
|
+
:createDate, :updateDate, :customerGroup, :discountCoupon,
|
25
|
+
:items, :billingPerson, :shippingPerson, :shippingOption,
|
26
|
+
:additionalInfo, :paymentParams, :discountInfo,
|
27
|
+
:trackingNumber, :paymentMessage, :extTransactionId,
|
28
|
+
:affiliateId, :creditCardStatus, :handlingFee
|
29
|
+
|
30
|
+
VALID_FULFILLMENT_STATUSES = %w(
|
31
|
+
AWAITING_PROCESSING
|
32
|
+
PROCESSING
|
33
|
+
SHIPPED
|
34
|
+
DELIVERED
|
35
|
+
WILL_NOT_DELIVER
|
36
|
+
RETURNED
|
37
|
+
READY_FOR_PICKUP
|
38
|
+
OUT_FOR_DELIVERY
|
39
|
+
).freeze
|
40
|
+
|
41
|
+
VALID_PAYMENT_STATUSES = %w(
|
42
|
+
AWAITING_PAYMENT
|
43
|
+
PAID
|
44
|
+
CANCELLED
|
45
|
+
REFUNDED
|
46
|
+
PARTIALLY_REFUNDED
|
47
|
+
INCOMPLETE
|
48
|
+
).freeze
|
49
|
+
|
50
|
+
# @deprecated Please use {#id} instead
|
51
|
+
def vendor_order_number
|
52
|
+
warn "[DEPRECATION] `vendor_order_number` is deprecated. Please use `id` instead."
|
53
|
+
id
|
54
|
+
end
|
55
|
+
|
56
|
+
# @deprecated Please use {#id} instead
|
57
|
+
def order_number
|
58
|
+
warn "[DEPRECATION] `order_number` is deprecated. Please use `id` instead."
|
59
|
+
id
|
60
|
+
end
|
61
|
+
|
62
|
+
# Public: Returns the billing person
|
63
|
+
#
|
64
|
+
# If there isn't a billing_person, then it assumed to be the shipping_person
|
65
|
+
#
|
66
|
+
def billing_person
|
67
|
+
build_billing_person || build_shipping_person
|
68
|
+
end
|
69
|
+
|
70
|
+
# Public: Returns the shipping person
|
71
|
+
#
|
72
|
+
# If there isn't a shipping_person, then it is assumed to be the
|
73
|
+
# billing_person
|
74
|
+
#
|
75
|
+
def shipping_person
|
76
|
+
build_shipping_person || build_billing_person
|
77
|
+
end
|
78
|
+
|
79
|
+
# Public: Returns a Array of `OrderItem` objects
|
80
|
+
def items
|
81
|
+
@items ||= data["items"].map { |item| OrderItem.new(item) }
|
82
|
+
end
|
83
|
+
|
84
|
+
def fulfillment_status=(status)
|
85
|
+
status = status.to_s.upcase
|
86
|
+
unless VALID_FULFILLMENT_STATUSES.include?(status)
|
87
|
+
raise Error("#{status} is an invalid fullfillment status")
|
88
|
+
end
|
89
|
+
super(status)
|
90
|
+
end
|
91
|
+
|
92
|
+
def fulfillment_status
|
93
|
+
super && super.downcase.to_sym
|
94
|
+
end
|
95
|
+
|
96
|
+
def payment_status=(status)
|
97
|
+
status = status.to_s.upcase
|
98
|
+
unless VALID_PAYMENT_STATUSES.include?(status)
|
99
|
+
raise Error("#{status} is an invalid payment status")
|
100
|
+
end
|
101
|
+
super(status)
|
102
|
+
end
|
103
|
+
|
104
|
+
def payment_status
|
105
|
+
super && super.downcase.to_sym
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def build_billing_person
|
111
|
+
@billing_person ||= data["billingPerson"] && Person.new(data["billingPerson"])
|
112
|
+
end
|
113
|
+
|
114
|
+
def build_shipping_person
|
115
|
+
@shipping_person ||= data["shippingPerson"] && Person.new(data["shippingPerson"])
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|