diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 0b607cc35f..bb17f266f1 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -169,7 +169,7 @@ jobs: if [ "$STATUS" == "ACTIVE" ]; then echo "✅ Deployment completed successfully." exit 0 - elif [[ "$STATUS" == "FAILED" || "$STATUS" == "CANCELLED" ]]; then + elif [[ "$STATUS" == "FAILED" || "$STATUS" == "CANCELED" ]]; then echo "❌ Deployment failed or was cancelled." exit 1 fi diff --git a/backend/app/DomainObjects/OrderDomainObject.php b/backend/app/DomainObjects/OrderDomainObject.php index 9a5baed0fd..5782dd2af5 100644 --- a/backend/app/DomainObjects/OrderDomainObject.php +++ b/backend/app/DomainObjects/OrderDomainObject.php @@ -29,6 +29,8 @@ class OrderDomainObject extends Generated\OrderDomainObjectAbstract implements I public ?EventDomainObject $event = null; + public ?string $sessionIdentifier = null; + public static function getAllowedFilterFields(): array { return [ @@ -253,4 +255,15 @@ public function getInvoices(): ?Collection { return $this->invoices; } + + public function setSessionIdentifier(?string $sessionIdentifier): OrderDomainObject + { + $this->sessionIdentifier = $sessionIdentifier; + return $this; + } + + public function getSessionIdentifier(): ?string + { + return $this->sessionIdentifier; + } } diff --git a/backend/app/Http/Actions/Orders/Public/CreateOrderActionPublic.php b/backend/app/Http/Actions/Orders/Public/CreateOrderActionPublic.php index bf024a9df7..9f0f48fde1 100644 --- a/backend/app/Http/Actions/Orders/Public/CreateOrderActionPublic.php +++ b/backend/app/Http/Actions/Orders/Public/CreateOrderActionPublic.php @@ -48,10 +48,12 @@ public function __invoke(CreateOrderRequest $request, int $eventId): JsonRespons ]) ); + $order->setSessionIdentifier($sessionId); + $response = $this->resourceResponse( resource: OrderResourcePublic::class, data: $order, - statusCode: ResponseCodes::HTTP_CREATED + statusCode: ResponseCodes::HTTP_CREATED, ); return $response->withCookie( diff --git a/backend/app/Http/Actions/Orders/Public/GetOrderActionPublic.php b/backend/app/Http/Actions/Orders/Public/GetOrderActionPublic.php index 01b7e0e925..ff8133af4f 100644 --- a/backend/app/Http/Actions/Orders/Public/GetOrderActionPublic.php +++ b/backend/app/Http/Actions/Orders/Public/GetOrderActionPublic.php @@ -6,13 +6,15 @@ use HiEvents\Resources\Order\OrderResourcePublic; use HiEvents\Services\Application\Handlers\Order\DTO\GetOrderPublicDTO; use HiEvents\Services\Application\Handlers\Order\GetOrderPublicHandler; +use HiEvents\Services\Infrastructure\Session\CheckoutSessionManagementService; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; class GetOrderActionPublic extends BaseAction { public function __construct( - private readonly GetOrderPublicHandler $getOrderPublicHandler + private readonly GetOrderPublicHandler $getOrderPublicHandler, + private readonly CheckoutSessionManagementService $sessionService, ) { } @@ -25,9 +27,17 @@ public function __invoke(int $eventId, string $orderShortId, Request $request): includeEventInResponse: $this->isIncludeRequested($request, 'event'), )); - return $this->resourceResponse( + $response = $this->resourceResponse( resource: OrderResourcePublic::class, data: $order, ); + + if ($request->query->has('session_identifier')) { + $response->headers->setCookie( + $this->sessionService->getSessionCookie() + ); + } + + return $response; } } diff --git a/backend/app/Resources/Order/OrderResourcePublic.php b/backend/app/Resources/Order/OrderResourcePublic.php index 4654c50926..7880d22ed5 100644 --- a/backend/app/Resources/Order/OrderResourcePublic.php +++ b/backend/app/Resources/Order/OrderResourcePublic.php @@ -8,7 +8,6 @@ use HiEvents\Resources\Attendee\AttendeeResourcePublic; use HiEvents\Resources\BaseResource; use HiEvents\Resources\Event\EventResourcePublic; -use HiEvents\Resources\Order\Invoice\InvoiceResource; use HiEvents\Resources\Order\Invoice\InvoiceResourcePublic; use Illuminate\Http\Request; @@ -64,6 +63,9 @@ public function toArray(Request $request): array !is_null($this->getAttendees()), fn() => AttendeeResourcePublic::collection($this->getAttendees()) ), + $this->mergeWhen($this->getSessionIdentifier() !== null, fn() => [ + 'session_identifier' => $this->getSessionIdentifier(), + ]), ]; } } diff --git a/backend/app/Services/Infrastructure/Session/CheckoutSessionManagementService.php b/backend/app/Services/Infrastructure/Session/CheckoutSessionManagementService.php index 792067f849..d7552ac842 100644 --- a/backend/app/Services/Infrastructure/Session/CheckoutSessionManagementService.php +++ b/backend/app/Services/Infrastructure/Session/CheckoutSessionManagementService.php @@ -22,7 +22,7 @@ public function __construct( } /** - * Get the session ID from the cookie, or generate a new one if it doesn't exist. + * Get the session ID from query param, cookie, or generate a new one. */ public function getSessionId(): string { @@ -30,7 +30,9 @@ public function getSessionId(): string return $this->sessionId; } - $this->sessionId = $this->request->cookie(self::SESSION_IDENTIFIER) ?? $this->createSessionId(); + $this->sessionId = $this->request->query(self::SESSION_IDENTIFIER) + ?? $this->request->cookie(self::SESSION_IDENTIFIER) + ?? $this->createSessionId(); return $this->sessionId; } diff --git a/frontend/src/api/order.client.ts b/frontend/src/api/order.client.ts index eb80c8001e..350893b33a 100644 --- a/frontend/src/api/order.client.ts +++ b/frontend/src/api/order.client.ts @@ -116,8 +116,24 @@ export const orderClientPublic = { return response.data; }, - findByShortId: async (eventId: number, orderShortId: string, includes: string[] = []) => { - const response = await publicApi.get>(`events/${eventId}/order/${orderShortId}?include=${includes.join(',')}`); + findByShortId: async ( + eventId: number, + orderShortId: string, + includes: string[] = [], + sessionIdentifier?: string + ) => { + const query = new URLSearchParams(); + if (includes.length > 0) { + query.append("include", includes.join(",")); + } + if (sessionIdentifier) { + query.append("session_identifier", sessionIdentifier); + } + + const response = await publicApi.get>( + `events/${eventId}/order/${orderShortId}?${query.toString()}` + ); + return response.data; }, @@ -125,11 +141,11 @@ export const orderClientPublic = { return await publicApi.get(`events/${eventId}/order/${orderShortId}/stripe/payment_intent`); }, - createStripePaymentIntent: async (eventId: number, orderShortId: string, sessionIdentifier: string) => { + createStripePaymentIntent: async (eventId: number, orderShortId: string) => { const response = await publicApi.post<{ client_secret: string, account_id?: string, - }>(`events/${eventId}/order/${orderShortId}/stripe/payment_intent?session_identifier=${sessionIdentifier}`); + }>(`events/${eventId}/order/${orderShortId}/stripe/payment_intent`); return response.data; }, diff --git a/frontend/src/components/routes/product-widget/SelectProducts/index.tsx b/frontend/src/components/routes/product-widget/SelectProducts/index.tsx index 8f6d74aa6c..44d3c32654 100644 --- a/frontend/src/components/routes/product-widget/SelectProducts/index.tsx +++ b/frontend/src/components/routes/product-widget/SelectProducts/index.tsx @@ -103,7 +103,10 @@ const SelectProducts = (props: SelectProductsProps) => { .then(() => { const url = '/checkout/' + eventId + '/' + data.data.short_id + '/details'; if (props.widgetMode === 'embedded') { - window.open(url, '_blank'); + window.open( + url + '?session_identifier=' + data.data.session_identifier + '&utm_source=embedded_widget', + '_blank' + ); setOrderInProcessOverlayVisible(true); return; } diff --git a/frontend/src/queries/useCreateStripePaymentIntent.ts b/frontend/src/queries/useCreateStripePaymentIntent.ts index 65826138a9..5eada79576 100644 --- a/frontend/src/queries/useCreateStripePaymentIntent.ts +++ b/frontend/src/queries/useCreateStripePaymentIntent.ts @@ -13,7 +13,6 @@ export const useCreateStripePaymentIntent = (eventId: IdParam, orderShortId: IdP const {client_secret, account_id} = await orderClientPublic.createStripePaymentIntent( Number(eventId), String(orderShortId), - getSessionIdentifier(), ); return {client_secret, account_id}; }, diff --git a/frontend/src/queries/useGetOrderPublic.ts b/frontend/src/queries/useGetOrderPublic.ts index c098c917e0..77dfb12850 100644 --- a/frontend/src/queries/useGetOrderPublic.ts +++ b/frontend/src/queries/useGetOrderPublic.ts @@ -1,25 +1,44 @@ import {useQuery} from "@tanstack/react-query"; import {orderClientPublic} from "../api/order.client.ts"; import {IdParam, Order} from "../types.ts"; +import {useMemo} from "react"; +import {isSsr} from "../utilites/helpers.ts"; -export const GET_ORDER_PUBLIC_QUERY_KEY = 'getOrderPublic'; +export const GET_ORDER_PUBLIC_QUERY_KEY = "getOrderPublic"; -export const useGetOrderPublic = (eventId: IdParam, orderShortId: IdParam, includes: string[] = []) => { - return useQuery({ - queryKey: [GET_ORDER_PUBLIC_QUERY_KEY, eventId, orderShortId], +const getSessionIdentifierFromUrl = (): string | null => { + if (isSsr()) return null; + + const url = new URL(window.location.href); + return url.searchParams.get("session_identifier"); +}; + +export const useGetOrderPublic = ( + eventId: IdParam, + orderShortId: IdParam, + includes: string[] = [] +) => { + const sessionIdentifier = useMemo(getSessionIdentifierFromUrl, []); + return useQuery({ + queryKey: [ + GET_ORDER_PUBLIC_QUERY_KEY, + eventId, + orderShortId, + sessionIdentifier, + ], queryFn: async () => { const {data} = await orderClientPublic.findByShortId( Number(eventId), String(orderShortId), includes, + sessionIdentifier ?? undefined ); return data; }, - refetchOnWindowFocus: false, staleTime: 500, retryOnMount: false, - retry: false + retry: false, }); -} +}; diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 1d851bf82a..9ed8652cfc 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -475,6 +475,7 @@ export interface Order { question_answers?: QuestionAnswer[]; event?: Event; latest_invoice?: Invoice; + session_identifier?: string; } export interface Invoice {