This commit is contained in:
2025-07-18 16:10:13 +03:00
parent 5487242bbe
commit ab0834149d
13 changed files with 58 additions and 24 deletions

22
.env
View File

@@ -53,5 +53,23 @@ NEXT_PUBLIC_DEFAULT_LOCALE=et
NEXT_PUBLIC_TEAM_NAVIGATION_STYLE=custom NEXT_PUBLIC_TEAM_NAVIGATION_STYLE=custom
NEXT_PUBLIC_USER_NAVIGATION_STYLE=custom NEXT_PUBLIC_USER_NAVIGATION_STYLE=custom
# MEDUSA #### MEDUSA
NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY= #NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY=pk_0ec86252438b38ce18d5601f7877e4395d7e0a6afa8687dfea8d37af33015633
#MEDUSA_BACKEND_URL=http://5.181.51.38:9000
NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY=pk_e23a820689a07d55aa0a0ad187268559f5d6288ecb0768ff4520516285bdef84
MEDUSA_BACKEND_URL=http://localhost:9000
# NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY=pk_068d930c33fea53608a410d84a51935f6ce2ccec5bef8e0ecf75eaee602ac486
# MEDUSA_BACKEND_URL=https://backoffice-test.medreport.ee:443
#### MONTONIO
NEXT_PUBLIC_MONTONIO_ACCESS_KEY=7da5d7fa-3383-4997-9435-46aa818f4ead
MONTONIO_SECRET_KEY=rNZkzwxOiH93mzkdV53AvhSsbGidrgO2Kl5lE/IT7cvo
MONTONIO_API_URL=https://sandbox-stargate.montonio.com
#### SUPABASE
# NEXT_PUBLIC_SUPABASE_URL=https://oqsdacktkhmbylmzstjq.supabase.co
# NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im9xc2RhY2t0a2htYnlsbXpzdGpxIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDY1MjgxMjMsImV4cCI6MjA2MjEwNDEyM30.LdHCTWxijFmhXdnT9KVuLRAVbtSwY7OO-oLtpd8GmO0
# SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im9xc2RhY2t0a2htYnlsbXpzdGpxIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc0NjUyODEyMywiZXhwIjoyMDYyMTA0MTIzfQ.KVcnkZ21Pd0XkJho23dZqFHawVTLQqfvF7l2RxsELLk
NEXT_PUBLIC_SUPABASE_URL=http://5.181.51.38:54321
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU

View File

@@ -2,7 +2,7 @@
# These values are only used when running the app in development mode. # These values are only used when running the app in development mode.
# SUPABASE # SUPABASE
NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321 NEXT_PUBLIC_SUPABASE_URL=http://5.181.51.38:54321
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0 NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU

View File

