From 9c56b2e1e5c6e4ba9881916bb4149b9800158556 Mon Sep 17 00:00:00 2001 From: kbe Date: Tue, 16 Sep 2025 16:36:39 +0200 Subject: [PATCH 1/5] refactor: prepare for free ticket --- app/controllers/orders_controller.rb | 9 +++++ app/helpers/tickets_helper.rb | 7 ++++ app/models/order.rb | 5 +++ app/models/ticket.rb | 2 +- app/models/ticket_type.rb | 6 +++- app/views/components/_ticket_card.html.erb | 2 +- app/views/promoter/ticket_types/edit.html.erb | 4 ++- app/views/promoter/ticket_types/new.html.erb | 8 ++--- app/views/promoter/ticket_types/show.html.erb | 4 +-- .../purchase_confirmation.html.erb | 4 +-- .../purchase_confirmation.text.erb | 6 ++-- app/views/tickets/show.html.erb | 2 +- test/controllers/orders_controller_test.rb | 2 +- test/models/order_test.rb | 18 ++++++++++ test/models/ticket_type_test.rb | 34 +++++++++++++++++++ 15 files changed, 96 insertions(+), 17 deletions(-) diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb index 7ce04a8..9456dd9 100644 --- a/app/controllers/orders_controller.rb +++ b/app/controllers/orders_controller.rb @@ -126,6 +126,15 @@ class OrdersController < ApplicationController @total_amount = @order.total_amount_cents @expiring_soon = @order.expiring_soon? + # For free orders, automatically mark as paid and redirect to success + if @order.free? + @order.mark_as_paid! + session.delete(:pending_cart) + session.delete(:ticket_names) + session.delete(:draft_order_id) + return redirect_to order_path(@order), notice: "Vos billets gratuits ont été confirmés !" + end + # Create Stripe checkout session if Stripe is configured if Rails.application.config.stripe[:secret_key].present? begin diff --git a/app/helpers/tickets_helper.rb b/app/helpers/tickets_helper.rb index 4722254..c661310 100644 --- a/app/helpers/tickets_helper.rb +++ b/app/helpers/tickets_helper.rb @@ -1,2 +1,9 @@ module TicketsHelper + def format_ticket_price(price_cents) + if price_cents == 0 + "Gratuit" + else + number_to_currency(price_cents / 100.0, unit: "€") + end + end end diff --git a/app/models/order.rb b/app/models/order.rb index 666a6a3..3c84bc7 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -116,6 +116,11 @@ class Order < ApplicationRecord promoter_payout_cents / 100.0 end + # Check if order contains only free tickets + def free? + total_amount_cents == 0 + end + # Create Stripe invoice for accounting records # # This method creates a post-payment invoice in Stripe for accounting purposes diff --git a/app/models/ticket.rb b/app/models/ticket.rb index 51b0ae1..94b2508 100755 --- a/app/models/ticket.rb +++ b/app/models/ticket.rb @@ -9,7 +9,7 @@ class Ticket < ApplicationRecord validates :qr_code, presence: true, uniqueness: true validates :order_id, presence: true validates :ticket_type_id, presence: true - validates :price_cents, presence: true, numericality: { greater_than: 0 } + validates :price_cents, presence: true, numericality: { greater_than_or_equal_to: 0 } validates :status, presence: true, inclusion: { in: %w[draft active used expired refunded] } validates :first_name, presence: true validates :last_name, presence: true diff --git a/app/models/ticket_type.rb b/app/models/ticket_type.rb index f06cfd5..1274758 100755 --- a/app/models/ticket_type.rb +++ b/app/models/ticket_type.rb @@ -6,7 +6,7 @@ class TicketType < ApplicationRecord # Validations validates :name, presence: true, length: { minimum: 3, maximum: 50 } validates :description, presence: true, length: { minimum: 10, maximum: 500 } - validates :price_cents, presence: true, numericality: { greater_than: 0 } + validates :price_cents, presence: true, numericality: { greater_than_or_equal_to: 0 } validates :quantity, presence: true, numericality: { only_integer: true, greater_than: 0 } validates :sale_start_at, presence: true validates :sale_end_at, presence: true @@ -48,6 +48,10 @@ class TicketType < ApplicationRecord [ quantity - tickets.count, 0 ].max end + def free? + price_cents == 0 + end + def sales_status return :draft if sale_start_at.nil? || sale_end_at.nil? return :expired if sale_end_at < Time.current diff --git a/app/views/components/_ticket_card.html.erb b/app/views/components/_ticket_card.html.erb index 11fe4d5..81c9056 100755 --- a/app/views/components/_ticket_card.html.erb +++ b/app/views/components/_ticket_card.html.erb @@ -7,7 +7,7 @@

