Skip to content

Commit

Permalink
Apply coupon to order
Browse files Browse the repository at this point in the history
  • Loading branch information
marlena-b committed Sep 20, 2024
1 parent 3e990b5 commit 7fd5de3
Show file tree
Hide file tree
Showing 15 changed files with 176 additions and 4 deletions.
4 changes: 4 additions & 0 deletions ecommerce/pricing/lib/pricing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ def call(event_store, command_bus)
RemoveFreeProductFromOrder,
RemoveFreeProductFromOrderHandler.new(event_store)
)
command_bus.register(
UseCoupon,
UseCouponHandler.new(event_store)
)
event_store.subscribe(CalculateOrderTotalValue, to: [
PriceItemAdded,
PriceItemRemoved,
Expand Down
8 changes: 8 additions & 0 deletions ecommerce/pricing/lib/pricing/commands.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,12 @@ class RemoveFreeProductFromOrder < Infra::Command

alias aggregate_id order_id
end

class UseCoupon < Infra::Command
attribute :order_id, Infra::Types::UUID
attribute :coupon_id, Infra::Types::UUID
attribute :discount, Infra::Types::CouponDiscount

alias aggregate_id order_id
end
end
6 changes: 6 additions & 0 deletions ecommerce/pricing/lib/pricing/events.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,10 @@ class FreeProductRemovedFromOrder < Infra::Event
attribute :order_id, Infra::Types::UUID
attribute :product_id, Infra::Types::UUID
end

class CouponUsed < Infra::Event
attribute :order_id, Infra::Types::UUID
attribute :coupon_id, Infra::Types::UUID
attribute :discount, Infra::Types::CouponDiscount
end
end
13 changes: 13 additions & 0 deletions ecommerce/pricing/lib/pricing/offer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,16 @@ def calculate_sub_amounts(pricing_catalog, time_promotions_discount)
end
end

def use_coupon(coupon_id, discount)
apply CouponUsed.new(
data: {
order_id: @id,
coupon_id: coupon_id,
discount: discount
}
)
end

private

on PriceItemAdded do |event|
Expand Down Expand Up @@ -151,6 +161,9 @@ def calculate_total_sub_discounts(pricing_catalog, time_promotions_discount)
@list.sub_discounts(pricing_catalog, time_promotions_discount, @discount)
end

on CouponUsed do |event|
end

class List

def initialize
Expand Down
12 changes: 12 additions & 0 deletions ecommerce/pricing/lib/pricing/services.rb
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,16 @@ def call(command)
end
end
end

class UseCouponHandler
def initialize(event_store)
@repository = Infra::AggregateRootRepository.new(event_store)
end

def call(command)
@repository.with_aggregate(Offer, command.aggregate_id) do |order|
order.use_coupon(command.coupon_id, command.discount)
end
end
end
end
2 changes: 1 addition & 1 deletion ecommerce/pricing/test/coupons_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def test_100_is_ok
end

def test_0_01_is_ok
register_coupon(@uid, fake_name, @code, 0.01)
register_coupon(@uid, fake_name, @code, "0.01")
end
end
end
38 changes: 38 additions & 0 deletions ecommerce/pricing/test/use_coupon_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
require_relative "test_helper"

module Pricing
class UseCouponTest < Test
cover "Pricing*"

def test_coupon_is_used
product_1_id = SecureRandom.uuid
set_price(product_1_id, 20)
coupon_id = SecureRandom.uuid
register_coupon(coupon_id, "Coupon", "coupon10", 10)
order_id = SecureRandom.uuid
add_item(order_id, product_1_id)

assert_events_contain(
stream_name(order_id),
CouponUsed.new(
data: {
order_id: order_id,
coupon_id: coupon_id,
discount: 10
}
)
) do
run_command(
Pricing::UseCoupon.new(order_id: order_id, coupon_id: coupon_id, discount: 10)
)
end
end

private

def stream_name(id)
"Pricing::Offer$#{id}"
end

end
end
7 changes: 7 additions & 0 deletions ecommerce/processes/lib/processes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class << self
def call(event_store, command_bus)
self.class.event_store = event_store
self.class.command_bus = command_bus
enable_coupon_discount_process(event_store, command_bus)
notify_payments_about_order_total_value(event_store, command_bus)
enable_shipment_sync(event_store, command_bus)
determine_vat_rates_on_order_placed(event_store, command_bus)
Expand Down Expand Up @@ -150,5 +151,11 @@ def register_order_on_order_placed(event_store, command_bus)
to: [Ordering::OrderPlaced]
)
end

def enable_coupon_discount_process(event_store, command_bus)
Infra::Process.new(event_store, command_bus)
.call(Pricing::CouponUsed, [:order_id, :discount],
Pricing::SetPercentageDiscount, [:order_id, :amount])
end
end
end
2 changes: 1 addition & 1 deletion infra/lib/infra/types.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ module Types
Price = Types::Coercible::Decimal.constrained(gt: 0)
Value = Types::Coercible::Decimal
PercentageDiscount = Types::Coercible::Decimal.constrained(gt: 0, lteq: 100)
CouponDiscount = Types::Coercible::Float.constrained(gt: 0, lteq: 100)
CouponDiscount = Types::Coercible::Decimal.constrained(gt: 0, lteq: 100)
UUIDQuantityHash = Types::Hash.map(UUID, Quantity)

class VatRate < Dry::Struct
Expand Down
20 changes: 20 additions & 0 deletions rails_application/app/controllers/client/orders_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,25 @@ def remove_item
)
end