@@ -38,6 +38,9 @@ export const POST = enhanceRouteHandler(
const body = await request.json(); const body = await request.json();
const namespace = 'montonio.verify-token'; const namespace = 'montonio.verify-token';
const activeCartId = request.cookies.get('_medusa_cart_id')?.value;
console.info('cart id', activeCartId);
try { try {
const { token } = BodySchema.parse(body); const { token } = BodySchema.parse(body);
@@ -58,6 +61,12 @@ export const POST = enhanceRouteHandler(
algorithms: ['HS256'], algorithms: ['HS256'],
}) as MontonioOrderToken; }) as MontonioOrderToken;
const [, cartId] = decoded.merchantReferenceDisplay.split(':');
console.info('active cart id parsed', {cartId, activeCartId, decoded:decoded.merchantReferenceDisplay});
if (cartId !== activeCartId) {
throw new Error('Invalid cart id');
}
logger.info( logger.info(
{ {
name: namespace, name: namespace,

View File

@@ -21,7 +21,7 @@ export default function CartItems({ cart, items, productColumnLabelKey }: {
return ( return (
<Table className="rounded-lg border border-separate"> <Table className="rounded-lg border border-separate">
<TableHeader className="text-ui-fg-subtle txt-medium-plus"> <TableHeader className="text-ui-fg-subtle txt-medium-plus">
<TableRow className=""> <TableRow>
<TableHead className="px-6"> <TableHead className="px-6">
<Trans i18nKey={productColumnLabelKey} /> <Trans i18nKey={productColumnLabelKey} />
</TableHead> </TableHead>

View File

@@ -8,7 +8,7 @@ import { Button } from '@kit/ui/button';
import { Trans } from '@kit/ui/trans'; import { Trans } from '@kit/ui/trans';
import { placeOrder } from "@lib/data/cart" import { placeOrder } from "@lib/data/cart"
import Link from 'next/link'; import Link from 'next/link';
import Loading from '@/app/home/loading'; import GlobalLoader from '../../loading';
enum Status { enum Status {
LOADING = 'LOADING', LOADING = 'LOADING',
@@ -18,12 +18,17 @@ enum Status {
export function MontonioCheckoutCallback() { export function MontonioCheckoutCallback() {
const router = useRouter(); const router = useRouter();
const [status, setStatus] = useState<Status>(Status.LOADING); const [status, setStatus] = useState<Status>(Status.LOADING);
const [isFinalized, setIsFinalized] = useState(false);
const searchParams = useSearchParams(); const searchParams = useSearchParams();
useEffect(() => { useEffect(() => {
if (isFinalized) {
return;
}
const token = searchParams.get('order-token'); const token = searchParams.get('order-token');
if (!token) { if (!token) {
router.push('/home/cart'); //router.push('/home/cart');
return; return;
} }
@@ -38,6 +43,7 @@ export function MontonioCheckoutCallback() {
}, },
body: JSON.stringify({ token }), body: JSON.stringify({ token }),
}); });
setIsFinalized(true);
if (!response.ok) { if (!response.ok) {
const body = await response.json(); const body = await response.json();
@@ -54,7 +60,7 @@ export function MontonioCheckoutCallback() {
router.push('/home/cart'); router.push('/home/cart');
} }
} else { } else {
setStatus(Status.ERROR); throw new Error('Payment failed or pending');
} }
} catch (e) { } catch (e) {
console.error("Error verifying token", e); console.error("Error verifying token", e);
@@ -63,7 +69,7 @@ export function MontonioCheckoutCallback() {
} }
void verifyToken(); void verifyToken();
}, [searchParams]); }, [searchParams, isFinalized]);
if (status === Status.ERROR) { if (status === Status.ERROR) {
return ( return (
@@ -91,5 +97,5 @@ export function MontonioCheckoutCallback() {
); );
} }
return (<Loading />); return <GlobalLoader />;
} }

View File

@@ -67,7 +67,7 @@ export default function CartTotals({ order }: {
</div> </div>
<div className="h-px w-full border-b border-gray-200 my-4" /> <div className="h-px w-full border-b border-gray-200 my-4" />
<div className="flex items-center justify-between text-ui-fg-base mb-2 txt-medium "> <div className="flex items-center justify-between text-ui-fg-base mb-2 txt-medium ">
<span><Trans i18nKey="cart:orderConfirmed.total" /></span> <span className="font-bold"><Trans i18nKey="cart:orderConfirmed.total" /></span>
<span <span
className="txt-xlarge-plus" className="txt-xlarge-plus"
data-testid="cart-total" data-testid="cart-total"

View File

@@ -1,6 +1,7 @@
import { Trans } from '@kit/ui/trans'; import { Trans } from '@kit/ui/trans';
import { PageBody, PageHeader } from '@kit/ui/page'; import { PageBody, PageHeader } from '@kit/ui/page';
import { StoreOrder } from "@medusajs/types" import { StoreOrder } from "@medusajs/types"
import Divider from "@modules/common/components/divider"
import CartTotals from "./cart-totals" import CartTotals from "./cart-totals"
import OrderDetails from "./order-details" import OrderDetails from "./order-details"
@@ -14,8 +15,10 @@ export default async function OrderCompleted({
return ( return (
<PageBody> <PageBody>
<PageHeader title={<Trans i18nKey="cart:orderConfirmed.title" />} /> <PageHeader title={<Trans i18nKey="cart:orderConfirmed.title" />} />
<Divider />
<div className="grid grid-cols-1 small:grid-cols-[1fr_360px] gap-x-40 lg:px-4 gap-y-6"> <div className="grid grid-cols-1 small:grid-cols-[1fr_360px] gap-x-40 lg:px-4 gap-y-6">
<OrderDetails order={order} /> <OrderDetails order={order} />
<Divider />
<OrderItems order={order} /> <OrderItems order={order} />
<CartTotals order={order} /> <CartTotals order={order} />
</div> </div>

View File

@@ -11,13 +11,13 @@ export default function OrderItem({ item, currencyCode }: {
}) { }) {
return ( return (
<TableRow className="w-full" data-testid="product-row"> <TableRow className="w-full" data-testid="product-row">
{/* <TableCell className="!pl-0 p-4 w-24"> {/* <TableCell className="px-6 w-24">
<div className="flex w-16"> <div className="flex w-16">
<Thumbnail thumbnail={item.thumbnail} size="square" /> <Thumbnail thumbnail={item.thumbnail} size="square" />
</div> </div>
</TableCell> */} </TableCell> */}
<TableCell className="text-left"> <TableCell className="text-left px-6">
<span <span
className="txt-medium-plus text-ui-fg-base" className="txt-medium-plus text-ui-fg-base"
data-testid="product-name" data-testid="product-name"
@@ -27,8 +27,8 @@ export default function OrderItem({ item, currencyCode }: {
<LineItemOptions variant={item.variant} data-testid="product-variant" /> <LineItemOptions variant={item.variant} data-testid="product-variant" />
</TableCell> </TableCell>
<TableCell className="!pr-0"> <TableCell className="px-6">
<span className="!pr-0 flex flex-col items-end h-full justify-center"> <span className="flex flex-col items-end h-full justify-center">
<span className="flex gap-x-1 "> <span className="flex gap-x-1 ">
<span className="text-ui-fg-muted"> <span className="text-ui-fg-muted">
{item.quantity}x{" "} {item.quantity}x{" "}

View File

@@ -2,7 +2,6 @@ import repeat from "@lib/util/repeat"
import { StoreOrder } from "@medusajs/types" import { StoreOrder } from "@medusajs/types"
import { Table, TableBody } from "@kit/ui/table" import { Table, TableBody } from "@kit/ui/table"
import Divider from "@modules/common/components/divider"
import SkeletonLineItem from "@modules/skeletons/components/skeleton-line-item" import SkeletonLineItem from "@modules/skeletons/components/skeleton-line-item"
import OrderItem from "./order-item" import OrderItem from "./order-item"
import { Heading } from "@kit/ui/heading" import { Heading } from "@kit/ui/heading"
@@ -19,8 +18,7 @@ export default function OrderItems({ order }: {
<Trans i18nKey="cart:orderConfirmed.summary" /> <Trans i18nKey="cart:orderConfirmed.summary" />
</Heading> </Heading>
<div className="flex flex-col"> <div className="flex flex-col">
<Divider className="!mb-0" /> <Table className="rounded-lg border border-separate">
<Table>
<TableBody data-testid="products-table"> <TableBody data-testid="products-table">
{items?.length {items?.length
? items ? items

View File

@@ -21,10 +21,10 @@ export async function register() {
* @param err * @param err
*/ */
export const onRequestError: Instrumentation.onRequestError = async (err) => { export const onRequestError: Instrumentation.onRequestError = async (err) => {
const { getServerMonitoringService } = await import('@kit/monitoring/server'); // const { getServerMonitoringService } = await import('@kit/monitoring/server');
const service = await getServerMonitoringService(); // const service = await getServerMonitoringService();
await service.ready(); // await service.ready();
await service.captureException(err as Error); // await service.captureException(err as Error);
}; };

View File

@@ -73,7 +73,7 @@ export async function handleNavigateToPayment({ language }: { language: string }
currency: cart.currency_code.toUpperCase(), currency: cart.currency_code.toUpperCase(),
description: `Order from Medreport`, description: `Order from Medreport`,
locale: language, locale: language,
merchantReference: `${account.id}:${Date.now()}`, merchantReference: `${account.id}:${cart.id}:${Date.now()}`,
}); });
const { error } = await supabase const { error } = await supabase

View File

@@ -84,7 +84,7 @@ export class MontonioWebhookHandlerService
}, `Received Montonio webhook event`); }, `Received Montonio webhook event`);
if (event.paymentStatus === 'PAID') { if (event.paymentStatus === 'PAID') {
const accountId = event.merchantReferenceDisplay.split(':')[0]; const [accountId] = event.merchantReferenceDisplay.split(':');
if (!accountId) { if (!accountId) {
throw new Error('Invalid merchant reference'); throw new Error('Invalid merchant reference');
} }

View File

@@ -45,7 +45,7 @@
}, },
"orderConfirmed": { "orderConfirmed": {
"title": "Tellimus on edukalt esitatud", "title": "Tellimus on edukalt esitatud",
"summary": "Summa", "summary": "Teenused",
"subtotal": "Vahesumma", "subtotal": "Vahesumma",
"taxes": "Maksud", "taxes": "Maksud",
"giftCard": "Kinkekaart", "giftCard": "Kinkekaart",