"> - <%= number_to_currency(price_cents / 100.0, unit: "€") %> + <%= format_ticket_price(price_cents) %>

diff --git a/app/views/promoter/ticket_types/edit.html.erb b/app/views/promoter/ticket_types/edit.html.erb index 0f7e836..87fae02 100644 --- a/app/views/promoter/ticket_types/edit.html.erb +++ b/app/views/promoter/ticket_types/edit.html.erb @@ -81,7 +81,7 @@
<%= form.number_field :price_euros, step: 0.01, - min: 0.01, + min: 0, class: "w-full px-4 py-2 pl-8 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent", data: { "ticket-type-form-target": "price", action: "input->ticket-type-form#updateTotal" } %>
@@ -91,6 +91,8 @@ Modifier le prix n'affectera pas les billets déjà vendus

+ <% else %> +

Prix unitaire du billet (0€ pour un billet gratuit)

<% end %>
diff --git a/app/views/promoter/ticket_types/new.html.erb b/app/views/promoter/ticket_types/new.html.erb index dd57e59..bfa8077 100644 --- a/app/views/promoter/ticket_types/new.html.erb +++ b/app/views/promoter/ticket_types/new.html.erb @@ -66,14 +66,14 @@
<%= form.label :price_euros, "Prix (€)", class: "block text-sm font-medium text-gray-700 mb-2" %>
- <%= form.number_field :price_euros, - step: 0.01, - min: 0.01, + <%= form.number_field :price_euros, + step: 0.01, + min: 0, class: "w-full px-4 py-2 pl-8 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent", data: { "ticket-type-form-target": "price", action: "input->ticket-type-form#updateTotal" } %>
-

Prix unitaire du billet

+

Prix unitaire du billet (0€ pour un billet gratuit)

diff --git a/app/views/promoter/ticket_types/show.html.erb b/app/views/promoter/ticket_types/show.html.erb index fea82b3..a5d1fe1 100644 --- a/app/views/promoter/ticket_types/show.html.erb +++ b/app/views/promoter/ticket_types/show.html.erb @@ -138,7 +138,7 @@

- <%= number_to_currency(ticket.price_cents / 100.0, unit: "€") %> + <%= format_ticket_price(ticket.price_cents) %>

<%= ticket.created_at.strftime("%d/%m/%Y") %> @@ -164,7 +164,7 @@

- <%= number_to_currency(@ticket_type.price_euros, unit: "€") %> + <%= format_ticket_price(@ticket_type.price_cents) %>
Prix unitaire
diff --git a/app/views/ticket_mailer/purchase_confirmation.html.erb b/app/views/ticket_mailer/purchase_confirmation.html.erb index fbb52e2..7f276a6 100755 --- a/app/views/ticket_mailer/purchase_confirmation.html.erb +++ b/app/views/ticket_mailer/purchase_confirmation.html.erb @@ -57,7 +57,7 @@

📱 Voir le détail et le code QR

-

<%= number_to_currency(ticket.price_cents / 100.0, unit: "€") %>

+

<%= format_ticket_price(ticket.price_cents) %>

@@ -83,7 +83,7 @@

Prix

-

<%= number_to_currency(@ticket.price_cents / 100.0, unit: "€") %>

+

<%= format_ticket_price(@ticket.price_cents) %>

