develop #3

Merged
kbe merged 227 commits from develop into main 2025-09-16 14:35:23 +00:00
8 changed files with 63 additions and 33 deletions
Showing only changes of commit d6184b6c84 - Show all commits

View File

@@ -0,0 +1,25 @@
module Api
module V1
class CartsController < ApiController
# Skip API key authentication for store_cart action (used by frontend forms)
skip_before_action :authenticate_api_key, only: [ :store ]
def store
event_id = params[:event_id]
@event = Event.find(event_id)
cart_data = params[:cart] || {}
session[:pending_cart] = cart_data
session[:event_id] = @event.id
render json: { status: "success", message: "Cart stored successfully" }
rescue ActiveRecord::RecordNotFound
render json: { status: "error", message: "Event not found" }, status: :not_found
rescue => e
error_message = e.message.present? ? e.message : "Unknown error"
Rails.logger.error "Error storing cart: #{error_message}"
render json: { status: "error", message: "Failed to store cart" }, status: 500
end
end
end
end

View File

@@ -4,9 +4,6 @@
module Api module Api
module V1 module V1
class OrdersController < ApiController class OrdersController < ApiController
# Skip API key authentication for store_cart action (used by frontend forms)
skip_before_action :authenticate_api_key, only: [ :store_cart ]
before_action :set_order, only: [ :show, :checkout, :retry_payment, :increment_payment_attempt ] before_action :set_order, only: [ :show, :checkout, :retry_payment, :increment_payment_attempt ]
before_action :set_event, only: [ :new, :create ] before_action :set_event, only: [ :new, :create ]

View File

@@ -2,7 +2,7 @@
# Provides authentication and common functionality for API controllers # Provides authentication and common functionality for API controllers
class ApiController < ApplicationController class ApiController < ApplicationController
# Disable CSRF protection for API requests (token-based authentication instead) # Disable CSRF protection for API requests (token-based authentication instead)
protect_from_forgery with: :null_session protect_from_forgery prepend: true
# Authenticate all API requests using API key # Authenticate all API requests using API key
# Must be called before any API action # Must be called before any API action

View File

