cornerstore 0.6.2
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 +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/README.md +75 -0
- data/Rakefile +1 -0
- data/cornerstore.gemspec +23 -0
- data/examples/cornerstore.yml +11 -0
- data/lib/cornerstore.rb +84 -0
- data/lib/cornerstore/api.rb +18 -0
- data/lib/cornerstore/api/address.rb +12 -0
- data/lib/cornerstore/api/cancellation.rb +22 -0
- data/lib/cornerstore/api/carrier.rb +3 -0
- data/lib/cornerstore/api/cart.rb +49 -0
- data/lib/cornerstore/api/collection.rb +25 -0
- data/lib/cornerstore/api/customer.rb +4 -0
- data/lib/cornerstore/api/differentiating_property.rb +15 -0
- data/lib/cornerstore/api/image.rb +20 -0
- data/lib/cornerstore/api/line_item.rb +74 -0
- data/lib/cornerstore/api/order.rb +94 -0
- data/lib/cornerstore/api/payment_means.rb +32 -0
- data/lib/cornerstore/api/price.rb +77 -0
- data/lib/cornerstore/api/product.rb +72 -0
- data/lib/cornerstore/api/property.rb +30 -0
- data/lib/cornerstore/api/search.rb +35 -0
- data/lib/cornerstore/api/shipment.rb +24 -0
- data/lib/cornerstore/api/variant.rb +49 -0
- data/lib/cornerstore/model.rb +117 -0
- data/lib/cornerstore/resource.rb +8 -0
- data/lib/cornerstore/resource/base.rb +83 -0
- data/lib/cornerstore/resource/filter.rb +21 -0
- data/lib/cornerstore/resource/remote.rb +27 -0
- data/lib/cornerstore/resource/writable.rb +20 -0
- data/lib/cornerstore/version.rb +3 -0
- data/spec/order_spec.rb +78 -0
- data/spec/price_spec.rb +51 -0
- data/spec/spec_helper.rb +22 -0
- metadata +126 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: dfc2a280092553731812a736e6e328ca98c4684a
|
4
|
+
data.tar.gz: 91b3b7e4604d61bf9de7f93f5e9eddfda886bffb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0dfd5351f44be439ac06b31fb00d62434affced7c7549f5007cfb14f8aa517f0afe02387e914ebbe0867d1c898913f788fbf77c6bb95d53c7596be294a94c402
|
7
|
+
data.tar.gz: d3eae73f7a1b60d331b2ec5172b09a9094b029f98cccaa13cb743d3c4fd91228c9ec22cb300855f93874ce4956cc8d6fe306fed7c22fd5e2ea6d9139d88161b5
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# Cornerstore
|
2
|
+
|
3
|
+
This is a client for the Cornerstore API-based online shop. Cornerstore allows you to create custom online shops
|
4
|
+
with your own front-end code, platform and tool belt choices. Instead of reinventing the wheel however, Cornerstore will
|
5
|
+
supply you with common e-commerce resources like products and carts via API. It also includes a checkout and Manager interface,
|
6
|
+
so your customers can manage their products and orders without you having to code yet another admin back-end.
|
7
|
+
|
8
|
+
Learn more about Cornerstore at http://www.cornerstore.io and http://addons.heroku.com/cornerstore
|
9
|
+
|
10
|
+
## Authentication
|
11
|
+
|
12
|
+
The gem tries to retrieve your Cornerstore credentials in the following order:
|
13
|
+
|
14
|
+
1. It checks if the env variable CORNERSTORE_URL is set and extracts the credentials. If you provisioned
|
15
|
+
with Heroku this variable is already configured.
|
16
|
+
|
17
|
+
2. If no env variable is available it checks the secrets.yml file on Rails 4.1 or looks for a
|
18
|
+
cornerstore.yml on previous versions. See examples/cornerstore.yml for an example.
|
19
|
+
|
20
|
+
3. You can always set the credentials directly by using the Cornerstore.subdomain= and Cornerstore.api_key=
|
21
|
+
methods.
|
22
|
+
|
23
|
+
## Getting products & collections
|
24
|
+
|
25
|
+
You can retrieve products from Cornerstore like so:
|
26
|
+
|
27
|
+
product = Cornerstore::Product.find('sugo-al-basilico')
|
28
|
+
product.manufacturer #=> "Fattoria Croccante"
|
29
|
+
|
30
|
+
product = Cornerstore::Product.find('5900338280000393023')
|
31
|
+
product.name #=> "Sugo Al Basilico"
|
32
|
+
|
33
|
+
collection = Cornerstore::Collection.find('spaghetti').child_collections.first
|
34
|
+
product = collection.products.first
|
35
|
+
product.variants.any? #=> true
|
36
|
+
product.variants.first.price.decimal_amount #=> 12.99
|
37
|
+
|
38
|
+
## Creating carts and adding line items
|
39
|
+
|
40
|
+
You can create carts and handle line items as state below. Line items can either be created from scratch or you can
|
41
|
+
derive them from a product/variant.
|
42
|
+
|
43
|
+
cart = Cornerstore::Cart.new({
|
44
|
+
success_redirect_url: "http://yourshop.com/cart/success",
|
45
|
+
cart_url: "http://yourshop.com/cart"
|
46
|
+
})
|
47
|
+
|
48
|
+
product = Cornerstore::Product.find('sugo-al-basilico')
|
49
|
+
variant = product.variants.find('SBS-39993')
|
50
|
+
|
51
|
+
# Derive from variant
|
52
|
+
cart.line_items.create_from_variant(variant, qty: 10)
|
53
|
+
|
54
|
+
line_item = cart.line_items.first
|
55
|
+
line_item.qty = 15
|
56
|
+
line_item.save
|
57
|
+
|
58
|
+
another_line_item = cart.line_items.last
|
59
|
+
another_line_item.destroy
|
60
|
+
|
61
|
+
# Create a line item directly
|
62
|
+
line_item = Cornerstore::LineItem.create({
|
63
|
+
qty: 10,
|
64
|
+
description: 'My own custom line item',
|
65
|
+
unit: 'Piece',
|
66
|
+
weight: 0.5,
|
67
|
+
price: Cornerstore::Price.new(12.99, 'USD')
|
68
|
+
})
|
69
|
+
|
70
|
+
## Learn more
|
71
|
+
|
72
|
+
We provide a full API documentation at http://developer.cornerstore.io/api, including many ruby examples. Generally every
|
73
|
+
attribute that is listed in the API docs corresponds to a ruby method that can be called on the respective objects. In
|
74
|
+
many cases there are additional methods available, just check out the classes of this gem and our Rails example store
|
75
|
+
and boilerplate at https://github.com/crispymtn/fattoria-croccante.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/cornerstore.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'cornerstore/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "cornerstore"
|
8
|
+
gem.version = Cornerstore::VERSION
|
9
|
+
gem.authors = ['Johannes Treitz', 'Christian Weyer']
|
10
|
+
gem.email = ['jt@crispymtn.com', 'cw@crispymtn.com']
|
11
|
+
gem.description = "This is a client for the Cornerstore e-commerce API"
|
12
|
+
gem.summary = "This is a client for the Cornerstore e-commerce API"
|
13
|
+
gem.homepage = "https://www.github.com/crispymtn/cornerstore-gem"
|
14
|
+
gem.files = `git ls-files`.split($/)
|
15
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
16
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
17
|
+
gem.require_paths = ["lib"]
|
18
|
+
|
19
|
+
gem.add_dependency 'rest-client'
|
20
|
+
gem.add_dependency 'activemodel'
|
21
|
+
gem.add_development_dependency "rspec"
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
development:
|
2
|
+
subdomain: your-shop-staging
|
3
|
+
api_key: dffe7d7382199992dfe # You find the API keys in the Cornerstore Manager under Settings > API
|
4
|
+
|
5
|
+
test:
|
6
|
+
subdomain: your-shop-staging
|
7
|
+
api_key: dffe7d7382199992dfe
|
8
|
+
|
9
|
+
production:
|
10
|
+
subdomain: your-shop
|
11
|
+
api_key: deee636888117727782
|
data/lib/cornerstore.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
require 'rest_client'
|
3
|
+
|
4
|
+
require 'cornerstore/version'
|
5
|
+
require 'cornerstore/resource'
|
6
|
+
require 'cornerstore/model'
|
7
|
+
require 'cornerstore/api'
|
8
|
+
|
9
|
+
module RestClient::AbstractResponse
|
10
|
+
def success?
|
11
|
+
(200..207).include? code
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
RestClient.log = 'stdout'
|
16
|
+
|
17
|
+
module Cornerstore
|
18
|
+
class << self
|
19
|
+
attr_writer :subdomain, :api_key
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.subdomain
|
23
|
+
@subdomain ||= read_config[:subdomain]
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.root_url
|
27
|
+
"https://#{subdomain}.cornerstore.io/api/v1"
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.assets_url
|
31
|
+
"http://cskit-production.s3.amazonaws.com"
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.headers
|
35
|
+
{
|
36
|
+
user_agent: "cornerstore-gem/#{Cornerstore::VERSION}",
|
37
|
+
authorization: "Token #{api_key}"
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
# We have three levels of configuration
|
43
|
+
# - Can be manually set with accessors on this module
|
44
|
+
# - Can be supplied by Rails secret file
|
45
|
+
# - Can be supplied by a cornerstore yml file
|
46
|
+
# - Can be supplied via CORNERSTORE_URL env variable
|
47
|
+
def self.read_config
|
48
|
+
config = {}
|
49
|
+
|
50
|
+
# ENV variable set beats everything. If we got one we use it regardless
|
51
|
+
# of other config options
|
52
|
+
if ENV.has_key?('CORNERSTORE_URL') and uri = URI.parse(ENV['CORNERSTORE_URL'])
|
53
|
+
config[:subdomain] = uri.host.sub('.cornerstore.io', '')
|
54
|
+
config[:api_key] = uri.password
|
55
|
+
|
56
|
+
else
|
57
|
+
# Is this Rails?
|
58
|
+
if defined?(Rails)
|
59
|
+
# Try to get the credentials from the secret storage,
|
60
|
+
# if that fails try to read the YAML file
|
61
|
+
if not (Rails.application.respond_to?(:secrets) and
|
62
|
+
config[:subdomain] = Rails.application.secrets.cornerstore_subdomain and
|
63
|
+
config[:api_key] = Rails.application.secrets.cornerstore_api_key)
|
64
|
+
|
65
|
+
yml_path = Rails.root.join('config', 'cornerstore.yml')
|
66
|
+
|
67
|
+
if File.exists?(yml_path) and yaml = YAML.load(File.read(yml_path))
|
68
|
+
config[:subdomain] = yaml[Rails.env]['subdomain']
|
69
|
+
config[:api_key] = yaml[Rails.env]['api_key']
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
else
|
74
|
+
raise ArgumentError, 'Could not find any Cornerstore credentials, please set them before proceeding'
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
return config
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.api_key
|
82
|
+
@api_ley ||= read_config[:api_key]
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'cornerstore/api/product'
|
2
|
+
require 'cornerstore/api/variant'
|
3
|
+
require 'cornerstore/api/price'
|
4
|
+
require 'cornerstore/api/property'
|
5
|
+
require 'cornerstore/api/differentiating_property'
|
6
|
+
require 'cornerstore/api/image'
|
7
|
+
require 'cornerstore/api/collection'
|
8
|
+
require 'cornerstore/api/cart'
|
9
|
+
require 'cornerstore/api/line_item'
|
10
|
+
require 'cornerstore/api/search'
|
11
|
+
|
12
|
+
require 'cornerstore/api/order'
|
13
|
+
require 'cornerstore/api/payment_means'
|
14
|
+
require 'cornerstore/api/cancellation'
|
15
|
+
require 'cornerstore/api/shipment'
|
16
|
+
require 'cornerstore/api/carrier'
|
17
|
+
require 'cornerstore/api/address'
|
18
|
+
require 'cornerstore/api/customer'
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Cornerstore::Cancellation < Cornerstore::Model::Base
|
2
|
+
attr_accessor :created_at,
|
3
|
+
:line_item_ids
|
4
|
+
|
5
|
+
alias_method :canceled_at, :created_at
|
6
|
+
|
7
|
+
def initialize(attributes = {}, parent=nil)
|
8
|
+
self.line_item_ids = attributes.delete('canceled_items')
|
9
|
+
self.created_at = DateTime.parse(attributes.delete('created_at')) unless attributes['created_at'].blank?
|
10
|
+
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def line_items
|
15
|
+
return [] unless self.parent
|
16
|
+
self.parent.line_items.select { |li| self.line_item_ids.include?(li.id) }
|
17
|
+
end
|
18
|
+
alias_method :canceled_items, :line_items
|
19
|
+
|
20
|
+
class Resource < Cornerstore::Resource::Base
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class Cornerstore::Cart < Cornerstore::Model::Base
|
2
|
+
include Cornerstore::Model::Writable
|
3
|
+
|
4
|
+
attr_accessor :line_items, :reference, :total, :success_redirect_url, :cart_url, :invoice_pdf_callback_url,
|
5
|
+
:delivery_note_pdf_callback_url, :placed_email_callback_url, :shipped_email_callback_url, :paid_email_callback_url, :canceled_email_callback_url
|
6
|
+
|
7
|
+
def initialize(attributes = {}, parent=nil)
|
8
|
+
self.total = Cornerstore::Price.new(attributes.delete('total'))
|
9
|
+
self.line_items = Cornerstore::LineItem::Resource.new(self, attributes.delete('line_items') || [], 'line_items')
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def id
|
14
|
+
reference
|
15
|
+
end
|
16
|
+
alias to_param id
|
17
|
+
|
18
|
+
def attributes
|
19
|
+
{
|
20
|
+
reference: reference,
|
21
|
+
success_redirect_url: success_redirect_url,
|
22
|
+
cart_url: cart_url,
|
23
|
+
invoice_pdf_callback_url: invoice_pdf_callback_url,
|
24
|
+
delivery_note_pdf_callback_url: delivery_note_pdf_callback_url,
|
25
|
+
placed_email_callback_url: placed_email_callback_url,
|
26
|
+
shipped_email_callback_url: shipped_email_callback_url,
|
27
|
+
paid_email_callback_url: paid_email_callback_url,
|
28
|
+
canceled_email_callback_url: canceled_email_callback_url
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def empty!
|
33
|
+
line_items.delete_all
|
34
|
+
line_items.empty?
|
35
|
+
end
|
36
|
+
|
37
|
+
def empty?
|
38
|
+
line_items.empty?
|
39
|
+
end
|
40
|
+
|
41
|
+
def checkout_url
|
42
|
+
"https://#{Cornerstore.subdomain}.cornerstore.io/checkout/#{self.reference}"
|
43
|
+
end
|
44
|
+
|
45
|
+
class Resource < Cornerstore::Resource::Base
|
46
|
+
include Cornerstore::Resource::Remote
|
47
|
+
include Cornerstore::Resource::Writable
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Cornerstore::Collection < Cornerstore::Model::Base
|
2
|
+
attr_accessor :name,
|
3
|
+
:parent,
|
4
|
+
:members,
|
5
|
+
:childs,
|
6
|
+
:products,
|
7
|
+
:properties
|
8
|
+
|
9
|
+
def initialize(attributes = {}, parent = nil)
|
10
|
+
self.products = Cornerstore::Product::Resource.new(self)
|
11
|
+
self.childs = Cornerstore::Collection::Resource.new(self, attributes.delete('child_collections') || [], 'childs')
|
12
|
+
self.properties = Cornerstore::Property::Resource.new(self, attributes.delete('properties') || [])
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def attributes
|
17
|
+
{
|
18
|
+
name: name
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
class Resource < Cornerstore::Resource::Base
|
23
|
+
include Cornerstore::Resource::Remote
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Cornerstore::DifferentiatingProperty < Cornerstore::Model::Base
|
2
|
+
attr_accessor :key,
|
3
|
+
:value
|
4
|
+
|
5
|
+
def attributes
|
6
|
+
{
|
7
|
+
hide_from: hide_from,
|
8
|
+
key: key,
|
9
|
+
value: value
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
class Resource < Cornerstore::Resource::Base
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Cornerstore::Image < Cornerstore::Model::Base
|
2
|
+
attr_accessor :cover,
|
3
|
+
:size,
|
4
|
+
:format,
|
5
|
+
:height,
|
6
|
+
:width,
|
7
|
+
:key
|
8
|
+
|
9
|
+
|
10
|
+
alias content_type format
|
11
|
+
alias file_size size
|
12
|
+
|
13
|
+
# small, small_square, medium, medium_square, large
|
14
|
+
def url(w = 600, h = 600, mode = 'crop')
|
15
|
+
"http://res.cloudinary.com/hgzhd1stm/image/upload/c_#{mode},h_#{h},w_#{w}/#{self.key}"
|
16
|
+
end
|
17
|
+
|
18
|
+
class Resource < Cornerstore::Resource::Base
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
class Cornerstore::LineItem < Cornerstore::Model::Base
|
2
|
+
include Cornerstore::Model::Writable
|
3
|
+
|
4
|
+
attr_accessor :order_number,
|
5
|
+
:description,
|
6
|
+
:qty,
|
7
|
+
:unit,
|
8
|
+
:price,
|
9
|
+
:total,
|
10
|
+
:weight,
|
11
|
+
:properties,
|
12
|
+
:product,
|
13
|
+
:variant
|
14
|
+
|
15
|
+
alias cart parent
|
16
|
+
|
17
|
+
validates :order_number, length: { within: 1..50 }
|
18
|
+
validates :description, length: { within: 1..255 }
|
19
|
+
validates :qty, numericality: { greater_than: 0, only_integer: true }
|
20
|
+
validates :unit, length: { within: 1..20 }
|
21
|
+
validates :price, presence: true
|
22
|
+
validates :weight, numericality: { greater_than: 0, allow_nil: true }
|
23
|
+
validate do
|
24
|
+
errors.add(:price, 'Price must be valid') unless price.valid?
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(attributes = {}, parent = nil)
|
28
|
+
self.price = Cornerstore::Price.new(attributes.delete('price'))
|
29
|
+
self.total = Cornerstore::Price.new(attributes.delete('total'))
|
30
|
+
self.properties = Cornerstore::Property::Resource.new(self, attributes.delete('properties') || [])
|
31
|
+
self.product = Cornerstore::Product.new(attributes.delete('product')) if attributes['product']
|
32
|
+
self.variant = Cornerstore::Variant.new(attributes.delete('variant'), self.product) if attributes['variant']
|
33
|
+
super
|
34
|
+
end
|
35
|
+
|
36
|
+
def attributes
|
37
|
+
{
|
38
|
+
order_number: order_number,
|
39
|
+
description: description,
|
40
|
+
qty: qty,
|
41
|
+
unit: unit,
|
42
|
+
price: price.attributes,
|
43
|
+
weight: weight
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
class Resource < Cornerstore::Resource::Base
|
48
|
+
include Cornerstore::Resource::Remote
|
49
|
+
include Cornerstore::Resource::Writable
|
50
|
+
|
51
|
+
def create_from_variant(variant, attr={})
|
52
|
+
attributes = {
|
53
|
+
variant_id: variant.id,
|
54
|
+
product_id: variant.product._id,
|
55
|
+
line_item: attr
|
56
|
+
}
|
57
|
+
|
58
|
+
RestClient.post("#{Cornerstore.root_url}/carts/#{@parent.id}/line_items/derive.json", attributes, Cornerstore.headers) do |response, request, result, &block|
|
59
|
+
if response.code == 201
|
60
|
+
attributes = ActiveSupport::JSON.decode(response)
|
61
|
+
line_item = @klass.new(attributes, @parent)
|
62
|
+
elsif response.code == 422 and data = ActiveSupport::JSON.decode(response) and data.has_key?('errors')
|
63
|
+
line_item = @klass.new(attr, @parent)
|
64
|
+
data['errors'].each_pair do |key, messages|
|
65
|
+
messages.map { |message| line_item.errors.add(key, message) }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
push line_item
|
70
|
+
line_item
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|