ecwid_api 0.0.2 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|