Skip to content
Draft
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
65 changes: 50 additions & 15 deletions app/controllers/events/bulk_payments_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module Events
class BulkPaymentsController < ApplicationController
skip_before_action :authenticate_user!, only: [ :new, :create, :show, :ticket, :resend_confirmation ]
skip_before_action :authenticate_user!, only: [ :new, :create, :show, :ticket, :resend_confirmation, :pay ]
before_action :set_event, only: [ :new, :create, :show ]
before_action :set_form, only: [ :new, :create ]

Expand Down Expand Up @@ -109,6 +109,35 @@ def resend_confirmation
end
end

# Public pay-later action: a payer who chose "Credit card (later)" (or landed
# on the ticket with payment pending) returns here to pay by card. Mirrors
# Registrations#pay. The resulting Stripe charge carries the submission id and
# flows through the existing webhook (PayChargeExtensions#create_bulk_payment)
# to record the payment.
def pay
authorize! :bulk_payment, to: :pay?

@submission = FormSubmission.bulk_payment.find_by!(slug: params[:slug])
@event = @submission.event

if @submission.payment.present?
redirect_to bulk_payment_ticket_path(@submission.slug), notice: "This payment has already been paid."
return
end

if @submission.bulk_payment_amount_cents(@event) <= 0
redirect_to bulk_payment_ticket_path(@submission.slug), alert: "No payment is due."
return
end

checkout_session = build_checkout_session(
@submission,
quantity: @submission.bulk_payment_attendee_count,
attendees_json: @submission.bulk_payment_attendees.to_json
)
redirect_to checkout_session.url, allow_other_host: true, status: :see_other
end

private

def visible_form_fields
Expand Down Expand Up @@ -146,22 +175,28 @@ def credit_card_payment?(form_params)
form_params[payment_method_field.id.to_s]&.downcase == FormBuilderService::PAYMENT_METHOD_PAY_NOW.downcase
end

# Submit-path checkout: quantity and attendees come from the just-submitted
# form params (no persisted answers to read yet).
def create_stripe_checkout_session(submission)
person = submission.person
unit_amount = @event.cost_cents
count_field = @form.form_fields.find_by(field_identifier: "number_of_attendees")
qty = count_field ? @form_params[count_field.id.to_s].to_i : 1

attendees_field = @form.form_fields.find_by(field_identifier: "number_of_attendees")
qty = attendees_field ? @form_params[attendees_field.id.to_s].to_i : 1
qty = 1 if qty < 1
attendees_field = @form.form_fields.find_by(field_identifier: "bulk_payment_attendees")
attendees_json = attendees_field ? @form_params[attendees_field.id.to_s] : nil

metadata = { form_submission_id: submission.id, event_id: @event.id }
build_checkout_session(submission, quantity: qty, attendees_json: attendees_json)
end

attendees_field = @form.form_fields.find_by(field_identifier: "bulk_payment_attendees")
if attendees_field
attendees_json = @form_params[attendees_field.id.to_s]
metadata[:attendees] = attendees_json if attendees_json.present?
end
# Builds the Stripe Checkout session for a bulk payment. Quantity and the
# attendees metadata are passed in so both the submit path (reads form params)
# and the pay-later path (reads the saved submission) can share this.
def build_checkout_session(submission, quantity:, attendees_json:)
quantity = 1 if quantity.to_i < 1

metadata = { form_submission_id: submission.id, event_id: @event.id }
metadata[:attendees] = attendees_json if attendees_json.present?

person = submission.person
person.set_payment_processor :stripe

