diff --git a/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/index.tsx b/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/index.tsx index e2ed330..070776d 100644 --- a/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/index.tsx +++ b/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/index.tsx @@ -10,7 +10,12 @@ import { QrisPaymentStep } from "./steps/qris-payement" import { VAPaymentStep } from "./steps/va-payment" import { SuccessStep } from "./steps/success" import { PaymentStep } from "./steps/payment" -import { TBookSessionRequest, usePostBookSession } from "@imphnen-frontend-service/service" +import { + TBookSessionRequest, + TPayment, + usePostBookSession, + usePostCreatePayment, +} from "@imphnen-frontend-service/service" const STEPS = ['topic', 'schedule', 'profile', 'payment', 'qr-payment', 'va-payment', 'success'] as const type Step = typeof STEPS[number] @@ -19,15 +24,24 @@ type Props = { open: boolean setOpen: (open: boolean) => void mentorId?: string - mentor?: { fullname?: string | null; current_role?: string; current_company?: string } + mentor?: { + fullname?: string | null + current_role?: string + current_company?: string + mentoring_rate?: number + } } export const AppointmentModal: FC = ({ open, setOpen, mentorId, mentor }) => { const [step, setStep] = useState('topic') const [selectedTopics, setSelectedTopics] = useState([]) const [booking, setBooking] = useState(null) + const [payment, setPayment] = useState(null) + const [paymentMethod, setPaymentMethod] = useState<'va' | 'qris'>('va') const [error, setError] = useState("") + const [sessionId, setSessionId] = useState("") const bookMutation = usePostBookSession(mentorId ?? "") + const paymentMutation = usePostCreatePayment(sessionId) const handleStep = async (action: 'next' | 'prev') => { setError("") @@ -37,8 +51,19 @@ export const AppointmentModal: FC = ({ open, setOpen, mentorId, mentor }) return } try { - await bookMutation.mutateAsync({ ...booking, topic: booking.topic || `Mentoring #${selectedTopics[0] ?? 1}` }) - setStep('success') + // 1. Book the session -> get session id + const booked = await bookMutation.mutateAsync({ ...booking, topic: booking.topic || `Mentoring #${selectedTopics[0] ?? 1}` }) + const sid = (booked as unknown as { data?: { id?: string } })?.data?.id ?? "" + if (!sid) { + setError("Gagal membuat sesi mentoring. Coba lagi.") + return + } + setSessionId(sid) + // 2. Create payment for the session with the chosen method + const pm = await paymentMutation.mutateAsync({ method: paymentMethod }) + setPayment(pm) + // 3. Route to the method-specific payment screen + setStep(paymentMethod === 'qris' ? 'qr-payment' : 'va-payment') } catch { setError("Gagal membuat sesi mentoring. Coba lagi.") } @@ -46,6 +71,8 @@ export const AppointmentModal: FC = ({ open, setOpen, mentorId, mentor }) } if (action === 'next' && step === 'success') { setOpen(false) + } else if (action === 'next' && (step === 'qr-payment' || step === 'va-payment')) { + setStep('success') } else if (action === 'next') { setStep(STEPS[STEPS.indexOf(step) + 1]) } else if (action === 'prev' && step !== 'topic') { @@ -135,9 +162,9 @@ export const AppointmentModal: FC = ({ open, setOpen, mentorId, mentor }) {step === 'topic' && } {step === 'schedule' && setBooking(data)} />} {step === 'profile' && } - {step === 'payment' && } - {step === 'qr-payment' && } - {step === 'va-payment' && } + {step === 'payment' && } + {step === 'qr-payment' && } + {step === 'va-payment' && } {step === 'success' && } diff --git a/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/payment.tsx b/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/payment.tsx index e0e804b..117b4b1 100644 --- a/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/payment.tsx +++ b/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/payment.tsx @@ -1,18 +1,32 @@ import { For, Show } from "@imphnen-frontend-service/utils" -import { FC } from "react" +import { FC, useState } from "react" import { motion } from "framer-motion" import { TOPICS } from "../../../sections/topics" import { TBookSessionRequest } from "@imphnen-frontend-service/service" type Props = { selectedTopics: number[] - mentor?: { fullname?: string | null; current_role?: string; current_company?: string } | null + mentor?: { + fullname?: string | null; + current_role?: string; + current_company?: string; + mentoring_rate?: number; + } | null booking?: TBookSessionRequest | null + onMethodChange: (method: 'va' | 'qris') => void } -export const PaymentStep: FC = ({ selectedTopics, mentor, booking }) => { - const rate = 50000 +const BANK_OPTIONS = ['BCA', 'BRI', 'BNI', 'MANDIRI'] + +export const PaymentStep: FC = ({ selectedTopics, mentor, booking, onMethodChange }) => { + const rate = mentor?.mentoring_rate ?? 50000 const serviceFee = 2000 + const [method, setMethod] = useState<'va' | 'qris'>('va') + + const handleMethod = (next: 'va' | 'qris') => { + setMethod(next) + onMethodChange(next) + } return ( = ({ selectedTopics, mentor, booking }) => {

Virtual Account

-
- +
+ {(bank, index) => (
handleMethod('va')} > - -
@@ -96,11 +111,12 @@ export const PaymentStep: FC = ({ selectedTopics, mentor, booking }) => {

QRIS

-
+
handleMethod('qris')} > - + diff --git a/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/qris-payement.tsx b/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/qris-payement.tsx index d17b546..0145756 100644 --- a/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/qris-payement.tsx +++ b/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/qris-payement.tsx @@ -1,5 +1,7 @@ -import { For } from "@imphnen-frontend-service/utils" +import { For, Show } from "@imphnen-frontend-service/utils" +import { FC } from "react" import { motion } from "framer-motion" +import { TPayment } from "@imphnen-frontend-service/service" const PAYMENT_STEP = [ 'Buka aplikasi e-wallet atau m-banking kamu', @@ -8,7 +10,11 @@ const PAYMENT_STEP = [ 'Konfimasi pembayaran, dan proses selesai.' ] -export const QrisPaymentStep = () => { +type Props = { + payment?: TPayment | null +} + +export const QrisPaymentStep: FC = ({ payment }) => { return ( {

Biaya yang harus dibayarkan

-

Rp. 52.000

+

+ Rp. {(payment?.total ?? 52000).toLocaleString("id-ID")} +

+ +

+ Ref: {payment?.external_ref} +

+
@@ -49,7 +62,10 @@ export const QrisPaymentStep = () => {
QR Code
+

+ Berlaku hingga {payment ? new Date(payment.expires_at).toLocaleString("id-ID") : "-"} +

) -} +} \ No newline at end of file diff --git a/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/va-payment.tsx b/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/va-payment.tsx index 05c3474..ad1c4b0 100644 --- a/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/va-payment.tsx +++ b/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/va-payment.tsx @@ -1,5 +1,7 @@ -import { For } from "@imphnen-frontend-service/utils" +import { For, Show } from "@imphnen-frontend-service/utils" +import { FC } from "react" import { motion } from "framer-motion" +import { TPayment } from "@imphnen-frontend-service/service" const PAYMENT_STEP = [ 'Buka aplikasi e-wallet atau m-banking kamu', @@ -8,7 +10,11 @@ const PAYMENT_STEP = [ 'Konfimasi pembayaran, dan proses selesai.' ] -export const VAPaymentStep = () => { +type Props = { + payment?: TPayment | null +} + +export const VAPaymentStep: FC = ({ payment }) => { return ( {

Cara melakukan pembayaran

-
    +
      {(step, index) => (
    • @@ -33,6 +39,15 @@ export const VAPaymentStep = () => { )}
    + +
    +

    + Biaya yang harus dibayarkan +

    +

    + Rp. {(payment?.total ?? 52000).toLocaleString("id-ID")} +

    +
@@ -44,12 +59,17 @@ export const VAPaymentStep = () => { Kode Virtual Account

- 8091239861969812 + {payment?.external_ref || "8091239861969812"}

- A/N Unknown + A/N IMPHNEN

+ +

+ Berlaku hingga {new Date(payment!.expires_at).toLocaleString("id-ID")} +

+
) -} +} \ No newline at end of file diff --git a/libs/service/src/api/dimentorin/index.ts b/libs/service/src/api/dimentorin/index.ts index a3ea0ee..f60b464 100644 --- a/libs/service/src/api/dimentorin/index.ts +++ b/libs/service/src/api/dimentorin/index.ts @@ -3,14 +3,16 @@ import { TArticleDetail, TArticleListItem, TArticleListParams, + TBookSessionRequest, + TCreatePaymentRequest, TMentorAvailability, TMentorDetail, TMentorListParams, TMentorRegisterRequest, TMentorStats, + TPayment, TSessionFeedbackRequest, TSessionListResponse, - TBookSessionRequest, } from '../../types/dimentorin'; import { TResponseMessage } from '../../types/common'; @@ -154,3 +156,41 @@ export const postRegisterMentor = async ( }); return data; }; + +export const postCreatePayment = async ( + sessionId: string, + payload: TCreatePaymentRequest +): Promise => { + const { data } = await api({ + method: 'POST', + url: `/dimentorin/payments/sessions/${sessionId}/create`, + data: payload, + }); + return data.data; +}; + +export const getMyPayments = async (): Promise => { + const { data } = await api({ + method: 'GET', + url: '/dimentorin/payments/me', + }); + return data.data; +}; + +export const getPaymentById = async (id: string): Promise => { + const { data } = await api({ + method: 'GET', + url: `/dimentorin/payments/${id}`, + }); + return data.data; +}; + +export const postConfirmPayment = async ( + id: string +): Promise => { + const { data } = await api({ + method: 'POST', + url: `/dimentorin/payments/${id}/confirm`, + }); + return data; +}; diff --git a/libs/service/src/hooks/dimentorin/index.ts b/libs/service/src/hooks/dimentorin/index.ts index 58b5591..83659c3 100644 --- a/libs/service/src/hooks/dimentorin/index.ts +++ b/libs/service/src/hooks/dimentorin/index.ts @@ -11,8 +11,12 @@ import { getMentorSessions, getMentorStats, getMentors, + getMyPayments, getMySessions, + getPaymentById, postBookSession, + postConfirmPayment, + postCreatePayment, postRegisterMentor, postSessionFeedback, } from '../../api/dimentorin'; @@ -21,11 +25,13 @@ import { TArticleListItem, TArticleListParams, TBookSessionRequest, + TCreatePaymentRequest, TMentorAvailability, TMentorDetail, TMentorListParams, TMentorRegisterRequest, TMentorStats, + TPayment, TSessionFeedbackRequest, TSessionListResponse, } from '../../types/dimentorin'; @@ -134,6 +140,41 @@ export const usePostRegisterMentor = () => { }); }; +export const usePostCreatePayment = (sessionId: string) => { + return useMutation({ + mutationKey: ['post-create-payment', sessionId], + mutationFn: async (payload: TCreatePaymentRequest) => + await postCreatePayment(sessionId, payload), + }); +}; + +export const useGetMyPayments = (): UseQueryResult< + TPayment[], + TResponseError +> => { + return useQuery({ + queryKey: ['get-my-payments'], + queryFn: async () => await getMyPayments(), + }); +}; + +export const useGetPaymentById = ( + id: string +): UseQueryResult => { + return useQuery({ + queryKey: ['get-payment-by-id', id], + queryFn: async () => await getPaymentById(id), + enabled: !!id, + }); +}; + +export const usePostConfirmPayment = () => { + return useMutation({ + mutationKey: ['post-confirm-payment'], + mutationFn: async (id: string) => await postConfirmPayment(id), + }); +}; + export const useGetArticles = ( params?: TArticleListParams ): UseQueryResult => { diff --git a/libs/service/src/types/dimentorin/index.ts b/libs/service/src/types/dimentorin/index.ts index 0fa9ec3..807683d 100644 --- a/libs/service/src/types/dimentorin/index.ts +++ b/libs/service/src/types/dimentorin/index.ts @@ -162,3 +162,22 @@ export type TMentorRegisterRequest = { mentoring_rate_amount: number; }; }; + +export type TPayment = { + id: string; + session_id: string; + mentor_id: string; + amount: number; + service_fee: number; + total: number; + method: string; + provider: string; + status: string; + external_ref?: string | null; + expires_at: string; + created_at: string; +}; + +export type TCreatePaymentRequest = { + method: string; // 'va' | 'qris' | 'manual' +};