yodel_shop 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Rakefile +1 -0
- data/lib/migrations/01_shop_product_model.rb +16 -0
- data/lib/migrations/02_shop_user_updates.rb +13 -0
- data/lib/migrations/03_shop_transaction_model.rb +52 -0
- data/lib/migrations/04_shop_cart_model.rb +30 -0
- data/lib/migrations/05_shop_product_hold_model.rb +15 -0
- data/lib/migrations/06_shop_coupon_model.rb +18 -0
- data/lib/migrations/07_shop_coupon_redemption_page_model.rb +12 -0
- data/lib/migrations/08_shop_model_functions.rb +37 -0
- data/lib/migrations/09_shop_cart_duration_task.rb +17 -0
- data/lib/migrations/10_shop_coupon_restrictions.rb +21 -0
- data/lib/migrations/11_shop_discount_model.rb +19 -0
- data/lib/models/cart.rb +15 -0
- data/lib/models/cart_page.rb +65 -0
- data/lib/models/coupon_redemption_page.rb +44 -0
- data/lib/models/product_hold.rb +7 -0
- data/lib/models/transaction_page.rb +138 -0
- data/lib/yodel_shop.rb +3 -0
- data/yodel_shop.gemspec +22 -0
- metadata +66 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class ShopProductModelMigration < Migration
|
2
|
+
def self.up(site)
|
3
|
+
site.pages.create_model :products do |products|
|
4
|
+
add_field :price, :decimal, validations: {required: {}}, default: '0.0'
|
5
|
+
add_field :shipping, :decimal, validations: {required: {}}, default: '0.0'
|
6
|
+
add_field :tax, :decimal, validations: {required: {}}, default: '0.0'
|
7
|
+
add_field :unlimited_quantity, :boolean, validations: {required: {}}, default: false
|
8
|
+
add_field :quantity, :integer, validations: {required: {}}, default: 0, index: true
|
9
|
+
add_many :holds, model: :product_hold
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.down(site)
|
14
|
+
site.products.destroy
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class ShopUserUpdatesMigration < Migration
|
2
|
+
def self.up(site)
|
3
|
+
site.users.modify do
|
4
|
+
add_field :balance, :decimal, validations: {required: {}}, default: '0.0'
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.down(site)
|
9
|
+
site.users.modify do
|
10
|
+
remove_field :balance
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
class ShopTransactionModelMigration < Migration
|
2
|
+
def self.up(site)
|
3
|
+
site.records.create_model :transactions do |transactions|
|
4
|
+
add_one :cart
|
5
|
+
add_field :product_total, :decimal, default: '0.0', validations: {required: {}}
|
6
|
+
add_field :shipping_total, :decimal, default: '0.0', validations: {required: {}}
|
7
|
+
add_field :tax_total, :decimal, default: '0.0', validations: {required: {}}
|
8
|
+
add_field :total, :function, fn: 'sum(product_total, shipping_total, tax_total)'
|
9
|
+
add_field :created_at, :time
|
10
|
+
|
11
|
+
add_embed_one :shipping_address do
|
12
|
+
add_field :name, :string
|
13
|
+
add_field :address, :string
|
14
|
+
add_field :city, :string
|
15
|
+
add_field :state, :string
|
16
|
+
add_field :country, :string
|
17
|
+
add_field :postcode, :string
|
18
|
+
add_field :phone, :string
|
19
|
+
add_field :email, :string
|
20
|
+
end
|
21
|
+
|
22
|
+
add_embed_one :billing_address do
|
23
|
+
add_field :name, :string
|
24
|
+
add_field :address, :string
|
25
|
+
add_field :city, :string
|
26
|
+
add_field :state, :string
|
27
|
+
add_field :country, :string
|
28
|
+
add_field :postcode, :string
|
29
|
+
add_field :phone, :string
|
30
|
+
add_field :email, :string
|
31
|
+
end
|
32
|
+
|
33
|
+
add_field :payment_reference, :string
|
34
|
+
|
35
|
+
# permissions
|
36
|
+
users_group = site.groups['Users'].id
|
37
|
+
transactions.view_group = users_group
|
38
|
+
transactions.create_group = users_group
|
39
|
+
transactions.update_group = users_group
|
40
|
+
transactions.delete_group = users_group
|
41
|
+
end
|
42
|
+
|
43
|
+
site.pages.create_model :transaction_pages do |transaction_pages|
|
44
|
+
transaction_pages.record_class_name = 'TransactionPage'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.down(site)
|
49
|
+
site.transactions.destroy
|
50
|
+
site.transaction_pages.destroy
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class ShopCartModelMigration < Migration
|
2
|
+
def self.up(site)
|
3
|
+
site.records.create_model :carts do |carts|
|
4
|
+
add_field :session_id, :string
|
5
|
+
add_one :user, index: true
|
6
|
+
add_many :product_holds, foreign_key: 'cart', destroy: true
|
7
|
+
add_field :created_at, :time
|
8
|
+
add_field :updated_at, :time
|
9
|
+
add_field :hold_duration, :integer, default: 600
|
10
|
+
add_one :transaction, index: true
|
11
|
+
carts.record_class_name = 'Cart'
|
12
|
+
|
13
|
+
# permissions
|
14
|
+
users_group = site.groups['Users'].id
|
15
|
+
carts.view_group = users_group
|
16
|
+
carts.create_group = users_group
|
17
|
+
carts.update_group = users_group
|
18
|
+
carts.delete_group = users_group
|
19
|
+
end
|
20
|
+
|
21
|
+
site.pages.create_model :cart_pages do |cart_pages|
|
22
|
+
cart_pages.record_class_name = 'CartPage'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.down(site)
|
27
|
+
site.carts.destroy
|
28
|
+
site.cart_pages.destroy
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class ShopProductHoldModelMigration < Migration
|
2
|
+
def self.up(site)
|
3
|
+
site.records.create_model :product_holds do |product_holds|
|
4
|
+
add_one :product, validations: {required: {}}, index: true
|
5
|
+
add_one :cart, validations: {required: {}}, index: true
|
6
|
+
add_field :quantity, :integer, default: 0, validations: {required: {}}
|
7
|
+
add_field :sold, :boolean, default: false
|
8
|
+
product_holds.record_class_name = 'ProductHold'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.down(site)
|
13
|
+
site.product_holds.destroy
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class ShopCouponModelMigration < Migration
|
2
|
+
def self.up(site)
|
3
|
+
site.records.create_model :coupons do |coupons|
|
4
|
+
add_field :code, :string
|
5
|
+
add_field :value, :decimal
|
6
|
+
end
|
7
|
+
|
8
|
+
site.records.create_model :coupon_redemptions do |coupon_redemptions|
|
9
|
+
add_one :user
|
10
|
+
add_one :coupon
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.down(site)
|
15
|
+
site.coupons.destroy
|
16
|
+
site.coupon_redemptions.destroy
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class CouponRedemptionPageModelMigration < Migration
|
2
|
+
def self.up(site)
|
3
|
+
site.pages.create_model :coupon_redemption_pages do |coupon_redemption_pages|
|
4
|
+
add_one :redirect_to, model: :page
|
5
|
+
coupon_redemption_pages.record_class_name = 'CouponRedemptionPage'
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.down(site)
|
10
|
+
site.coupon_redemption_pages.destroy
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class ShopModelFunctionsMigration < Migration
|
2
|
+
def self.up(site)
|
3
|
+
site.products.modify do |products|
|
4
|
+
add_field :total_cost, :function, fn: 'sum(price, shipping, tax)'
|
5
|
+
end
|
6
|
+
|
7
|
+
site.product_holds.modify do |products|
|
8
|
+
add_field :total_cost, :function, fn: 'multiply(product.total_cost, quantity)'
|
9
|
+
end
|
10
|
+
|
11
|
+
site.carts.modify do |carts|
|
12
|
+
add_field :total_cost, :function, fn: 'product_holds.sum(total_cost)'
|
13
|
+
end
|
14
|
+
|
15
|
+
site.transactions.modify do |transactions|
|
16
|
+
remove_field :product_total
|
17
|
+
remove_field :shipping_total
|
18
|
+
remove_field :tax_total
|
19
|
+
remove_field :total
|
20
|
+
|
21
|
+
modify_field :shipping_address do |shipping_address|
|
22
|
+
remove_field :name
|
23
|
+
add_field :first_name, :string
|
24
|
+
add_field :last_name, :string
|
25
|
+
end
|
26
|
+
|
27
|
+
modify_field :billing_address do |billing_address|
|
28
|
+
remove_field :name
|
29
|
+
add_field :first_name, :string
|
30
|
+
add_field :last_name, :string
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.down(site)
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class ShopCartDurationMigration < Migration
|
2
|
+
def self.up(site)
|
3
|
+
# duration is now stored outside a cart
|
4
|
+
site.carts.modify do |carts|
|
5
|
+
remove_field :hold_duration
|
6
|
+
end
|
7
|
+
|
8
|
+
# clean up carts every minute
|
9
|
+
cart_task = Task.new(site)
|
10
|
+
cart_task.type = 'perform_destroy_stale_carts'
|
11
|
+
cart_task.repeat_in = 60
|
12
|
+
cart_task.save
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.down(site)
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class CouponRestrictionsMigration < Migration
|
2
|
+
def self.up(site)
|
3
|
+
site.coupon_redemptions.destroy
|
4
|
+
|
5
|
+
site.coupons.modify do |coupons|
|
6
|
+
add_field :user_restrictions, :hash
|
7
|
+
add_field :product_restrictions, :hash
|
8
|
+
add_field :value_type, :enum, options: %w{currency percent}, default: 'currency'
|
9
|
+
add_many :redemptions, model: :user
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.down(site)
|
14
|
+
site.coupons.modify do |coupons|
|
15
|
+
remove_field :user_restrictions
|
16
|
+
remove_field :product_restrictions
|
17
|
+
remove_field :value_type
|
18
|
+
remove_field :redemptions
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class ShopDiscountModelMigration < Migration
|
2
|
+
def self.up(site)
|
3
|
+
site.records.create_model :discounts do |discounts|
|
4
|
+
add_field :amount, :integer, validations: {required: {}}, default: '0.0'
|
5
|
+
end
|
6
|
+
|
7
|
+
site.carts.modify do |carts|
|
8
|
+
add_many :discounts
|
9
|
+
modify_field :total_cost, fn: 'subtract(product_holds.sum(total_cost), discounts.sum(amount))'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.down(site)
|
14
|
+
site.discounts.destroy
|
15
|
+
site.carts.modify do |carts|
|
16
|
+
remove_field :discounts
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/models/cart.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
class Cart < Record
|
2
|
+
CART_HOLD_DURATION = 10 * 60 # 10 minutes
|
3
|
+
|
4
|
+
before_destroy :delete_all_product_holds
|
5
|
+
def delete_all_product_holds
|
6
|
+
product_holds.each(&:destroy)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.perform_destroy_stale_carts(site)
|
10
|
+
# ignore carts which have been purchased
|
11
|
+
query = site.carts.where(transaction: nil)
|
12
|
+
query = query.where(updated_at: {'$lt' => Time.at(Time.now.utc.to_i - CART_HOLD_DURATION)})
|
13
|
+
query.all.each(&:destroy)
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
class CartPage < Page
|
2
|
+
def products
|
3
|
+
cart.product_holds.collect(&:product)
|
4
|
+
end
|
5
|
+
|
6
|
+
def hold_product(product, quantity)
|
7
|
+
# TODO: find_or_create
|
8
|
+
# use any existing holds for this product
|
9
|
+
hold = site.product_holds.where(product: product.id, cart: cart.id).first
|
10
|
+
hold ||= site.product_holds.new(product: product, cart: cart)
|
11
|
+
|
12
|
+
if product.quantity >= quantity
|
13
|
+
if product.increment! :quantity, -quantity, :quantity.gt => 0
|
14
|
+
hold.quantity += quantity
|
15
|
+
return hold.save
|
16
|
+
end
|
17
|
+
end
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
21
|
+
def cart
|
22
|
+
if logged_in?
|
23
|
+
@cart ||= site.carts.where(transaction: nil, user: current_user.id).order('created desc').first
|
24
|
+
else
|
25
|
+
# TODO: need to track by session
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
respond_to :post do
|
31
|
+
with :html do
|
32
|
+
return unless user_allowed_to?(:create)
|
33
|
+
|
34
|
+
# we're creating a new cart, so delete any existing (current) carts
|
35
|
+
site.carts.where(transaction: nil, user: current_user.id).all.each(&:destroy)
|
36
|
+
|
37
|
+
# create a new cart to hold any products sent in the request
|
38
|
+
@cart = site.carts.new(user: current_user, name: current_user.name)
|
39
|
+
@cart.save
|
40
|
+
|
41
|
+
# if any products were added to the cart, create holds on them
|
42
|
+
flash[:failed] = []
|
43
|
+
|
44
|
+
params['products'].each do |product_options|
|
45
|
+
product = site.products.where(_id: BSON::ObjectId.from_string(product_options['id'])).first
|
46
|
+
quantity = product_options['quantity'].to_i
|
47
|
+
unless product.unlimited_quantity || hold_product(product, quantity)
|
48
|
+
flash[:failed] << product_options['id']
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# finally render the cart page
|
53
|
+
cart.reload
|
54
|
+
respond_to_get_with_html
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
respond_to :put do
|
59
|
+
with :html do
|
60
|
+
return unless user_allowed_to?(:update)
|
61
|
+
# FIXME: implement
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class CouponRedemptionPage < Page
|
2
|
+
respond_to :post do
|
3
|
+
with :html do
|
4
|
+
user = current_user
|
5
|
+
coupon = site.coupons.where(code: params['coupon_code']).first
|
6
|
+
|
7
|
+
if user && coupon
|
8
|
+
unless coupon.redemptions.include?(user)
|
9
|
+
if user_permitted_to_redeem?(user, coupon)
|
10
|
+
coupon.redemptions << user
|
11
|
+
coupon.save
|
12
|
+
|
13
|
+
# FIXME: needs to be atomic and respect percent value_type
|
14
|
+
user.balance += coupon.value
|
15
|
+
user.save_without_validation
|
16
|
+
flash[:coupon_successfully_redeemed] = true
|
17
|
+
else
|
18
|
+
flash[:failed_redemption_rules] = true
|
19
|
+
end
|
20
|
+
else
|
21
|
+
flash[:coupon_already_redeemed] = true
|
22
|
+
end
|
23
|
+
else
|
24
|
+
flash[:coupon_not_found] = true
|
25
|
+
end
|
26
|
+
|
27
|
+
response.redirect redirect_to.path
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def user_permitted_to_redeem?(user, coupon)
|
33
|
+
return true if coupon.user_restrictions.blank?
|
34
|
+
|
35
|
+
coupon.user_restrictions.each do |field, restriction|
|
36
|
+
# TODO: support other restriction types
|
37
|
+
if restriction.is_a?(Array)
|
38
|
+
return false unless restriction.include?(user.get(field))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
true
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
class TransactionPage < Page
|
2
|
+
|
3
|
+
# TODO: find way to abstract this
|
4
|
+
def cart
|
5
|
+
if logged_in?
|
6
|
+
@cart ||= site.carts.where(transaction: nil, user: current_user.id).order('created desc').first
|
7
|
+
else
|
8
|
+
# TODO: need to track by session
|
9
|
+
nil
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def successful?
|
14
|
+
@successful
|
15
|
+
end
|
16
|
+
|
17
|
+
def products
|
18
|
+
cart.product_holds.collect(&:product)
|
19
|
+
end
|
20
|
+
|
21
|
+
respond_to :post do
|
22
|
+
with :html do
|
23
|
+
return unless user_allowed_to?(:create)
|
24
|
+
|
25
|
+
# update the cart from the checkout form (e.g to include discounts)
|
26
|
+
cart.from_json(params['cart']) if params['cart']
|
27
|
+
|
28
|
+
# FIXME: race condition (user a window 1, user a window 2, both doing purchases, inconsistent charge)
|
29
|
+
# Unable to do an atomic update because big decimals are stored as strings... store as int (*100)??
|
30
|
+
current_user.balance -= cart.total_cost
|
31
|
+
current_user.save_without_validation
|
32
|
+
transaction_successful = false
|
33
|
+
transaction_id = BSON::ObjectId.new
|
34
|
+
|
35
|
+
if current_user.balance < 0
|
36
|
+
gateway = ActiveMerchant::Billing::PayWayGateway.new(
|
37
|
+
username: 'Q14555',
|
38
|
+
password: 'Au5xh8v8g',
|
39
|
+
merchant: '23891864',
|
40
|
+
pem: File.join(site.root_directory, 'ccapi.pem'),
|
41
|
+
eci: 'SSL'
|
42
|
+
)
|
43
|
+
|
44
|
+
# construct an expiry month & date from a date string or separate m/y values
|
45
|
+
if params['expiry_month'] && params['expiry_year']
|
46
|
+
expiry_month = params['expiry_month']
|
47
|
+
expiry_year = params['expiry_year']
|
48
|
+
else
|
49
|
+
begin
|
50
|
+
expiry = Date.parse(params['expiry'].to_s)
|
51
|
+
expiry_month = expiry.month.to_s
|
52
|
+
expiry_year = expiry.year.to_s
|
53
|
+
rescue
|
54
|
+
expiry_month = expiry_month = nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# ignore spaces and any non numeric characters
|
59
|
+
card_number = params['card_number'].scan(/\d+/).join
|
60
|
+
|
61
|
+
# use the current user's name if no other name is provided
|
62
|
+
first_name = params['first_name'] || current_user.first_name
|
63
|
+
last_name = params['last_name'] || current_user.last_name
|
64
|
+
|
65
|
+
card = ActiveMerchant::Billing::CreditCard.new(
|
66
|
+
number: card_number,
|
67
|
+
month: expiry_month,
|
68
|
+
year: expiry_year,
|
69
|
+
first_name: first_name,
|
70
|
+
last_name: last_name,
|
71
|
+
verification_value: params['verification']
|
72
|
+
)
|
73
|
+
|
74
|
+
if card.valid?
|
75
|
+
order_number = [transaction_id.data.collect(&:chr).join].pack('m0')
|
76
|
+
result = gateway.purchase((current_user.balance * -100).to_i, card, order_number: order_number)
|
77
|
+
if result.success?
|
78
|
+
payment_reference = result.params['receipt_no']
|
79
|
+
transaction_successful = true
|
80
|
+
else
|
81
|
+
flash[:transact_error] = result.message
|
82
|
+
end
|
83
|
+
else
|
84
|
+
flash[:card_error] = card.errors.collect {|field, val| "#{field.humanize} #{val.to_sentence}"}.join('. ')
|
85
|
+
end
|
86
|
+
else
|
87
|
+
payment_reference = 'Positive Balance'
|
88
|
+
transaction_successful = true
|
89
|
+
end
|
90
|
+
|
91
|
+
if transaction_successful
|
92
|
+
transaction = site.transactions.new(cart: cart)
|
93
|
+
transaction.set_id(transaction_id)
|
94
|
+
|
95
|
+
# billing
|
96
|
+
transaction.billing_address.first_name = params['first_name']
|
97
|
+
transaction.billing_address.last_name = params['last_name']
|
98
|
+
|
99
|
+
# shipping
|
100
|
+
transaction.shipping_address.first_name = current_user.first_name
|
101
|
+
transaction.shipping_address.last_name = current_user.last_name
|
102
|
+
transaction.shipping_address.address = current_user.address
|
103
|
+
transaction.shipping_address.state = current_user.state
|
104
|
+
transaction.shipping_address.city = current_user.city
|
105
|
+
transaction.shipping_address.postcode = current_user.postcode
|
106
|
+
transaction.shipping_address.phone = current_user.phone
|
107
|
+
transaction.shipping_address.email = current_user.email
|
108
|
+
|
109
|
+
# transaction
|
110
|
+
transaction.payment_reference = payment_reference
|
111
|
+
transaction.save
|
112
|
+
|
113
|
+
# mark the products as sold
|
114
|
+
# TODO: switch to hold.update(sold: true)
|
115
|
+
cart.product_holds.each do |hold|
|
116
|
+
hold.sold = true
|
117
|
+
hold.save
|
118
|
+
end
|
119
|
+
|
120
|
+
cart.transaction = transaction
|
121
|
+
cart.save
|
122
|
+
|
123
|
+
@successful = true
|
124
|
+
respond_to_get_with_html()
|
125
|
+
else
|
126
|
+
current_user.balance += cart.total_cost
|
127
|
+
current_user.save_without_validation
|
128
|
+
flash[:first_name] = params['first_name']
|
129
|
+
flash[:last_name] = params['last_name']
|
130
|
+
flash[:expiry] = params['expiry']
|
131
|
+
flash[:card_number] = params['card_number']
|
132
|
+
flash[:verification] = params['verification']
|
133
|
+
response.redirect('/cart')
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
data/lib/yodel_shop.rb
ADDED
data/yodel_shop.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require 'yodel_shop'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'yodel_shop'
|
7
|
+
s.version = YodelShop::VERSION
|
8
|
+
s.authors = ['Will Cannings']
|
9
|
+
s.email = ['me@willcannings.com']
|
10
|
+
s.homepage = 'http://yodelcms.com'
|
11
|
+
s.summary = 'Yodel CMS Shop Extension'
|
12
|
+
s.description = 'Yodel CMS Shop Extension'
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
s.require_paths = ['lib']
|
18
|
+
|
19
|
+
# specify any dependencies here; for example:
|
20
|
+
# s.add_development_dependency "rspec"
|
21
|
+
# s.add_runtime_dependency "rest-client"
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: yodel_shop
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Will Cannings
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-12-09 00:00:00.000000000Z
|
13
|
+
dependencies: []
|
14
|
+
description: Yodel CMS Shop Extension
|
15
|
+
email:
|
16
|
+
- me@willcannings.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- .gitignore
|
22
|
+
- Gemfile
|
23
|
+
- Rakefile
|
24
|
+
- lib/migrations/01_shop_product_model.rb
|
25
|
+
- lib/migrations/02_shop_user_updates.rb
|
26
|
+
- lib/migrations/03_shop_transaction_model.rb
|
27
|
+
- lib/migrations/04_shop_cart_model.rb
|
28
|
+
- lib/migrations/05_shop_product_hold_model.rb
|
29
|
+
- lib/migrations/06_shop_coupon_model.rb
|
30
|
+
- lib/migrations/07_shop_coupon_redemption_page_model.rb
|
31
|
+
- lib/migrations/08_shop_model_functions.rb
|
32
|
+
- lib/migrations/09_shop_cart_duration_task.rb
|
33
|
+
- lib/migrations/10_shop_coupon_restrictions.rb
|
34
|
+
- lib/migrations/11_shop_discount_model.rb
|
35
|
+
- lib/models/cart.rb
|
36
|
+
- lib/models/cart_page.rb
|
37
|
+
- lib/models/coupon_redemption_page.rb
|
38
|
+
- lib/models/product_hold.rb
|
39
|
+
- lib/models/transaction_page.rb
|
40
|
+
- lib/yodel_shop.rb
|
41
|
+
- yodel_shop.gemspec
|
42
|
+
homepage: http://yodelcms.com
|
43
|
+
licenses: []
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ! '>='
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
requirements: []
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 1.8.10
|
63
|
+
signing_key:
|
64
|
+
specification_version: 3
|
65
|
+
summary: Yodel CMS Shop Extension
|
66
|
+
test_files: []
|