Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ecommerce/pricing/lib/pricing.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require "infra"
require_relative "pricing/discounts"
require_relative "pricing/coupon_discount"
require_relative "pricing/coupon"
require_relative "pricing/commands"
require_relative "pricing/events"
Expand Down
12 changes: 7 additions & 5 deletions ecommerce/pricing/lib/pricing/coupon.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require_relative 'events'
require_relative "events"
require_relative "coupon_discount"

module Pricing
class Coupon
Expand All @@ -10,22 +11,23 @@ def initialize(id)
@id = id
end

def register(name, code, discount)
def register(name, code, discount_raw)
raise AlreadyRegistered if @registered

discount = discount_raw.is_a?(CouponDiscount) ? discount_raw : CouponDiscount.parse(discount_raw)

apply CouponRegistered.new(
data: {
coupon_id: @id,
name: name,
code: code,
discount: discount
discount: discount.to_d
}
)
end

on CouponRegistered do |event|
on CouponRegistered do |_event|
@registered = true
end
end
end

32 changes: 32 additions & 0 deletions ecommerce/pricing/lib/pricing/coupon_discount.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# frozen_string_literal: true

require "bigdecimal"
module Pricing
class CouponDiscount
UnacceptableRange = Class.new(StandardError)
Unparseable = Class.new(StandardError)

def self.parse(raw)
new(raw)
rescue ArgumentError
raise Unparseable, "Discount must be a number"
end

attr_reader :value

def initialize(raw)
@value = BigDecimal(raw.to_s).freeze

raise UnacceptableRange, "Discount must be greater than 0" if @value <= 0
raise UnacceptableRange, "Discount must be less than or equal to 100" if @value > 100
end

def to_d
value
end

def to_s
value.to_s("F")
end
end
end
6 changes: 4 additions & 2 deletions ecommerce/pricing/lib/pricing/offer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,14 @@ def remove_free_product(order_id, product_id)
)
end

def use_coupon(coupon_id, discount)
def use_coupon(coupon_id, discount_raw)
discount = discount_raw.is_a?(CouponDiscount) ? discount_raw : CouponDiscount.parse(discount_raw)

apply CouponUsed.new(
data: {
order_id: @id,
coupon_id: coupon_id,
discount: discount
discount: discount.to_d
}
)
end
Expand Down
28 changes: 28 additions & 0 deletions ecommerce/pricing/test/coupon_discount_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
require_relative "test_helper"

module Pricing
class CouponDiscountTest < Test
cover "Pricing::CouponDiscount*"

def test_rejects_zero
assert_raises(CouponDiscount::UnacceptableRange) { CouponDiscount.parse("0") }
end

def test_rejects_negative
assert_raises(CouponDiscount::UnacceptableRange) { CouponDiscount.parse("-0.01") }
end

def test_rejects_over_100
assert_raises(CouponDiscount::UnacceptableRange) { CouponDiscount.parse("100.01") }
end

def test_rejects_non_numeric
assert_raises(CouponDiscount::Unparseable) { CouponDiscount.parse("abc") }
end

def test_accepts_string_decimal
discount = CouponDiscount.parse("0.01")
assert_equal(BigDecimal("0.01"), discount.to_d)
end
end
end
14 changes: 14 additions & 0 deletions ecommerce/pricing/test/coupons_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,19 @@ def test_100_is_ok
def test_0_01_is_ok
register_coupon(@uid, fake_name, @code, "0.01")
end

def test_negative_discount_is_rejected
assert_raises(Infra::Command::Invalid) do
register_coupon(@uid, fake_name, @code, -0.01)
end
end

def test_negative_discount_string_is_rejected
assert_raises(Infra::Command::Invalid) do
register_coupon(@uid, fake_name, @code, "-150")
end
end


end
end
23 changes: 15 additions & 8 deletions rails_application/app/controllers/coupons_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,35 @@ def new
end

def create
coupon_id = params[:coupon_id]
@coupon_id = params[:coupon_id].presence || SecureRandom.uuid

discount = Pricing::CouponDiscount.parse(params[:discount])

ActiveRecord::Base.transaction do
create_coupon(coupon_id)
create_coupon(@coupon_id, discount)
end
rescue Pricing::CouponDiscount::UnacceptableRange, Pricing::CouponDiscount::Unparseable
flash.now[:alert] = "Discount must be greater than 0 and less than or equal to 100"
render "new", status: :unprocessable_entity
rescue Pricing::Coupon::AlreadyRegistered
flash[:notice] = "Coupon is already registered"
render "new"
flash.now[:alert] = "Coupon is already registered"
render "new", status: :unprocessable_entity
rescue Infra::Command::Invalid
flash.now[:alert] = "Invalid coupon data"
render "new", status: :unprocessable_entity
else
redirect_to coupons_path, notice: "Coupon was successfully created"
end

private

def create_coupon(coupon_id)
def create_coupon(coupon_id, discount)
command_bus.(
Pricing::RegisterCoupon.new(
coupon_id: coupon_id,
name: params[:name],
code: params[:code],
discount: params[:discount]
discount: discount.to_d
)
)
command_bus.(
Expand All @@ -38,5 +46,4 @@ def create_coupon(coupon_id)
)
)
end

end
end
6 changes: 3 additions & 3 deletions rails_application/app/views/coupons/new.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@
<label for="coupon" class="block font-bold">
Name
</label>
<%= text_field_tag :name, "", required: true, class: "mt-1 focus:ring-blue-500 focus:border-blue-500 block shadow-sm sm:text-sm border-gray-300 rounded-md" %>
<%= text_field_tag :name, params[:name], required: true, class: "mt-1 focus:ring-blue-500 focus:border-blue-500 block shadow-sm sm:text-sm border-gray-300 rounded-md" %>

<label for="coupon" class="block font-bold">
Code
</label>
<%= text_field_tag :code, "", required: true, class: "mt-1 focus:ring-blue-500 focus:border-blue-500 block shadow-sm sm:text-sm border-gray-300 rounded-md" %>
<%= text_field_tag :code, params[:code], required: true, class: "mt-1 focus:ring-blue-500 focus:border-blue-500 block shadow-sm sm:text-sm border-gray-300 rounded-md" %>

<label for="coupon" class="block font-bold">
Discount
</label>
<%= number_field_tag :discount, nil, min: 1, max: 100, step: 0.01, id: "discount", required: true, class: "mt-1 focus:ring-blue-500 focus:border-blue-500 block shadow-sm sm:text-sm border-gray-300 rounded-md" %>
<%= number_field_tag :discount, params[:discount], min: 1, max: 100, step: 0.01, id: "discount", required: true, class: "mt-1 focus:ring-blue-500 focus:border-blue-500 block shadow-sm sm:text-sm border-gray-300 rounded-md" %>
<% end %>
21 changes: 21 additions & 0 deletions rails_application/test/integration/coupons_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,27 @@ def test_creation
assert_select("td", "6.69")
end

def test_creation_with_negative_discount_is_rejected
register_store("Test Store")

get "/coupons/new"
coupon_id = css_select("input[name='coupon_id']").first["value"]

post "/coupons", params: {
coupon_id: coupon_id,
name: "Bad Coupon",
code: "NEG",
discount: "-150"
}

assert_response :unprocessable_entity

get "/coupons"
assert_response :success
assert_select("td", text: "Bad Coupon", count: 0)
assert_select("td", text: "NEG", count: 0)
end

private

def register_coupon(name, code, discount)
Expand Down