cornerstore 0.6.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|