def use_coupon
coupon = Coupons::Coupon.find_by!("lower(code) = ?", params[:coupon_code].downcase)
ActiveRecord::Base.transaction do
command_bus.(use_coupon_cmd(params[:id], coupon.uid, coupon.discount))
end
flash[:notice] = "Coupon applied!"
redirect_to edit_client_order_path(params[:id])
rescue ActiveRecord::RecordNotFound
flash[:alert] = "Coupon not found!"
redirect_to edit_client_order_path(params[:id])
rescue Pricing::NotPossibleToAssignDiscountTwice
flash[:alert] = "Coupon already used!"
redirect_to edit_client_order_path(params[:id])
end

private

def use_coupon_cmd(order_id, coupon_id, discount)
Pricing::UseCoupon.new(order_id: order_id, coupon_id: coupon_id, discount: discount)
end
end
end
3 changes: 1 addition & 2 deletions rails_application/app/read_models/orders/update_discount.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module Orders
class UpdateDiscount
def call(event)
order = Order.find_by_uid(event.data.fetch(:order_id))
order = Order.find_or_create_by(uid: event.data.fetch(:order_id))
if is_newest_value?(event, order)
order.percentage_discount = event.data.fetch(:amount)
order.discount_updated_at = event.metadata.fetch(:timestamp)
Expand All @@ -28,4 +28,3 @@ def broadcaster
end
end
end

5 changes: 5 additions & 0 deletions rails_application/app/views/client/orders/edit.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@
</tbody>
</table>

<%= form_with url: use_coupon_client_order_path(id: @order_id), method: :post, class: "inline-flex gap-4 mt-8" do |form| %>
<%= form.text_field :coupon_code, class: "focus:ring-blue-500 focus:border-blue-500 block shadow-sm sm:text-sm border-gray-300 rounded-md", data: { turbo_permanent: true } %>
<%= form.submit("Use Coupon", class: "px-4 py-2 border rounded-md shadow-sm text-sm font-medium border-gray-300 text-gray-700 bg-white hover:bg-gray-50") %>
<% end %>

<%= form_tag({controller: "client/orders", action: "create"}, method: "post", id: "form") do %>
<%= hidden_field_tag(:order_id, @order_id) %>

Expand Down
1 change: 1 addition & 0 deletions rails_application/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
member do
post :add_item
post :remove_item
post :use_coupon
end
end

Expand Down
55 changes: 55 additions & 0 deletions rails_application/test/integration/client_orders_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,56 @@ def test_current_time_promotion_is_applied
assert_select "tr td", "$2.00"
end

def test_using_coupon_applies_discount
customer_id = register_customer("Customer Shop")
product_id = register_product("Fearless Refactoring", 4, 10)
register_coupon("Coupon", "coupon10", 10)

login(customer_id)
visit_client_orders

order_id = SecureRandom.uuid
as_client_add_item_to_basket_for_order(product_id, order_id)
as_client_use_coupon(order_id, "COUPON10")

assert_select "#notice", "Coupon applied!"

as_client_submit_order_for_customer(order_id)

assert_select "tr td", "$3.60"
end

def test_using_coupon_with_wrong_code
customer_id = register_customer("Customer Shop")
product_id = register_product("Fearless Refactoring", 4, 10)
register_coupon("Coupon", "coupon10", 10)

login(customer_id)
visit_client_orders

order_id = SecureRandom.uuid
as_client_add_item_to_basket_for_order(product_id, order_id)
as_client_use_coupon(order_id, "WRONGCODE")

assert_select "#alert", "Coupon not found!"
end

def test_using_coupon_twice
customer_id = register_customer("Customer Shop")
product_id = register_product("Fearless Refactoring", 4, 10)
register_coupon("Coupon", "coupon10", 10)

login(customer_id)
visit_client_orders

order_id = SecureRandom.uuid
as_client_add_item_to_basket_for_order(product_id, order_id)
as_client_use_coupon(order_id, "COUPON10")
as_client_use_coupon(order_id, "COUPON10")

assert_select "#alert", "Coupon already used!"
end

private

def submit_order_for_customer(customer_id, order_id)
Expand All @@ -222,6 +272,11 @@ def as_client_add_item_to_basket_for_order(async_remote_id, order_id)
post "/client_orders/#{order_id}/add_item?product_id=#{async_remote_id}"
end

def as_client_use_coupon(order_id, code)
post "/client_orders/#{order_id}/use_coupon", params: { coupon_code: code }
follow_redirect!
end

def cancel_order(order_id)
post "/orders/#{order_id}/cancel"
end
Expand Down
4 changes: 4 additions & 0 deletions rails_application/test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ def register_product(name, price, vat_rate_code)
product_id
end

def register_coupon(name, code, discount)
post "/coupons", params: { coupon_id: SecureRandom.uuid, name: name, code: code, discount: discount }
end

def add_available_vat_rate(rate, code = rate.to_s)
post "/available_vat_rates", params: { code: code, rate: rate }
end
Expand Down

0 comments on commit 7fd5de3

Please sign in to comment.