yodel_shop 0.0.1
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.
- 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: []
|