@@ -10,7 +10,7 @@ export default class extends Controller {
"checkoutButton", "checkoutButton",
"form", "form",
]; ];
static values = { eventSlug: String, eventId: String }; static values = { eventSlug: String, eventId: String, orderNewUrl: String, storeCartUrl: String };
// Initialize the controller and update the cart summary // Initialize the controller and update the cart summary
connect() { connect() {
@@ -118,8 +118,8 @@ export default class extends Controller {
await this.storeCartInSession(cartData); await this.storeCartInSession(cartData);
// Redirect to event-scoped orders/new page // Redirect to event-scoped orders/new page
const OrderNewUrl = `/orders/new/events/${this.eventSlugValue}.${this.eventIdValue}`; const orderNewUrl = this.orderNewUrlValue;
window.location.href = OrderNewUrl; window.location.href = orderNewUrl;
} catch (error) { } catch (error) {
console.error("Error storing cart:", error); console.error("Error storing cart:", error);
alert("Une erreur est survenue. Veuillez réessayer."); alert("Une erreur est survenue. Veuillez réessayer.");
@@ -145,7 +145,7 @@ export default class extends Controller {
// Store cart data in session via AJAX // Store cart data in session via AJAX
async storeCartInSession(cartData) { async storeCartInSession(cartData) {
const storeCartUrl = `/api/v1/events/${this.eventIdValue}/store_cart`; const storeCartUrl = this.storeCartUrlValue;
const response = await fetch(storeCartUrl, { const response = await fetch(storeCartUrl, {
method: "POST", method: "POST",
@@ -155,7 +155,7 @@ export default class extends Controller {
.querySelector('meta[name="csrf-token"]') .querySelector('meta[name="csrf-token"]')
.getAttribute("content"), .getAttribute("content"),
}, },
body: JSON.stringify({ cart: cartData }), body: JSON.stringify({ cart: cartData, event_id: this.eventIdValue }),
}); });
if (!response.ok) { if (!response.ok) {

View File

@@ -135,7 +135,11 @@
controller: "ticket-selection", controller: "ticket-selection",
ticket_selection_target: "form", ticket_selection_target: "form",
ticket_selection_event_slug_value: @event.slug, ticket_selection_event_slug_value: @event.slug,
ticket_selection_event_id_value: @event.id 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
} do |form| %> } do |form| %>
<div class="bg-gradient-to-br from-purple-50 to-indigo-50 rounded-2xl border border-purple-100 p-6 shadow-sm"> <div class="bg-gradient-to-br from-purple-50 to-indigo-50 rounded-2xl border border-purple-100 p-6 shadow-sm">

View File

@@ -141,6 +141,9 @@
<button <button
id="checkout-button" id="checkout-button"
data-order-id="<%= @order.id %>"
data-increment-url="/api/v1/orders/<%= @order.id %>/increment_payment_attempt"
data-session-id="<%= @checkout_session.id if @checkout_session.present? %>"
class="w-full btn btn-primary py-4 px-6 rounded-xl transition-all duration-200 transform hover:scale-105 active:scale-95 shadow-lg hover:shadow-xl" class="w-full btn btn-primary py-4 px-6 rounded-xl transition-all duration-200 transform hover:scale-105 active:scale-95 shadow-lg hover:shadow-xl"
> >
<div class="flex items-center justify-center"> <div class="flex items-center justify-center">
@@ -199,11 +202,14 @@
try { try {
// Increment payment attempt counter // Increment payment attempt counter
console.log('Incrementing payment attempt for order:', '<%= @order.id %>'); const orderId = checkoutButton.dataset.orderId;
const response = await fetch('/api/v1/orders/<%= @order.id %>/increment_payment_attempt', { const incrementUrl = checkoutButton.dataset.incrementUrl;
console.log('Incrementing payment attempt for order:', orderId);
const response = await fetch(incrementUrl, {
method: 'PATCH', method: 'PATCH',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json',
'X-CSRF-Token': document.querySelector('[name=csrf-token]').content
} }
}); });
@@ -226,9 +232,10 @@
`; `;
// Redirect to Stripe // Redirect to Stripe
console.log('Redirecting to Stripe with session ID:', '<%= @checkout_session&.id %>'); const sessionId = checkoutButton.dataset.sessionId;
console.log('Redirecting to Stripe with session ID:', sessionId);
const stripeResult = await stripe.redirectToCheckout({ const stripeResult = await stripe.redirectToCheckout({
sessionId: '<%= @checkout_session.id %>' sessionId: sessionId
}); });
if (stripeResult.error) { if (stripeResult.error) {

View File

@@ -96,11 +96,8 @@ Rails.application.routes.draw do
namespace :api do namespace :api do
namespace :v1 do namespace :v1 do
# RESTful routes for event management # RESTful routes for event management
resources :events, only: [ :index, :show, :create, :update, :destroy ] do resources :events, only: [ :index, :show, :create, :update, :destroy ]
member do post "carts/store", to: "carts#store", as: "store_cart"
post :store_cart
end
end
# RESTful routes for order management # RESTful routes for order management
resources :orders, only: [] do resources :orders, only: [] do

View File

@@ -41,7 +41,7 @@ class Api::V1::EventsControllerTest < ActionDispatch::IntegrationTest
end end
test "should store cart" do test "should store cart" do
post store_cart_api_v1_event_url(@event), params: { cart: { ticket_type_id: 1, quantity: 2 } }, as: :json post api_v1_store_cart_path, params: { cart: { ticket_type_id: 1, quantity: 2 }, event_id: @event.id }, as: :json, headers: headers_api_key
assert_response :success assert_response :success
assert_equal @event.id, session[:event_id] assert_equal @event.id, session[:event_id]
end end