diff --git a/app/views/ticket_mailer/purchase_confirmation.text.erb b/app/views/ticket_mailer/purchase_confirmation.text.erb index b4d9c78..3329cb9 100755 --- a/app/views/ticket_mailer/purchase_confirmation.text.erb +++ b/app/views/ticket_mailer/purchase_confirmation.text.erb @@ -13,11 +13,11 @@ DÉTAILS DE VOTRE COMMANDE Événement : <%= @event.name %> Date & heure : <%= @event.start_time.strftime("%d %B %Y à %H:%M") %> Nombre de billets : <%= @tickets.count %> -Total : <%= number_to_currency(@order.total_amount_euros, unit: "€") %> +Total : <%= @order.free? ? "Gratuit" : number_to_currency(@order.total_amount_euros, unit: "€") %> BILLETS INCLUS : <% @tickets.each_with_index do |ticket, index| %> -- Billet #<%= index + 1 %> : <%= ticket.ticket_type.name %> - <%= number_to_currency(ticket.price_cents / 100.0, unit: "€") %> +- Billet #<%= index + 1 %> : <%= ticket.ticket_type.name %> - <%= format_ticket_price(ticket.price_cents) %> <% end %> Vos billets sont attachés à cet email en format PDF. Présentez-les à l'entrée de l'événement pour y accéder. @@ -32,7 +32,7 @@ DÉTAILS DE VOTRE BILLET Événement : <%= @event.name %> Type de billet : <%= @ticket.ticket_type.name %> Date & heure : <%= @event.start_time.strftime("%d %B %Y à %H:%M") %> -Prix : <%= number_to_currency(@ticket.price_cents / 100.0, unit: "€") %> +Prix : <%= format_ticket_price(@ticket.price_cents) %> Votre billet est attaché à cet email en format PDF. Présentez-le à l'entrée de l'événement pour y accéder. diff --git a/app/views/tickets/show.html.erb b/app/views/tickets/show.html.erb index 94da8ac..cba7c3f 100644 --- a/app/views/tickets/show.html.erb +++ b/app/views/tickets/show.html.erb @@ -80,7 +80,7 @@

- <%= number_to_currency(@ticket.price_euros, unit: "€", separator: ",", delimiter: " ", format: "%n %u") %> + <%= format_ticket_price(@ticket.price_cents) %>

diff --git a/test/controllers/orders_controller_test.rb b/test/controllers/orders_controller_test.rb index aaa6a3c..1cb416d 100644 --- a/test/controllers/orders_controller_test.rb +++ b/test/controllers/orders_controller_test.rb @@ -125,7 +125,7 @@ class OrdersControllerTest < ActionDispatch::IntegrationTest assert_equal "draft", new_order.status assert_equal @user, new_order.user assert_equal @event, new_order.event - assert_equal @ticket_type.price_cents + 100, new_order.total_amount_cents # includes 1€ service fee + assert_equal @ticket_type.price_cents, new_order.total_amount_cents # Service fee deducted from promoter payout, not added to customer assert_redirected_to checkout_order_path(new_order) assert_equal new_order.id, session[:draft_order_id] diff --git a/test/models/order_test.rb b/test/models/order_test.rb index 6db2124..0a67b6b 100644 --- a/test/models/order_test.rb +++ b/test/models/order_test.rb @@ -603,4 +603,22 @@ class OrderTest < ActiveSupport::TestCase result = order.stripe_invoice_pdf_url assert_nil result end + + test "free? should return true for zero amount orders" do + free_order = Order.create!( + user: @user, event: @event, total_amount_cents: 0, + status: "draft", payment_attempts: 0 + ) + + assert free_order.free? + end + + test "free? should return false for non-zero amount orders" do + paid_order = Order.create!( + user: @user, event: @event, total_amount_cents: 1000, + status: "draft", payment_attempts: 0 + ) + + assert_not paid_order.free? + end end diff --git a/test/models/ticket_type_test.rb b/test/models/ticket_type_test.rb index 3aea6b0..0b3caf3 100755 --- a/test/models/ticket_type_test.rb +++ b/test/models/ticket_type_test.rb @@ -244,4 +244,38 @@ class TicketTypeTest < ActiveSupport::TestCase ) assert_not ticket_type.save end + + test "should allow free tickets with zero price" do + user = User.create!( + email: "test@example.com", + password: "password123", + password_confirmation: "password123" + ) + + event = Event.create!( + name: "Test Event", + slug: "test-event", + description: "Valid description for the event that is long enough", + latitude: 48.8566, + longitude: 2.3522, + venue_name: "Test Venue", + venue_address: "123 Test Street", + user: user + ) + + ticket_type = TicketType.new( + name: "Free Ticket", + description: "Valid description for the free ticket type", + price_cents: 0, + quantity: 50, + sale_start_at: Time.current, + sale_end_at: Time.current + 1.day, + event: event + ) + + assert ticket_type.save + assert ticket_type.free? + assert_equal 0, ticket_type.price_cents + assert_equal 0.0, ticket_type.price_euros + end end -- 2.49.1 From 329ba89eaa602a7817837ff4a9e2d6be899adfba Mon Sep 17 00:00:00 2001 From: kbe Date: Tue, 16 Sep 2025 16:55:40 +0200 Subject: [PATCH 2/5] chore: Better description details for ticket types --- app/views/promoter/ticket_types/edit.html.erb | 12 ++---- app/views/promoter/ticket_types/new.html.erb | 37 ++++++++++--------- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/app/views/promoter/ticket_types/edit.html.erb b/app/views/promoter/ticket_types/edit.html.erb index 87fae02..0ba89f4 100644 --- a/app/views/promoter/ticket_types/edit.html.erb +++ b/app/views/promoter/ticket_types/edit.html.erb @@ -165,8 +165,10 @@

