feat(dimentorin): my sessions + mentor dashboard with live payment status
- /mentoring/my-sessions: mentee session list w/ payment status (Lunas/Menunggu), Cek Status button (Midtrans auto-paid refresh), Detail Mentor link - /mentoring/mentor-dashboard: mentor session requests w/ payment panel (Refresh + Konfirmasi) - header menu: Sesi Saya - TPayment type fixed to camelCase (sessionId/mentorId/externalRef) matching backend DTO - QRIS step renders real Midtrans QR from qr_string via qrserver API - e2e verified: sessions render, refresh + confirm payment buttons work end-to-end
This commit is contained in:
@@ -8,6 +8,7 @@ import { Link, NavLink, useLocation } from "react-router-dom";
|
||||
const MENUS: { label: string; href: string }[] = [
|
||||
{ label: "Home", href: "/" },
|
||||
{ label: 'Mentoring', href: '/mentoring' },
|
||||
{ label: 'Sesi Saya', href: '/mentoring/my-sessions' },
|
||||
{ label: 'Resources', href: '/resources' },
|
||||
{ label: 'Articles', href: '/articles' },
|
||||
]
|
||||
|
||||
+13
-4
@@ -47,9 +47,9 @@ export const QrisPaymentStep: FC<Props> = ({ payment }) => {
|
||||
<p className="text-xs font-semibold text-neutral-700 md:text-[15px]">
|
||||
Rp. {(payment?.total ?? 52000).toLocaleString("id-ID")}
|
||||
</p>
|
||||
<Show condition={!!payment?.external_ref && payment.provider !== 'midtrans'}>
|
||||
<Show condition={!!payment?.externalRef && payment.provider !== 'midtrans'}>
|
||||
<p className="text-[10px] text-neutral-400 mt-1 font-medium">
|
||||
Ref: {payment?.external_ref}
|
||||
Ref: {payment?.externalRef}
|
||||
</p>
|
||||
</Show>
|
||||
</div>
|
||||
@@ -60,10 +60,19 @@ export const QrisPaymentStep: FC<Props> = ({ payment }) => {
|
||||
QR Code Pembayaran
|
||||
</p>
|
||||
<div className="bg-primary-50 p-2.5 rounded-lg size-[120px] mx-auto md:size-[142px] md:me-0">
|
||||
<img src="/image/sample-qrcode.webp" alt="QR Code" className="w-full" />
|
||||
<Show
|
||||
condition={!!payment?.externalRef && payment.provider === 'midtrans'}
|
||||
fallback={<img src="/image/sample-qrcode.webp" alt="QR Code" className="w-full" />}
|
||||
>
|
||||
<img
|
||||
src={`https://api.qrserver.com/v1/create-qr-code/?size=240x240&data=${encodeURIComponent(payment!.externalRef!)}`}
|
||||
alt="QR Code Midtrans"
|
||||
className="w-full"
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
<p className="text-[8px] text-neutral-500 text-center mt-2 md:text-[10px]">
|
||||
Berlaku hingga {payment ? new Date(payment.expires_at).toLocaleString("id-ID") : "-"}
|
||||
Berlaku hingga {payment ? new Date(payment.expiresAt).toLocaleString("id-ID") : "-"}
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
+3
-3
@@ -59,14 +59,14 @@ export const VAPaymentStep: FC<Props> = ({ payment }) => {
|
||||
Kode Virtual Account
|
||||
</p>
|
||||
<p className="text-xs font-semibold text-neutral-700 mb-1.5 md:text-[15px]">
|
||||
{payment?.external_ref || "8091239861969812"}
|
||||
{payment?.externalRef || "8091239861969812"}
|
||||
</p>
|
||||
<p className="text-[8px] text-neutral-400 md:text-[10px]">
|
||||
A/N IMPHNEN
|
||||
</p>
|
||||
<Show condition={!!payment?.expires_at}>
|
||||
<Show condition={!!payment?.expiresAt}>
|
||||
<p className="text-[8px] text-neutral-500 mt-2 md:text-[10px]">
|
||||
Berlaku hingga {new Date(payment!.expires_at).toLocaleString("id-ID")}
|
||||
Berlaku hingga {new Date(payment!.expiresAt).toLocaleString("id-ID")}
|
||||
</p>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
import { FC, ReactElement } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||
import { For, Show, cn } from '@imphnen-frontend-service/utils';
|
||||
import {
|
||||
useGetMentorMe,
|
||||
useGetMentorSessions,
|
||||
useGetSessionPayments,
|
||||
usePostConfirmPayment,
|
||||
usePostRefreshPayment,
|
||||
} from '@imphnen-frontend-service/service';
|
||||
import { ReloadOutlined, CheckOutlined } from '@ant-design/icons';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
const STATUS_STYLE: Record<string, string> = {
|
||||
pending: 'bg-amber-100 text-amber-700',
|
||||
confirmed: 'bg-emerald-100 text-emerald-700',
|
||||
completed: 'bg-primary-100 text-primary-700',
|
||||
cancelled: 'bg-red-100 text-red-600',
|
||||
};
|
||||
|
||||
const PAYMENT_STYLE: Record<string, string> = {
|
||||
pending: 'bg-amber-100 text-amber-700',
|
||||
paid: 'bg-emerald-100 text-emerald-700',
|
||||
expired: 'bg-neutral-200 text-neutral-600',
|
||||
};
|
||||
|
||||
const formatDate = (iso: string) => {
|
||||
try {
|
||||
return new Date(iso).toLocaleString('id-ID', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
});
|
||||
} catch {
|
||||
return iso;
|
||||
}
|
||||
};
|
||||
|
||||
const formatRupiah = (n: number) =>
|
||||
new Intl.NumberFormat('id-ID', {
|
||||
style: 'currency',
|
||||
currency: 'IDR',
|
||||
maximumFractionDigits: 0,
|
||||
}).format(n);
|
||||
|
||||
const SessionCard: FC<{ sessionId: string }> = ({ sessionId }) => {
|
||||
const { data: payments } = useGetSessionPayments(sessionId);
|
||||
const refreshPayment = usePostRefreshPayment();
|
||||
const confirmPayment = usePostConfirmPayment();
|
||||
|
||||
const payment = (payments ?? [])[0];
|
||||
|
||||
return (
|
||||
<Show condition={!!payment && payment.status === 'pending'}>
|
||||
<div className="flex flex-wrap items-center justify-between gap-2 px-3 py-2 rounded-md text-xs font-medium bg-neutral-50 border border-neutral-100">
|
||||
<span
|
||||
className={cn(
|
||||
'px-2 py-0.5 rounded-full capitalize',
|
||||
PAYMENT_STYLE[payment?.status ?? ''] ?? ''
|
||||
)}
|
||||
>
|
||||
Pembayaran: {payment?.status}
|
||||
</span>
|
||||
<span>{payment ? formatRupiah(payment.total) : ''}</span>
|
||||
<div className="flex gap-2">
|
||||
<Show
|
||||
condition={!!payment && payment.provider === 'midtrans'}
|
||||
>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
className="gap-2"
|
||||
disabled={refreshPayment.isPending}
|
||||
onClick={async () => {
|
||||
try {
|
||||
if (payment) await refreshPayment.mutateAsync(payment.id);
|
||||
toast.success('Status pembayaran diperbarui');
|
||||
} catch {
|
||||
toast.error('Gagal memperbarui status');
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ReloadOutlined /> Refresh
|
||||
</Button>
|
||||
</Show>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="success"
|
||||
className="gap-2"
|
||||
disabled={confirmPayment.isPending}
|
||||
onClick={async () => {
|
||||
try {
|
||||
if (payment) await confirmPayment.mutateAsync(payment.id);
|
||||
toast.success('Pembayaran dikonfirmasi ✅');
|
||||
} catch {
|
||||
toast.error('Gagal mengonfirmasi pembayaran');
|
||||
}
|
||||
}}
|
||||
>
|
||||
<CheckOutlined /> Konfirmasi
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
export const Components: FC = (): ReactElement => {
|
||||
const { data: mentor, isLoading: loadingMentor } = useGetMentorMe();
|
||||
const mentorId = mentor?.id ?? '';
|
||||
const { data: sessions, isLoading } = useGetMentorSessions(mentorId);
|
||||
|
||||
return (
|
||||
<main className="w-full px-8 py-12 md:px-[60px] lg:px-20">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-2xl font-bold text-neutral-800 md:text-3xl">
|
||||
Dashboard Mentor
|
||||
</h1>
|
||||
<p className="text-sm text-neutral-500 mt-1">
|
||||
Permintaan sesi yang masuk ke kamu beserta status pembayarannya.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Show
|
||||
condition={!loadingMentor && !mentor}
|
||||
fallback={
|
||||
<Show
|
||||
condition={isLoading}
|
||||
fallback={
|
||||
<Show
|
||||
condition={(sessions?.sessions?.length ?? 0) > 0}
|
||||
fallback={
|
||||
<div className="bg-white rounded-lg p-12 text-center">
|
||||
<p className="text-neutral-500">
|
||||
Belum ada permintaan sesi mentoring.
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<For data={sessions?.sessions ?? []}>
|
||||
{(session) => (
|
||||
<div
|
||||
key={session.id}
|
||||
className="bg-white rounded-lg p-5 shadow-sm border border-neutral-100"
|
||||
>
|
||||
<div className="flex justify-between items-start gap-3 mb-3">
|
||||
<h3 className="font-semibold text-neutral-800 line-clamp-2">
|
||||
{session.topic}
|
||||
</h3>
|
||||
<span
|
||||
className={cn(
|
||||
'shrink-0 px-3 py-1 rounded-full text-[11px] font-medium capitalize',
|
||||
STATUS_STYLE[session.status] ?? 'bg-neutral-100 text-neutral-600'
|
||||
)}
|
||||
>
|
||||
{session.status}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-neutral-500 mb-1">
|
||||
📅 {formatDate(session.scheduled_at)}
|
||||
</p>
|
||||
<p className="text-sm text-neutral-500 mb-3">
|
||||
🧑🎓{' '}
|
||||
{session.mentee_fullname ?? session.mentee_email ?? 'Mentee'}
|
||||
{' · '}
|
||||
{session.session_type === 'video_call'
|
||||
? 'Video Call'
|
||||
: 'Chat'}
|
||||
</p>
|
||||
|
||||
<SessionCard sessionId={session.id} />
|
||||
|
||||
<div className="mt-3">
|
||||
<Link to={`/mentoring/${session.mentor_id}`}>
|
||||
<Button size="sm" variant="bordered">
|
||||
Lihat Detail
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
}
|
||||
>
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
{[0, 1].map((i) => (
|
||||
<div key={i} className="bg-white rounded-lg p-5 animate-pulse">
|
||||
<div className="h-4 bg-neutral-200 rounded w-2/3 mb-3" />
|
||||
<div className="h-3 bg-neutral-200 rounded w-1/2 mb-2" />
|
||||
<div className="h-3 bg-neutral-200 rounded w-1/3" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Show>
|
||||
}
|
||||
>
|
||||
<div className="bg-white rounded-lg p-12 text-center">
|
||||
<p className="text-neutral-500 mb-4">
|
||||
Kamu belum terdaftar sebagai mentor. Daftar dulu untuk menerima
|
||||
permintaan sesi.
|
||||
</p>
|
||||
<Link to="/auth/register-mentor">
|
||||
<Button variant="primary">Daftar Jadi Mentor</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
export default Components;
|
||||
@@ -0,0 +1,191 @@
|
||||
import { FC, ReactElement } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||
import { For, Show, cn } from '@imphnen-frontend-service/utils';
|
||||
import {
|
||||
useGetMySessions,
|
||||
useGetMyPayments,
|
||||
usePostRefreshPayment,
|
||||
} from '@imphnen-frontend-service/service';
|
||||
import { ReloadOutlined } from '@ant-design/icons';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
const STATUS_STYLE: Record<string, string> = {
|
||||
pending: 'bg-amber-100 text-amber-700',
|
||||
confirmed: 'bg-emerald-100 text-emerald-700',
|
||||
completed: 'bg-primary-100 text-primary-700',
|
||||
cancelled: 'bg-red-100 text-red-600',
|
||||
};
|
||||
|
||||
const PAYMENT_STYLE: Record<string, string> = {
|
||||
pending: 'bg-amber-100 text-amber-700',
|
||||
paid: 'bg-emerald-100 text-emerald-700',
|
||||
expired: 'bg-neutral-200 text-neutral-600',
|
||||
};
|
||||
|
||||
const formatDate = (iso: string) => {
|
||||
try {
|
||||
return new Date(iso).toLocaleString('id-ID', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
});
|
||||
} catch {
|
||||
return iso;
|
||||
}
|
||||
};
|
||||
|
||||
const formatRupiah = (n: number) =>
|
||||
new Intl.NumberFormat('id-ID', {
|
||||
style: 'currency',
|
||||
currency: 'IDR',
|
||||
maximumFractionDigits: 0,
|
||||
}).format(n);
|
||||
|
||||
export const Components: FC = (): ReactElement => {
|
||||
const { data: sessions, isLoading } = useGetMySessions();
|
||||
const { data: payments } = useGetMyPayments();
|
||||
const refreshPayment = usePostRefreshPayment();
|
||||
|
||||
const paymentBySession = new Map(
|
||||
(payments ?? []).map((p) => [p.sessionId, p])
|
||||
);
|
||||
|
||||
const handleRefresh = async (paymentId: string) => {
|
||||
try {
|
||||
await refreshPayment.mutateAsync(paymentId);
|
||||
const updated = paymentBySession.get(
|
||||
(payments ?? []).find((p) => p.id === paymentId)?.sessionId ?? ''
|
||||
);
|
||||
toast.success(
|
||||
updated?.status === 'paid'
|
||||
? 'Pembayaran berhasil dikonfirmasi otomatis ✅'
|
||||
: 'Status pembayaran masih menunggu pembayaran'
|
||||
);
|
||||
} catch {
|
||||
toast.error('Gagal memperbarui status pembayaran');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<main className="w-full px-8 py-12 md:px-[60px] lg:px-20">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-2xl font-bold text-neutral-800 md:text-3xl">
|
||||
Sesi Mentoring Saya
|
||||
</h1>
|
||||
<p className="text-sm text-neutral-500 mt-1">
|
||||
Pantau jadwal, status pembayaran, dan konfirmasi sesi kamu.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Show
|
||||
condition={isLoading}
|
||||
fallback={
|
||||
<Show
|
||||
condition={(sessions?.sessions?.length ?? 0) > 0}
|
||||
fallback={
|
||||
<div className="bg-white rounded-lg p-12 text-center">
|
||||
<p className="text-neutral-500 mb-4">
|
||||
Kamu belum memiliki sesi mentoring.
|
||||
</p>
|
||||
<Link to="/mentoring">
|
||||
<Button variant="primary">Cari Mentor</Button>
|
||||
</Link>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
{(sessions?.sessions ?? []).map((session) => {
|
||||
if (!session) return null;
|
||||
const payment = paymentBySession.get(session.id);
|
||||
return (
|
||||
<div
|
||||
key={session.id}
|
||||
className="bg-white rounded-lg p-5 shadow-sm border border-neutral-100"
|
||||
>
|
||||
<div className="flex justify-between items-start gap-3 mb-3">
|
||||
<h3 className="font-semibold text-neutral-800 line-clamp-2">
|
||||
{session.topic}
|
||||
</h3>
|
||||
<span
|
||||
className={cn(
|
||||
'shrink-0 px-3 py-1 rounded-full text-[11px] font-medium capitalize',
|
||||
STATUS_STYLE[session.status] ?? 'bg-neutral-100 text-neutral-600'
|
||||
)}
|
||||
>
|
||||
{session.status}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-neutral-500 mb-1">
|
||||
📅 {formatDate(session.scheduled_at)}
|
||||
</p>
|
||||
<p className="text-sm text-neutral-500 mb-4">
|
||||
⏱️ {session.duration_minutes} menit ·{' '}
|
||||
{session.session_type === 'video_call'
|
||||
? 'Video Call'
|
||||
: 'Chat'}
|
||||
</p>
|
||||
|
||||
<Show condition={!!payment}>
|
||||
<div
|
||||
className={cn(
|
||||
'flex justify-between items-center px-3 py-2 rounded-md text-xs font-medium mb-3',
|
||||
PAYMENT_STYLE[payment?.status ?? ''] ?? ''
|
||||
)}
|
||||
>
|
||||
<span className="capitalize">
|
||||
{payment?.status === 'paid' ? 'Lunas' : 'Menunggu pembayaran'}
|
||||
</span>
|
||||
<span>{payment ? formatRupiah(payment.total) : ''}</span>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Show
|
||||
condition={!!payment && payment.status === 'pending' && payment.provider === 'midtrans'}
|
||||
>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
className="gap-2"
|
||||
disabled={refreshPayment.isPending}
|
||||
onClick={() => payment && handleRefresh(payment.id)}
|
||||
>
|
||||
<ReloadOutlined /> Cek Status
|
||||
</Button>
|
||||
</Show>
|
||||
<Show condition={session.status === 'confirmed'}>
|
||||
<Link to={`/mentoring/${session.mentor_id}`}>
|
||||
<Button size="sm" variant="bordered">
|
||||
Detail Mentor
|
||||
</Button>
|
||||
</Link>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Show>
|
||||
}
|
||||
>
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
{[0, 1].map((i) => (
|
||||
<div key={i} className="bg-white rounded-lg p-5 animate-pulse">
|
||||
<div className="h-4 bg-neutral-200 rounded w-2/3 mb-3" />
|
||||
<div className="h-3 bg-neutral-200 rounded w-1/2 mb-2" />
|
||||
<div className="h-3 bg-neutral-200 rounded w-1/3" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
export default Components;
|
||||
@@ -185,6 +185,16 @@ export const getPaymentById = async (id: string): Promise<TPayment> => {
|
||||
return data.data;
|
||||
};
|
||||
|
||||
export const getSessionPayments = async (
|
||||
sessionId: string
|
||||
): Promise<TPayment[]> => {
|
||||
const { data } = await api({
|
||||
method: 'GET',
|
||||
url: `/dimentorin/payments/session/${sessionId}`,
|
||||
});
|
||||
return data.data;
|
||||
};
|
||||
|
||||
export const postConfirmPayment = async (
|
||||
id: string
|
||||
): Promise<TResponseMessage> => {
|
||||
@@ -194,3 +204,13 @@ export const postConfirmPayment = async (
|
||||
});
|
||||
return data;
|
||||
};
|
||||
|
||||
export const postRefreshPayment = async (
|
||||
id: string
|
||||
): Promise<TPayment> => {
|
||||
const { data } = await api({
|
||||
method: 'POST',
|
||||
url: `/dimentorin/payments/${id}/refresh`,
|
||||
});
|
||||
return data.data;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useMutation, useQuery, UseQueryResult } from '@tanstack/react-query';
|
||||
import { useMutation, useQuery, useQueryClient, UseQueryResult } from '@tanstack/react-query';
|
||||
import {
|
||||
getArticleById,
|
||||
getArticleBySlug,
|
||||
@@ -14,9 +14,11 @@ import {
|
||||
getMyPayments,
|
||||
getMySessions,
|
||||
getPaymentById,
|
||||
getSessionPayments,
|
||||
postBookSession,
|
||||
postConfirmPayment,
|
||||
postCreatePayment,
|
||||
postRefreshPayment,
|
||||
postRegisterMentor,
|
||||
postSessionFeedback,
|
||||
} from '../../api/dimentorin';
|
||||
@@ -168,10 +170,39 @@ export const useGetPaymentById = (
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetSessionPayments = (
|
||||
sessionId: string
|
||||
): UseQueryResult<TPayment[], TResponseError> => {
|
||||
return useQuery({
|
||||
queryKey: ['get-session-payments', sessionId],
|
||||
queryFn: async () => await getSessionPayments(sessionId),
|
||||
enabled: !!sessionId,
|
||||
});
|
||||
};
|
||||
|
||||
export const usePostConfirmPayment = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationKey: ['post-confirm-payment'],
|
||||
mutationFn: async (id: string) => await postConfirmPayment(id),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['get-my-payments'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['get-my-sessions'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['get-mentor-sessions'] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const usePostRefreshPayment = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationKey: ['post-refresh-payment'],
|
||||
mutationFn: async (id: string) => await postRefreshPayment(id),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['get-my-payments'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['get-my-sessions'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['get-mentor-sessions'] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -165,17 +165,18 @@ export type TMentorRegisterRequest = {
|
||||
|
||||
export type TPayment = {
|
||||
id: string;
|
||||
session_id: string;
|
||||
mentor_id: string;
|
||||
sessionId: string;
|
||||
mentorId: string;
|
||||
amount: number;
|
||||
service_fee: number;
|
||||
serviceFee: number;
|
||||
total: number;
|
||||
method: string;
|
||||
provider: string;
|
||||
status: string;
|
||||
external_ref?: string | null;
|
||||
expires_at: string;
|
||||
created_at: string;
|
||||
externalRef?: string | null;
|
||||
providerOrderId?: string | null;
|
||||
expiresAt: string;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
export type TCreatePaymentRequest = {
|
||||
|
||||
Reference in New Issue
Block a user