person.payment_processor.checkout(
Expand All @@ -171,10 +206,10 @@ def create_stripe_checkout_session(submission)
line_items: [ {
price_data: {
currency: "usd",
product_data: { name: "#{Form::BULK_PAYMENT_PUBLIC_NAME} (#{qty} attendees): #{@event.title}" },
unit_amount: unit_amount
product_data: { name: "#{Form::BULK_PAYMENT_PUBLIC_NAME} (#{quantity} attendees): #{@event.title}" },
unit_amount: @event.cost_cents
},
quantity: qty
quantity: quantity
} ],
success_url: bulk_payment_ticket_url(submission.slug, checkout: "success"),
cancel_url: bulk_payment_ticket_url(submission.slug, checkout: "cancelled")
Expand Down
4 changes: 4 additions & 0 deletions app/policies/events/bulk_payment_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,8 @@ def show?
def ticket?
true
end

def pay?
true
end
end
9 changes: 9 additions & 0 deletions app/views/events/bulk_payments/ticket.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,15 @@
</span>
<% end %>
</div>

<% if @payment.nil? && total_cents.to_i > 0 %>
<div>
<%= button_to bulk_payment_pay_path(@submission.slug),
class: "inline-flex items-center justify-center gap-2 rounded-lg bg-purple-700 hover:bg-purple-800 text-white font-semibold px-5 py-2.5 transition-colors" do %>
<i class="fa-solid fa-credit-card"></i> Pay with credit card
<% end %>
</div>
<% end %>
</div>

<!-- Registrant details -->
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
resources :community_news
get "bulk_payment/:slug", to: "events/bulk_payments#ticket", as: :bulk_payment_ticket
post "bulk_payment/:slug/resend_confirmation", to: "events/bulk_payments#resend_confirmation", as: :bulk_payment_resend_confirmation
post "bulk_payment/:slug/pay", to: "events/bulk_payments#pay", as: :bulk_payment_pay
get "registration/:slug", to: "events/registrations#show", as: :registration_ticket
get "registration/:slug/invoice", to: "events/registrations#invoice", as: :registration_invoice
get "registration/:slug/receipt", to: "events/registrations#receipt", as: :registration_receipt
Expand Down
68 changes: 68 additions & 0 deletions spec/requests/events/bulk_payments_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,22 @@ def get_ticket
expect(response.body).not_to include("Cancel registration")
end

it "shows a pay-with-credit-card button while payment is pending" do
get_ticket

expect(response.body).to include(bulk_payment_pay_path(submission.slug))
expect(response.body).to include("Pay with credit card")
end

it "hides the pay button once a payment has been recorded" do
submission.create_payment!(type: "CashPayment", amount_cents: 1000, amount_cents_remaining: 1000,
person: payer, payer_type: "Person")

get_ticket

expect(response.body).not_to include(bulk_payment_pay_path(submission.slug))
end

it "links the invoice back to the ticket" do
get_ticket

Expand Down Expand Up @@ -348,4 +364,56 @@ def get_ticket
expect(flash[:notice]).to eq("Confirmation email sent.")
end
end

describe "POST pay" do
let(:event) { create(:event, :publicly_visible, cost_cents: 15_00, title: "Spring Workshop") }
let(:payer) { create(:person) }
let!(:submission) { create(:form_submission, person: payer, form: form, event: event, role: "bulk_payment") }
let!(:number_field) do
create(:form_field, form: form, answer_type: :free_form_input_one_line,
field_identifier: "number_of_attendees", name: "Number of attendees", required: false)
end
let(:fake_session) { double(url: "https://checkout.stripe.com/test") }

before do
submission.form_answers.create!(form_field: number_field, submitted_answer: "3",
question_name_when_answered: "Number of attendees")
fake_processor = double(checkout: fake_session)
allow_any_instance_of(Person).to receive(:set_payment_processor)
allow_any_instance_of(Person).to receive(:payment_processor).and_return(fake_processor)
sign_out admin
end

it "redirects the public payer to Stripe Checkout for a pending submission" do
post bulk_payment_pay_path(submission.slug)

expect(response).to redirect_to("https://checkout.stripe.com/test")
expect(response.status).to eq(303)
end

it "returns to the ticket when a payment already exists" do
submission.create_payment!(type: "CashPayment", amount_cents: 4500, amount_cents_remaining: 4500,
person: payer, payer_type: "Person")

post bulk_payment_pay_path(submission.slug)

expect(response).to redirect_to(bulk_payment_ticket_path(submission.slug))
expect(flash[:notice]).to eq("This payment has already been paid.")
end

it "returns to the ticket when no payment is due" do
event.update!(cost_cents: 0)

post bulk_payment_pay_path(submission.slug)

expect(response).to redirect_to(bulk_payment_ticket_path(submission.slug))
expect(flash[:alert]).to eq("No payment is due.")
end

it "returns 404 for an unknown slug" do
post bulk_payment_pay_path("nope")

expect(response).to have_http_status(:not_found)
end
end
end
Loading