- Événement: <%= @event.start_time.strftime("%d/%m/%Y à %H:%M") %>
- Les ventes doivent se terminer avant le début de l'événement. + Début d'événement : <%= @event.start_time.strftime("%d/%m/%Y à %H:%M") %>
+ <% unless @event.allow_booking_during_event? %> + Les ventes doivent se terminer avant le début de l'événement. + <% end %>

@@ -210,12 +212,6 @@ <%= link_to promoter_event_ticket_type_path(@event, @ticket_type), class: "text-gray-500 hover:text-gray-700 transition-colors" do %> Annuler <% end %> - <% if @ticket_type.tickets.any? %> -

- - <%= pluralize(@ticket_type.tickets.count, 'billet') %> déjà vendu(s) -

- <% end %>
diff --git a/app/views/promoter/ticket_types/new.html.erb b/app/views/promoter/ticket_types/new.html.erb index bfa8077..3701c35 100644 --- a/app/views/promoter/ticket_types/new.html.erb +++ b/app/views/promoter/ticket_types/new.html.erb @@ -42,7 +42,7 @@

Informations générales

- +
<%= form.label :name, "Nom du type de billet", class: "block text-sm font-medium text-gray-700 mb-2" %> @@ -61,7 +61,7 @@

Prix et quantité

- +
<%= form.label :price_euros, "Prix (€)", class: "block text-sm font-medium text-gray-700 mb-2" %> @@ -75,11 +75,11 @@

Prix unitaire du billet (0€ pour un billet gratuit)

- +
<%= form.label :quantity, "Quantité disponible", class: "block text-sm font-medium text-gray-700 mb-2" %> - <%= form.number_field :quantity, - min: 1, + <%= form.number_field :quantity, + min: 1, class: "w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent", data: { "ticket-type-form-target": "quantity", action: "input->ticket-type-form#updateTotal" } %>

Nombre total de billets de ce type

@@ -100,18 +100,18 @@

Période de vente

- +
<%= form.label :sale_start_at, "Début des ventes", class: "block text-sm font-medium text-gray-700 mb-2" %> - <%= form.datetime_local_field :sale_start_at, + <%= form.datetime_local_field :sale_start_at, value: @ticket_type.sale_start_at&.strftime("%Y-%m-%dT%H:%M"), class: "w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent" %>
- +
<%= form.label :sale_end_at, "Fin des ventes", class: "block text-sm font-medium text-gray-700 mb-2" %> - <%= form.datetime_local_field :sale_end_at, + <%= form.datetime_local_field :sale_end_at, value: @ticket_type.sale_end_at&.strftime("%Y-%m-%dT%H:%M"), class: "w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent" %>

Les ventes s'arrêtent automatiquement à cette date

@@ -123,8 +123,11 @@

- Événement: <%= @event.start_time.strftime("%d/%m/%Y à %H:%M") %>
- Les ventes doivent se terminer avant le début de l'événement. + Début d'événement : <%= @event.start_time.strftime("%d/%m/%Y à %H:%M") %>
+ + <% unless @event.allow_booking_during_event? %> + Les ventes doivent se terminer avant le début de l'événement. + <% end %>

@@ -134,13 +137,13 @@

Conditions d'accès

- +
<%= form.label :minimum_age, "Âge minimum", class: "block text-sm font-medium text-gray-700 mb-2" %> - <%= form.number_field :minimum_age, - min: 0, - max: 120, + <%= form.number_field :minimum_age, + min: 0, + max: 120, class: "w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent", placeholder: "Laisser vide si aucune restriction" %>

Âge minimum requis (optionnel)

@@ -167,11 +170,11 @@ Annuler <% end %>
- +
<%= form.submit "Créer le type de billet", class: "inline-flex items-center px-6 py-3 bg-purple-600 text-white font-medium rounded-lg hover:bg-purple-700 transition-colors duration-200" %>
<% end %>
-
\ No newline at end of file +
-- 2.49.1 From 5279ebe1a428db7bd94a547e15b401088f1d9eaa Mon Sep 17 00:00:00 2001 From: kbe Date: Tue, 16 Sep 2025 17:15:09 +0200 Subject: [PATCH 3/5] feat(event available/sold out): Promoter can mark event as sold out or available On the event page, promoter can choose to mark the event as "sold out" using the status field or as "published". Only published event can be marked as sold out if promoter thinks he cannot handle all the people available. --- app/controllers/promoter/events_controller.rb | 12 ++++++++++- app/models/event.rb | 1 - app/views/promoter/events/show.html.erb | 21 +++++++++++++++---- config/routes.rb | 1 + 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/app/controllers/promoter/events_controller.rb b/app/controllers/promoter/events_controller.rb index 31e2ada..a45c7c0 100644 --- a/app/controllers/promoter/events_controller.rb +++ b/app/controllers/promoter/events_controller.rb @@ -5,7 +5,7 @@ class Promoter::EventsController < ApplicationController before_action :authenticate_user! before_action :ensure_can_manage_events! - before_action :set_event, only: [ :show, :edit, :update, :destroy, :publish, :unpublish, :cancel, :mark_sold_out, :duplicate ] + before_action :set_event, only: [ :show, :edit, :update, :destroy, :publish, :unpublish, :cancel, :mark_sold_out, :mark_available, :duplicate ] # Display all events for the current promoter def index @@ -93,6 +93,16 @@ class Promoter::EventsController < ApplicationController end end + # Mark event as available again + def mark_available + if @event.sold_out? + @event.update(state: :published) + redirect_to promoter_event_path(@event), notice: "Event marqué comme disponible!" + else + redirect_to promoter_event_path(@event), alert: "Cet event ne peut pas être marqué comme disponible." + end + end + # Duplicate an event and all its ticket types def duplicate clone_ticket_types = params[:clone_ticket_types] == "true" diff --git a/app/models/event.rb b/app/models/event.rb index 674770c..2fa5daa 100755 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -55,7 +55,6 @@ class Event < ApplicationRecord # Scope for published events ordered by start time scope :upcoming, -> { published.where("start_time >= ?", Time.current).order(start_time: :asc) } - # === Instance Methods === # Check if coordinates were successfully geocoded or are fallback coordinates diff --git a/app/views/promoter/events/show.html.erb b/app/views/promoter/events/show.html.erb index fe95cb4..ce0b4a1 100644 --- a/app/views/promoter/events/show.html.erb +++ b/app/views/promoter/events/show.html.erb @@ -132,10 +132,14 @@
-
+

Événement complet

Tous les billets pour cet événement ont été vendus.

+ <%= button_to mark_available_promoter_event_path(@event), method: :patch, class: "ml-4 inline-flex items-center px-3 py-1 bg-white border border-blue-300 text-blue-700 text-sm font-medium rounded-lg hover:bg-blue-50 transition-colors duration-200" do %> + + Marquer comme disponible + <% end %>
<% end %> @@ -273,10 +277,19 @@ Gérer les types de billets <% end %> - <%= button_to mark_sold_out_promoter_event_path(@event), method: :patch, class: "w-full inline-flex items-center justify-center px-4 py-3 bg-gray-50 text-gray-700 font-medium text-sm rounded-lg hover:bg-gray-100 transition-colors duration-200", disabled: !@event.published? do %> - - Marquer comme complet + + <% if @event.sold_out? %> + <%= button_to mark_available_promoter_event_path(@event), method: :patch, class: "w-full inline-flex items-center justify-center px-4 py-3 bg-blue-50 text-blue-700 font-medium text-sm rounded-lg hover:bg-blue-100 transition-colors duration-200" do %> + + Marquer comme disponible + <% end %> + <% elsif @event.published? %> + <%= button_to mark_sold_out_promoter_event_path(@event), method: :patch, class: "w-full inline-flex items-center justify-center px-4 py-3 bg-gray-50 text-gray-700 font-medium text-sm rounded-lg hover:bg-gray-100 transition-colors duration-200" do %> + + Marquer comme complet + <% end %> <% end %> +
<%= button_to promoter_event_path(@event), method: :delete, data: { confirm: "Êtes-vous sûr de vouloir supprimer cet événement ? Cette action est irréversible." }, diff --git a/config/routes.rb b/config/routes.rb index d4bf3be..ce1b6b6 100755 --- a/config/routes.rb +++ b/config/routes.rb @@ -81,6 +81,7 @@ Rails.application.routes.draw do patch :unpublish patch :cancel patch :mark_sold_out + patch :mark_available post :duplicate end -- 2.49.1 From 04393add1449a727971bc9fbeaa9c7a887710996 Mon Sep 17 00:00:00 2001 From: kbe Date: Tue, 16 Sep 2025 17:22:00 +0200 Subject: [PATCH 4/5] fix(tests): Remove service fee expectation from Stripe invoice test and fix duplicated keys in event view - Update StripeInvoiceServiceTest to match the implementation that no longer adds service fees to customer invoices - Remove duplicated Stimulus data attributes in events/show.html.erb that were causing warnings - Align tests with the hybrid fee model where fees are deducted from promoter payouts --- app/views/events/show.html.erb | 8 +++----- test/services/stripe_invoice_service_test.rb | 15 +-------------- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/app/views/events/show.html.erb b/app/views/events/show.html.erb index 02f8dd1..00ccc34 100755 --- a/app/views/events/show.html.erb +++ b/app/views/events/show.html.erb @@ -135,11 +135,9 @@ controller: "ticket-selection", ticket_selection_target: "form", ticket_selection_event_slug_value: @event.slug, - ticket_selection_event_id_value: @event.id, - ticket_selection_order_new_url_value: event_order_new_path(@event.slug, @event.id), - ticket_selection_store_cart_url_value: api_v1_store_cart_path, - ticket_selection_order_new_url_value: event_order_new_path(@event.slug, @event.id), - ticket_selection_store_cart_url_value: api_v1_store_cart_path + ticket_selection_event_id_value: @event.id, + ticket_selection_order_new_url_value: event_order_new_path(@event.slug, @event.id), + ticket_selection_store_cart_url_value: api_v1_store_cart_path } do |form| %>
diff --git a/test/services/stripe_invoice_service_test.rb b/test/services/stripe_invoice_service_test.rb index 60e914e..ebbd7d2 100644 --- a/test/services/stripe_invoice_service_test.rb +++ b/test/services/stripe_invoice_service_test.rb @@ -210,25 +210,12 @@ class StripeInvoiceServiceTest < ActiveSupport::TestCase } } - expected_service_fee_line_item = { - customer: "cus_test123", - invoice: "in_test123", - amount: 100, - currency: "eur", - description: "Frais de service - Frais de traitement de la commande", - metadata: { - item_type: "service_fee", - amount_cents: 100 - } - } - mock_invoice = mock("invoice") mock_invoice.stubs(:id).returns("in_test123") mock_invoice.stubs(:finalize_invoice).returns(mock_invoice) mock_invoice.expects(:pay) Stripe::Invoice.expects(:create).returns(mock_invoice) - Stripe::InvoiceItem.expects(:create).with(expected_ticket_line_item) - Stripe::InvoiceItem.expects(:create).with(expected_service_fee_line_item) + Stripe::InvoiceItem.expects(:create).with(expected_ticket_line_item) # Only for tickets, no service fee result = @service.create_post_payment_invoice assert_not_nil result -- 2.49.1 From b5c1846f2c620edb24ea046b21d84fdf13e493e9 Mon Sep 17 00:00:00 2001 From: kbe Date: Tue, 16 Sep 2025 17:25:14 +0200 Subject: [PATCH 5/5] fix(mailers): Include TicketsHelper in TicketMailer to make format_ticket_price available - Add helper :tickets to TicketMailer to make format_ticket_price method available in mailer templates - Fixes undefined method 'format_ticket_price' error in purchase confirmation emails - Required after recent changes to support free tickets --- app/mailers/ticket_mailer.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/mailers/ticket_mailer.rb b/app/mailers/ticket_mailer.rb index 79a066c..8164572 100755 --- a/app/mailers/ticket_mailer.rb +++ b/app/mailers/ticket_mailer.rb @@ -1,4 +1,6 @@ class TicketMailer < ApplicationMailer + helper :tickets + def purchase_confirmation_order(order) @order = order @user = order.user -- 2.49.1