Compare commits

...

4 Commits

Author SHA1 Message Date
arifal ecd4dc2ef4 0.9.0 2026-05-08 20:59:06 +07:00
arifal 2223a4630e add booking and payment schema 2026-05-08 20:59:01 +07:00
arifal c9c4c0e683 0.8.0 2026-05-08 20:43:42 +07:00
arifal d4db13778a create payment roadmap pr a 2026-05-08 20:43:14 +07:00
31 changed files with 6375 additions and 169 deletions
+250
View File
@@ -0,0 +1,250 @@
# Setrip — Payment Roadmap
Rencana implementasi flow pembayaran: handle trip gratis, halaman detail payment, dan integrasi Midtrans.
> **Prinsip:** Sumber kebenaran pembayaran = record di tabel khusus, bukan timestamp di `TripParticipant`. Manual transfer & Midtrans = dua provider dari pipeline yang sama. Trip gratis = first-class case (bukan paid trip dengan amount 0).
---
## Audit state sekarang (baseline)
- Field `TripParticipant.markedPaidAt` + `paymentConfirmedAt` — dual-timestamp manual.
- Flow: user klik "Saya sudah bayar" → organizer klik "Konfirmasi pembayaran". Tidak ada record transaksi terpisah, tidak ada bukti transfer, tidak ada amount snapshot.
- Tidak ada halaman `/trips/[id]/payment`. Tombol bayar inline di trip detail.
- **Bug**: trip gratis (`price === 0`) tetap melewati flow yang sama — peserta tetap harus klik "Saya sudah bayar" dan organizer tetap harus konfirmasi.
File terkait: [server/services/trip.service.ts](server/services/trip.service.ts) (`markParticipantPayment`, `confirmParticipantPayment`), [features/booking/actions.ts](features/booking/actions.ts), [features/booking/components/organizer-payment-queue.tsx](features/booking/components/organizer-payment-queue.tsx), [features/trip/components/join-trip-button.tsx](features/trip/components/join-trip-button.tsx), [server/repositories/participant.repo.ts](server/repositories/participant.repo.ts).
---
## PR A — Free trip handling + halaman detail payment (manual flow) ✅
Selesai. `tsc --noEmit` lulus. Tanpa schema baru, tanpa migration.
**Keputusan akses control:** halaman `/trips/[id]/payment` boleh diakses peserta apapun yang aktif (PENDING/CONFIRMED). Peserta PENDING bisa lihat nominal + rekening untuk persiapan, tapi diberi notice "tunggu approve dulu sebelum transfer". Organizer trip-nya sendiri di-redirect ke trip detail.
| # | Item | Status | File |
|---|---|---|---|
| A1 | Helper `lib/trip-pricing.ts` dengan `isFreeTrip` & `isPaidTrip` | ✅ | [lib/trip-pricing.ts](lib/trip-pricing.ts) |
| A2 | Service guard: `markParticipantPayment` & `confirmParticipantPayment` reject trip gratis | ✅ | [server/services/trip.service.ts](server/services/trip.service.ts) |
| A3 | UI gate di JoinTripButton: hide flow pembayaran kalau gratis | ✅ | [features/trip/components/join-trip-button.tsx](features/trip/components/join-trip-button.tsx) |
| A4 | UI gate di trip detail: skip `OrganizerPaymentQueue` kalau gratis | ✅ | [app/trips/[id]/page.tsx](app/trips/[id]/page.tsx) |
| A5 | Halaman `/trips/[id]/payment` (server component dengan akses kontrol) | ✅ | [app/trips/[id]/payment/page.tsx](app/trips/%5Bid%5D/payment/page.tsx) |
| A6 | Konten halaman trip gratis: banner 🎉 + status keikutsertaan + CTA | ✅ | `FreeTripSection` di [page.tsx](app/trips/%5Bid%5D/payment/page.tsx) |
| A7 | Konten halaman trip berbayar: timeline + rekening organizer + tombol "Saya sudah bayar" + status | ✅ | `PaidTripSection`, `PaymentTimeline`, `BankRow` di [page.tsx](app/trips/%5Bid%5D/payment/page.tsx) |
| A8 | Link dari trip detail → `/trips/[id]/payment` (replace tombol inline) | ✅ | `showPaymentLink` di [join-trip-button.tsx](features/trip/components/join-trip-button.tsx) |
| A9 | Metadata + `robots: noindex` halaman payment | ✅ | [page.tsx](app/trips/%5Bid%5D/payment/page.tsx) — `metadata.robots: { index: false, follow: false }` |
| A+ | `MarkPaidButton` (client component) untuk action "Saya sudah bayar" | ✅ | [features/booking/components/mark-paid-button.tsx](features/booking/components/mark-paid-button.tsx) |
| A+ | `CopyButton` (client component) untuk copy nomor rekening / nominal | ✅ | [features/booking/components/copy-button.tsx](features/booking/components/copy-button.tsx) |
**Tindakan manual:** tidak ada. Tidak ada migration di PR A.
**Catatan edge case yang sudah dihandle:**
- Organizer trip-nya sendiri buka halaman → redirect ke trip detail.
- User belum login → redirect ke login dengan `callbackUrl`.
- User belum join / sudah cancel → tampil notice "kamu belum terdaftar".
- Trip dengan organizer yang belum APPROVED verifikasinya → tampil notice "rekening belum tersedia, hubungi organizer langsung" (tidak crash).
- Peserta PENDING di-warning "tunggu approve dulu sebelum transfer".
- Tombol "Saya sudah bayar" hanya muncul untuk CONFIRMED + belum mark + bank tersedia.
---
## PR B — Refactor schema ke `Booking` + `Payment` (provider MANUAL only) ✅
Selesai. `tsc --noEmit` lulus. Pondasi untuk Midtrans sudah siap.
### Schema (target)
```prisma
model Booking {
id String @id @default(cuid())
tripId String
trip Trip @relation(fields: [tripId], references: [id])
userId String
user User @relation(fields: [userId], references: [id])
participantId String @unique
participant TripParticipant @relation(fields: [participantId], references: [id])
/// Snapshot harga saat booking dibuat — protect dari perubahan trip.price.
amount Int
currency String @default("IDR")
status BookingStatus @default(PENDING)
payments Payment[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([tripId, status])
@@index([userId])
}
enum BookingStatus {
PENDING // join belum approve atau belum bayar
AWAITING_PAY // approved, tinggal bayar
PAID // lunas (manual confirm atau gateway settlement)
CANCELLED
REFUNDED
EXPIRED
}
model Payment {
id String @id @default(cuid())
bookingId String
booking Booking @relation(fields: [bookingId], references: [id])
provider PaymentProvider
/// order_id eksternal — untuk MANUAL referensi internal, untuk MIDTRANS dikirim ke gateway. Unik per attempt.
externalOrderId String @unique
externalTxId String?
method String? // bca_va, gopay, qris, manual_transfer, dst
amount Int
status PaymentStatus @default(PENDING)
rawCallback Json?
snapToken String?
expiresAt DateTime?
paidAt DateTime?
failedAt DateTime?
rejectionReason String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([bookingId, status])
@@index([provider, status])
}
enum PaymentProvider {
MANUAL
MIDTRANS
}
enum PaymentStatus {
PENDING
AWAITING
PAID
FAILED
EXPIRED
CANCELLED
REFUNDED
}
```
### Tugas
| # | Item | Status | File |
|---|---|---|---|
| B1 | Schema: tambah `Booking`, `Payment`, 3 enum (`BookingStatus`, `PaymentProvider`, `PaymentStatus`) + relasi di `User`, `Trip`, `TripParticipant` | ✅ | [prisma/schema.prisma](prisma/schema.prisma) |
| B2 | Migration `add_booking_payment` (CreateEnum + CreateTable + Index + FK) | ✅ | [prisma/migrations/20260508150000_add_booking_payment/migration.sql](prisma/migrations/20260508150000_add_booking_payment/migration.sql) |
| B3 | Backfill script TS (idempotent, skip baris yang sudah punya Booking) | ✅ | [prisma/backfill-bookings.ts](prisma/backfill-bookings.ts) |
| B4 | `bookingRepo` + `paymentRepo` | ✅ | [server/repositories/booking.repo.ts](server/repositories/booking.repo.ts), [server/repositories/payment.repo.ts](server/repositories/payment.repo.ts) |
| B5 | `bookingService``markPaidManual`, `confirmPaidManual`, `getByTripAndUser`, `getAwaitingManualForTrip` (idempotent, transactional dengan retry serialisasi) | ✅ | [server/services/booking.service.ts](server/services/booking.service.ts) |
| B6 | `tripService.markParticipantPayment` → delegate ke `bookingService.markPaidManual`. Tetap update `TripParticipant.markedPaidAt` untuk backcompat. | ✅ | [server/services/trip.service.ts](server/services/trip.service.ts) |
| B7 | `tripService.confirmParticipantPayment` → delegate ke `bookingService.confirmPaidManual`. Tetap update `paymentConfirmedAt`. | ✅ | [server/services/trip.service.ts](server/services/trip.service.ts) |
| B+ | `tripService.joinTrip` → upsert Booking PENDING (handle re-join dari CANCELLED) | ✅ | [server/services/trip.service.ts](server/services/trip.service.ts) |
| B+ | `tripService.confirmParticipant` → transition Booking PENDING → AWAITING_PAY (paid) atau PAID (free) | ✅ | [server/services/trip.service.ts](server/services/trip.service.ts) |
| B+ | `tripService.cancelJoin` & `rejectParticipant` → Booking → CANCELLED | ✅ | [server/services/trip.service.ts](server/services/trip.service.ts) |
| B8 | Halaman `/trips/[id]/payment` baca dari Booking + Payment (bukan timestamp lama) | ✅ | [app/trips/[id]/payment/page.tsx](app/trips/%5Bid%5D/payment/page.tsx) |
| B9 | `OrganizerPaymentQueue` di trip detail dapat data dari `bookingService.getAwaitingManualForTrip` | ✅ | [app/trips/[id]/page.tsx](app/trips/%5Bid%5D/page.tsx) |
| B10 | Deprecate `TripParticipant.markedPaidAt` + `paymentConfirmedAt` (komen `@deprecated`, tetap di-update untuk backcompat) | ✅ | [prisma/schema.prisma](prisma/schema.prisma) |
| B11 | Index optimization (`@@index([tripId, status])` di Booking, `@@index([provider, status])` di Payment, `@@index([userId])` di Booking) | ✅ | [prisma/schema.prisma](prisma/schema.prisma) |
**Tindakan manual (urutan penting):**
1. `npx prisma migrate deploy` — apply schema migration `20260508150000_add_booking_payment`.
2. `npx tsx prisma/backfill-bookings.ts` — populate Booking + Payment dari `TripParticipant` lama. Idempotent, aman dijalankan ulang.
3. Verifikasi: jumlah Booking aktif = jumlah TripParticipant aktif setelah backfill.
---
## PR C — Midtrans integration (Snap + webhook) ⏳
Tambah provider MIDTRANS ke pipeline yang sudah dibuat di PR B. Test di sandbox dulu.
### Persiapan akun & env
| Env | Keterangan |
|---|---|
| `MIDTRANS_SERVER_KEY` | Server key dari dashboard Midtrans (sandbox/production sesuai mode). Rahasia. |
| `MIDTRANS_CLIENT_KEY` | Client key. Boleh di expose ke frontend (untuk Snap script). |
| `MIDTRANS_IS_PRODUCTION` | `true`/`false` — pilih endpoint sandbox vs production. |
| `MIDTRANS_NOTIFICATION_URL` | URL callback publik kita, mis. `https://setrip.id/api/webhooks/midtrans`. Didaftarkan di dashboard Midtrans. |
Tambah ke [env.example](env.example) dengan komentar.
### Tugas
| # | Item | Status | Catatan |
|---|---|---|---|
| C1 | Update [env.example](env.example) + dokumentasi env | ⏳ | 4 env baru. |
| C2 | `lib/midtrans.ts` — client tipis: `createSnapTransaction`, `verifySignature`, `mapStatus` | ⏳ | Pakai `fetch` + `crypto.createHash('sha512')`. Tidak butuh dependency baru. |
| C3 | Status mapping helper | ⏳ | `transaction_status` + `fraud_status` Midtrans → `PaymentStatus` internal. Tabel mapping ada di README PR ini. |
| C4 | Service `paymentService.startMidtransPayment(bookingId)` | ⏳ | Bikin Payment row provider=MIDTRANS, kirim ke Midtrans, simpan `snapToken` + `expiresAt`. Kalau Booking sudah PAID → reject. |
| C5 | Halaman payment: tombol "Bayar online (Midtrans)" untuk trip berbayar | ⏳ | Fallback "Transfer manual" tetap ada (provider MANUAL). User pilih sebelum lanjut. |
| C6 | Frontend: load Snap script + invoke `window.snap.pay(token)` | ⏳ | Loaded conditional di halaman payment, bukan global. Pakai client key dari env publik. |
| C7 | Webhook endpoint `app/api/webhooks/midtrans/route.ts` | ⏳ | POST. Verify signature (sha512). Lookup Payment by `externalOrderId`. Update idempotent. Selalu return 200. |
| C8 | Booking status sync setelah webhook PAID | ⏳ | `Booking.status = PAID`. Sync `TripParticipant.paymentConfirmedAt` untuk kompatibilitas. Concurrency: gunakan DB transaction. |
| C9 | Cron / scheduled job: expire Payment lama | ⏳ | Midtrans default expire 24 jam, tapi DB-side juga harus bersih supaya UI status akurat. Bisa dijalankan via Vercel cron atau manual scheduler. |
| C10 | Anti-replay: skip kalau `Payment.status` sudah final (PAID/FAILED/EXPIRED) | ⏳ | Webhook bisa diretry oleh Midtrans. |
| C11 | Logging callback mentah ke `Payment.rawCallback` (Json) | ⏳ | Audit & dispute. |
| C12 | Test scenario di sandbox | ⏳ | Settlement BCA VA, gopay, deny (kartu fraud), expire, cancel. |
| C13 | Status badge di halaman payment | ⏳ | Tampil real-time tanpa polling agresif (refresh manual atau interval longgar 10s). |
| C14 | Email/in-app notification setelah PAID | ⏳ | Optional Phase ini, bisa Phase berikutnya. |
### Mapping `transaction_status` Midtrans → `PaymentStatus`
| Midtrans | Trigger lain | `PaymentStatus` |
|---|---|---|
| `capture` | `fraud_status === "accept"` | PAID |
| `capture` | `fraud_status === "challenge"` | AWAITING (review manual di dashboard Midtrans) |
| `settlement` | — | PAID |
| `pending` | — | AWAITING |
| `deny` | — | FAILED |
| `expire` | — | EXPIRED |
| `cancel` | — | CANCELLED |
| `refund` / `partial_refund` | — | REFUNDED |
### Webhook checklist (security)
1. Verify signature: `sha512(order_id + status_code + gross_amount + SERVER_KEY) === signature_key`. Mismatch → 401, log.
2. Cek `gross_amount` cocok dengan `payment.amount` — kalau tidak sama, log anomaly, jangan PAID.
3. Lookup `Payment.externalOrderId === order_id`. Tidak ada → 200 OK + log (jangan biarkan Midtrans retry forever).
4. Idempotent: kalau status sudah final, skip update tapi tetap return 200.
5. Pakai DB transaction untuk update Payment + Booking + TripParticipant bersamaan.
6. Selalu return 200 kalau request valid (mismatch signature → 401, sisanya → 200 + log).
### Edge cases yang gampang lupa
- **Quota race**: dua user bayar bersamaan untuk slot terakhir → slot harus di-hold saat Booking dibuat (status AWAITING_PAY masih hitung kuota), release otomatis saat Payment EXPIRED.
- **Trip dibatalkan organizer setelah peserta bayar** → `Booking.status = REFUNDED` setelah dana balik. Implementasi refund Midtrans = PR terpisah (tidak di scope PR C ini).
- **User retry pembayaran setelah gagal** → bikin Payment baru (bukan reuse), `externalOrderId` baru (`setrip-{bookingId}-{retry}`). Booking status tetap AWAITING_PAY.
- **Webhook duplicate**: Midtrans bisa kirim notifikasi yang sama 2-3 kali. Idempotency key = `Payment.externalOrderId` + status terkini.
- **Sandbox vs production**: simulator Midtrans akan kirim callback ke `MIDTRANS_NOTIFICATION_URL`. Pastikan URL sandbox bisa diakses publik (tunneling kalau dev lokal — ngrok / cloudflared).
---
## ❌ Anti-list (yang harus DITOLAK kalau muncul)
- **Halaman payment yang berubah jadi marketplace checkout**: upsell ("trip serupa lebih murah"), pricing comparison, "harga turun" — semua menarik framing OTA. Halaman payment fokus pada satu transaksi: lunas atau belum.
- **Multi-method abstraksi prematur**: jangan bikin "PaymentProvider" generic untuk Stripe/Xendit/Doku sekaligus sebelum salah satu jalan. Mulai dari MANUAL + MIDTRANS, baru tambah kalau perlu.
- **Auto-refund logic kompleks** sebelum manual refund di dashboard Midtrans dipakai. Refund jarang, manual cukup di Phase awal.
- **Payment retry otomatis** dari sisi server. User harus eksplisit klik "bayar lagi" untuk attempt baru — supaya tidak ambigu siapa yang trigger.
- **Multi-currency** sebelum ada permintaan eksplisit. `currency` di schema sudah default IDR, tapi tidak perlu UI selector.
- **Saving credit card / tokenization** tanpa kebutuhan jelas. PCI scope naik drastis. Snap sudah handle tanpa simpan kartu di sisi kita.
---
## Saran phasing
PR berurutan. Setiap PR mandiri (siap di-deploy):
1. **PR A** — Free trip handling + halaman payment (manual). Cepat, low-risk, no migration. **Mulai dari sini.**
2. **PR B** — Refactor ke `Booking` + `Payment`. Migration + backfill data lama. UI tetap mirip.
3. **PR C** — Tambah Midtrans Snap. Test di sandbox dulu sebelum production.
Pertanyaan terbuka sebelum mulai:
1. **Akses halaman payment**: hanya peserta CONFIRMED, atau juga PENDING (yang belum disetujui organizer)? Saya rekomendasi CONFIRMED only — peserta PENDING belum perlu lihat detail bayar.
2. **Snapshot amount di Booking**: kalau organizer ubah `trip.price` setelah booking dibuat, booking lama pakai harga lama atau baru? Saya rekomendasi tetap pakai snapshot lama (audit-friendly).
3. **Manual + Midtrans co-exist**: user pilih satu provider per booking, atau bisa retry dengan provider berbeda? Saya rekomendasi pilih satu — kalau gagal di Midtrans, bisa cancel dan buat Payment baru dengan provider MANUAL.
+16
View File
@@ -59,3 +59,19 @@ export type TripImage = Prisma.TripImageModel
*
*/
export type TripParticipant = Prisma.TripParticipantModel
/**
* Model Booking
* Booking 1-1 ke TripParticipant. Lifecycle ikut peserta:
* - join → Booking PENDING (menunggu approve organizer)
* - organizer confirm → AWAITING_PAY (paid trip) atau PAID (free trip)
* - peserta + organizer rampungkan pembayaran → PAID
* - cancel/reject → CANCELLED
* `amount` adalah snapshot harga saat booking dibuat — protect dari perubahan trip.price.
*/
export type Booking = Prisma.BookingModel
/**
* Model Payment
* Satu attempt pembayaran. Satu Booking bisa punya banyak Payment kalau retry
* (di Phase MIDTRANS nanti). Untuk MANUAL biasanya cukup 1 Payment.
*/
export type Payment = Prisma.PaymentModel
+16
View File
@@ -83,3 +83,19 @@ export type TripImage = Prisma.TripImageModel
*
*/
export type TripParticipant = Prisma.TripParticipantModel
/**
* Model Booking
* Booking 1-1 ke TripParticipant. Lifecycle ikut peserta:
* - join → Booking PENDING (menunggu approve organizer)
* - organizer confirm → AWAITING_PAY (paid trip) atau PAID (free trip)
* - peserta + organizer rampungkan pembayaran → PAID
* - cancel/reject → CANCELLED
* `amount` adalah snapshot harga saat booking dibuat — protect dari perubahan trip.price.
*/
export type Booking = Prisma.BookingModel
/**
* Model Payment
* Satu attempt pembayaran. Satu Booking bisa punya banyak Payment kalau retry
* (di Phase MIDTRANS nanti). Untuk MANUAL biasanya cukup 1 Payment.
*/
export type Payment = Prisma.PaymentModel
+177
View File
@@ -287,6 +287,108 @@ export type EnumParticipantStatusWithAggregatesFilter<$PrismaModel = never> = {
_max?: Prisma.NestedEnumParticipantStatusFilter<$PrismaModel>
}
export type EnumBookingStatusFilter<$PrismaModel = never> = {
equals?: $Enums.BookingStatus | Prisma.EnumBookingStatusFieldRefInput<$PrismaModel>
in?: $Enums.BookingStatus[] | Prisma.ListEnumBookingStatusFieldRefInput<$PrismaModel>
notIn?: $Enums.BookingStatus[] | Prisma.ListEnumBookingStatusFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumBookingStatusFilter<$PrismaModel> | $Enums.BookingStatus
}
export type EnumBookingStatusWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.BookingStatus | Prisma.EnumBookingStatusFieldRefInput<$PrismaModel>
in?: $Enums.BookingStatus[] | Prisma.ListEnumBookingStatusFieldRefInput<$PrismaModel>
notIn?: $Enums.BookingStatus[] | Prisma.ListEnumBookingStatusFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumBookingStatusWithAggregatesFilter<$PrismaModel> | $Enums.BookingStatus
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedEnumBookingStatusFilter<$PrismaModel>
_max?: Prisma.NestedEnumBookingStatusFilter<$PrismaModel>
}
export type EnumPaymentProviderFilter<$PrismaModel = never> = {
equals?: $Enums.PaymentProvider | Prisma.EnumPaymentProviderFieldRefInput<$PrismaModel>
in?: $Enums.PaymentProvider[] | Prisma.ListEnumPaymentProviderFieldRefInput<$PrismaModel>
notIn?: $Enums.PaymentProvider[] | Prisma.ListEnumPaymentProviderFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumPaymentProviderFilter<$PrismaModel> | $Enums.PaymentProvider
}
export type EnumPaymentStatusFilter<$PrismaModel = never> = {
equals?: $Enums.PaymentStatus | Prisma.EnumPaymentStatusFieldRefInput<$PrismaModel>
in?: $Enums.PaymentStatus[] | Prisma.ListEnumPaymentStatusFieldRefInput<$PrismaModel>
notIn?: $Enums.PaymentStatus[] | Prisma.ListEnumPaymentStatusFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumPaymentStatusFilter<$PrismaModel> | $Enums.PaymentStatus
}
export type JsonNullableFilter<$PrismaModel = never> =
| Prisma.PatchUndefined<
Prisma.Either<Required<JsonNullableFilterBase<$PrismaModel>>, Exclude<keyof Required<JsonNullableFilterBase<$PrismaModel>>, 'path'>>,
Required<JsonNullableFilterBase<$PrismaModel>>
>
| Prisma.OptionalFlat<Omit<Required<JsonNullableFilterBase<$PrismaModel>>, 'path'>>
export type JsonNullableFilterBase<$PrismaModel = never> = {
equals?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
path?: string[]
mode?: Prisma.QueryMode | Prisma.EnumQueryModeFieldRefInput<$PrismaModel>
string_contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
string_starts_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
string_ends_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
array_starts_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
array_ends_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
array_contains?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
lt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
lte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
gt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
gte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
}
export type EnumPaymentProviderWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.PaymentProvider | Prisma.EnumPaymentProviderFieldRefInput<$PrismaModel>
in?: $Enums.PaymentProvider[] | Prisma.ListEnumPaymentProviderFieldRefInput<$PrismaModel>
notIn?: $Enums.PaymentProvider[] | Prisma.ListEnumPaymentProviderFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumPaymentProviderWithAggregatesFilter<$PrismaModel> | $Enums.PaymentProvider
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedEnumPaymentProviderFilter<$PrismaModel>
_max?: Prisma.NestedEnumPaymentProviderFilter<$PrismaModel>
}
export type EnumPaymentStatusWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.PaymentStatus | Prisma.EnumPaymentStatusFieldRefInput<$PrismaModel>
in?: $Enums.PaymentStatus[] | Prisma.ListEnumPaymentStatusFieldRefInput<$PrismaModel>
notIn?: $Enums.PaymentStatus[] | Prisma.ListEnumPaymentStatusFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumPaymentStatusWithAggregatesFilter<$PrismaModel> | $Enums.PaymentStatus
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedEnumPaymentStatusFilter<$PrismaModel>
_max?: Prisma.NestedEnumPaymentStatusFilter<$PrismaModel>
}
export type JsonNullableWithAggregatesFilter<$PrismaModel = never> =
| Prisma.PatchUndefined<
Prisma.Either<Required<JsonNullableWithAggregatesFilterBase<$PrismaModel>>, Exclude<keyof Required<JsonNullableWithAggregatesFilterBase<$PrismaModel>>, 'path'>>,
Required<JsonNullableWithAggregatesFilterBase<$PrismaModel>>
>
| Prisma.OptionalFlat<Omit<Required<JsonNullableWithAggregatesFilterBase<$PrismaModel>>, 'path'>>
export type JsonNullableWithAggregatesFilterBase<$PrismaModel = never> = {
equals?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
path?: string[]
mode?: Prisma.QueryMode | Prisma.EnumQueryModeFieldRefInput<$PrismaModel>
string_contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
string_starts_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
string_ends_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
array_starts_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
array_ends_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
array_contains?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
lt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
lte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
gt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
gte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
_min?: Prisma.NestedJsonNullableFilter<$PrismaModel>
_max?: Prisma.NestedJsonNullableFilter<$PrismaModel>
}
export type NestedStringFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
@@ -573,4 +675,79 @@ export type NestedEnumParticipantStatusWithAggregatesFilter<$PrismaModel = never
_max?: Prisma.NestedEnumParticipantStatusFilter<$PrismaModel>
}
export type NestedEnumBookingStatusFilter<$PrismaModel = never> = {
equals?: $Enums.BookingStatus | Prisma.EnumBookingStatusFieldRefInput<$PrismaModel>
in?: $Enums.BookingStatus[] | Prisma.ListEnumBookingStatusFieldRefInput<$PrismaModel>
notIn?: $Enums.BookingStatus[] | Prisma.ListEnumBookingStatusFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumBookingStatusFilter<$PrismaModel> | $Enums.BookingStatus
}
export type NestedEnumBookingStatusWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.BookingStatus | Prisma.EnumBookingStatusFieldRefInput<$PrismaModel>
in?: $Enums.BookingStatus[] | Prisma.ListEnumBookingStatusFieldRefInput<$PrismaModel>
notIn?: $Enums.BookingStatus[] | Prisma.ListEnumBookingStatusFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumBookingStatusWithAggregatesFilter<$PrismaModel> | $Enums.BookingStatus
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedEnumBookingStatusFilter<$PrismaModel>
_max?: Prisma.NestedEnumBookingStatusFilter<$PrismaModel>
}
export type NestedEnumPaymentProviderFilter<$PrismaModel = never> = {
equals?: $Enums.PaymentProvider | Prisma.EnumPaymentProviderFieldRefInput<$PrismaModel>
in?: $Enums.PaymentProvider[] | Prisma.ListEnumPaymentProviderFieldRefInput<$PrismaModel>
notIn?: $Enums.PaymentProvider[] | Prisma.ListEnumPaymentProviderFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumPaymentProviderFilter<$PrismaModel> | $Enums.PaymentProvider
}
export type NestedEnumPaymentStatusFilter<$PrismaModel = never> = {
equals?: $Enums.PaymentStatus | Prisma.EnumPaymentStatusFieldRefInput<$PrismaModel>
in?: $Enums.PaymentStatus[] | Prisma.ListEnumPaymentStatusFieldRefInput<$PrismaModel>
notIn?: $Enums.PaymentStatus[] | Prisma.ListEnumPaymentStatusFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumPaymentStatusFilter<$PrismaModel> | $Enums.PaymentStatus
}
export type NestedEnumPaymentProviderWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.PaymentProvider | Prisma.EnumPaymentProviderFieldRefInput<$PrismaModel>
in?: $Enums.PaymentProvider[] | Prisma.ListEnumPaymentProviderFieldRefInput<$PrismaModel>
notIn?: $Enums.PaymentProvider[] | Prisma.ListEnumPaymentProviderFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumPaymentProviderWithAggregatesFilter<$PrismaModel> | $Enums.PaymentProvider
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedEnumPaymentProviderFilter<$PrismaModel>
_max?: Prisma.NestedEnumPaymentProviderFilter<$PrismaModel>
}
export type NestedEnumPaymentStatusWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.PaymentStatus | Prisma.EnumPaymentStatusFieldRefInput<$PrismaModel>
in?: $Enums.PaymentStatus[] | Prisma.ListEnumPaymentStatusFieldRefInput<$PrismaModel>
notIn?: $Enums.PaymentStatus[] | Prisma.ListEnumPaymentStatusFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumPaymentStatusWithAggregatesFilter<$PrismaModel> | $Enums.PaymentStatus
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedEnumPaymentStatusFilter<$PrismaModel>
_max?: Prisma.NestedEnumPaymentStatusFilter<$PrismaModel>
}
export type NestedJsonNullableFilter<$PrismaModel = never> =
| Prisma.PatchUndefined<
Prisma.Either<Required<NestedJsonNullableFilterBase<$PrismaModel>>, Exclude<keyof Required<NestedJsonNullableFilterBase<$PrismaModel>>, 'path'>>,
Required<NestedJsonNullableFilterBase<$PrismaModel>>
>
| Prisma.OptionalFlat<Omit<Required<NestedJsonNullableFilterBase<$PrismaModel>>, 'path'>>
export type NestedJsonNullableFilterBase<$PrismaModel = never> = {
equals?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
path?: string[]
mode?: Prisma.QueryMode | Prisma.EnumQueryModeFieldRefInput<$PrismaModel>
string_contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
string_starts_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
string_ends_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
array_starts_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
array_ends_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
array_contains?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
lt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
lte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
gt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
gte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
}
+33
View File
@@ -60,3 +60,36 @@ export const ParticipantStatus = {
} as const
export type ParticipantStatus = (typeof ParticipantStatus)[keyof typeof ParticipantStatus]
export const BookingStatus = {
PENDING: 'PENDING',
AWAITING_PAY: 'AWAITING_PAY',
PAID: 'PAID',
CANCELLED: 'CANCELLED',
REFUNDED: 'REFUNDED',
EXPIRED: 'EXPIRED'
} as const
export type BookingStatus = (typeof BookingStatus)[keyof typeof BookingStatus]
export const PaymentProvider = {
MANUAL: 'MANUAL',
MIDTRANS: 'MIDTRANS'
} as const
export type PaymentProvider = (typeof PaymentProvider)[keyof typeof PaymentProvider]
export const PaymentStatus = {
PENDING: 'PENDING',
AWAITING: 'AWAITING',
PAID: 'PAID',
FAILED: 'FAILED',
EXPIRED: 'EXPIRED',
CANCELLED: 'CANCELLED',
REFUNDED: 'REFUNDED'
} as const
export type PaymentStatus = (typeof PaymentStatus)[keyof typeof PaymentStatus]
File diff suppressed because one or more lines are too long
@@ -391,7 +391,9 @@ export const ModelName = {
Trip: 'Trip',
TripReview: 'TripReview',
TripImage: 'TripImage',
TripParticipant: 'TripParticipant'
TripParticipant: 'TripParticipant',
Booking: 'Booking',
Payment: 'Payment'
} as const
export type ModelName = (typeof ModelName)[keyof typeof ModelName]
@@ -407,7 +409,7 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
omit: GlobalOmitOptions
}
meta: {
modelProps: "user" | "userProfile" | "account" | "organizerVerification" | "trip" | "tripReview" | "tripImage" | "tripParticipant"
modelProps: "user" | "userProfile" | "account" | "organizerVerification" | "trip" | "tripReview" | "tripImage" | "tripParticipant" | "booking" | "payment"
txIsolationLevel: TransactionIsolationLevel
}
model: {
@@ -1003,6 +1005,154 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
}
}
}
Booking: {
payload: Prisma.$BookingPayload<ExtArgs>
fields: Prisma.BookingFieldRefs
operations: {
findUnique: {
args: Prisma.BookingFindUniqueArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$BookingPayload> | null
}
findUniqueOrThrow: {
args: Prisma.BookingFindUniqueOrThrowArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$BookingPayload>
}
findFirst: {
args: Prisma.BookingFindFirstArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$BookingPayload> | null
}
findFirstOrThrow: {
args: Prisma.BookingFindFirstOrThrowArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$BookingPayload>
}
findMany: {
args: Prisma.BookingFindManyArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$BookingPayload>[]
}
create: {
args: Prisma.BookingCreateArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$BookingPayload>
}
createMany: {
args: Prisma.BookingCreateManyArgs<ExtArgs>
result: BatchPayload
}
createManyAndReturn: {
args: Prisma.BookingCreateManyAndReturnArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$BookingPayload>[]
}
delete: {
args: Prisma.BookingDeleteArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$BookingPayload>
}
update: {
args: Prisma.BookingUpdateArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$BookingPayload>
}
deleteMany: {
args: Prisma.BookingDeleteManyArgs<ExtArgs>
result: BatchPayload
}
updateMany: {
args: Prisma.BookingUpdateManyArgs<ExtArgs>
result: BatchPayload
}
updateManyAndReturn: {
args: Prisma.BookingUpdateManyAndReturnArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$BookingPayload>[]
}
upsert: {
args: Prisma.BookingUpsertArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$BookingPayload>
}
aggregate: {
args: Prisma.BookingAggregateArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.AggregateBooking>
}
groupBy: {
args: Prisma.BookingGroupByArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.BookingGroupByOutputType>[]
}
count: {
args: Prisma.BookingCountArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.BookingCountAggregateOutputType> | number
}
}
}
Payment: {
payload: Prisma.$PaymentPayload<ExtArgs>
fields: Prisma.PaymentFieldRefs
operations: {
findUnique: {
args: Prisma.PaymentFindUniqueArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$PaymentPayload> | null
}
findUniqueOrThrow: {
args: Prisma.PaymentFindUniqueOrThrowArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$PaymentPayload>
}
findFirst: {
args: Prisma.PaymentFindFirstArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$PaymentPayload> | null
}
findFirstOrThrow: {
args: Prisma.PaymentFindFirstOrThrowArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$PaymentPayload>
}
findMany: {
args: Prisma.PaymentFindManyArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$PaymentPayload>[]
}
create: {
args: Prisma.PaymentCreateArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$PaymentPayload>
}
createMany: {
args: Prisma.PaymentCreateManyArgs<ExtArgs>
result: BatchPayload
}
createManyAndReturn: {
args: Prisma.PaymentCreateManyAndReturnArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$PaymentPayload>[]
}
delete: {
args: Prisma.PaymentDeleteArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$PaymentPayload>
}
update: {
args: Prisma.PaymentUpdateArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$PaymentPayload>
}
deleteMany: {
args: Prisma.PaymentDeleteManyArgs<ExtArgs>
result: BatchPayload
}
updateMany: {
args: Prisma.PaymentUpdateManyArgs<ExtArgs>
result: BatchPayload
}
updateManyAndReturn: {
args: Prisma.PaymentUpdateManyAndReturnArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$PaymentPayload>[]
}
upsert: {
args: Prisma.PaymentUpsertArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$PaymentPayload>
}
aggregate: {
args: Prisma.PaymentAggregateArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.AggregatePayment>
}
groupBy: {
args: Prisma.PaymentGroupByArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.PaymentGroupByOutputType>[]
}
count: {
args: Prisma.PaymentCountArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.PaymentCountAggregateOutputType> | number
}
}
}
}
} & {
other: {
@@ -1178,6 +1328,43 @@ export const TripParticipantScalarFieldEnum = {
export type TripParticipantScalarFieldEnum = (typeof TripParticipantScalarFieldEnum)[keyof typeof TripParticipantScalarFieldEnum]
export const BookingScalarFieldEnum = {
id: 'id',
tripId: 'tripId',
userId: 'userId',
participantId: 'participantId',
amount: 'amount',
currency: 'currency',
status: 'status',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
} as const
export type BookingScalarFieldEnum = (typeof BookingScalarFieldEnum)[keyof typeof BookingScalarFieldEnum]
export const PaymentScalarFieldEnum = {
id: 'id',
bookingId: 'bookingId',
provider: 'provider',
externalOrderId: 'externalOrderId',
externalTxId: 'externalTxId',
method: 'method',
amount: 'amount',
status: 'status',
rawCallback: 'rawCallback',
snapToken: 'snapToken',
expiresAt: 'expiresAt',
paidAt: 'paidAt',
failedAt: 'failedAt',
rejectionReason: 'rejectionReason',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
} as const
export type PaymentScalarFieldEnum = (typeof PaymentScalarFieldEnum)[keyof typeof PaymentScalarFieldEnum]
export const SortOrder = {
asc: 'asc',
desc: 'desc'
@@ -1186,6 +1373,14 @@ export const SortOrder = {
export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder]
export const NullableJsonNullValueInput = {
DbNull: DbNull,
JsonNull: JsonNull
} as const
export type NullableJsonNullValueInput = (typeof NullableJsonNullValueInput)[keyof typeof NullableJsonNullValueInput]
export const QueryMode = {
default: 'default',
insensitive: 'insensitive'
@@ -1202,6 +1397,15 @@ export const NullsOrder = {
export type NullsOrder = (typeof NullsOrder)[keyof typeof NullsOrder]
export const JsonNullValueFilter = {
DbNull: DbNull,
JsonNull: JsonNull,
AnyNull: AnyNull
} as const
export type JsonNullValueFilter = (typeof JsonNullValueFilter)[keyof typeof JsonNullValueFilter]
/**
* Field references
@@ -1327,6 +1531,62 @@ export type ListEnumParticipantStatusFieldRefInput<$PrismaModel> = FieldRefInput
/**
* Reference to a field of type 'BookingStatus'
*/
export type EnumBookingStatusFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'BookingStatus'>
/**
* Reference to a field of type 'BookingStatus[]'
*/
export type ListEnumBookingStatusFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'BookingStatus[]'>
/**
* Reference to a field of type 'PaymentProvider'
*/
export type EnumPaymentProviderFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'PaymentProvider'>
/**
* Reference to a field of type 'PaymentProvider[]'
*/
export type ListEnumPaymentProviderFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'PaymentProvider[]'>
/**
* Reference to a field of type 'PaymentStatus'
*/
export type EnumPaymentStatusFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'PaymentStatus'>
/**
* Reference to a field of type 'PaymentStatus[]'
*/
export type ListEnumPaymentStatusFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'PaymentStatus[]'>
/**
* Reference to a field of type 'Json'
*/
export type JsonFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Json'>
/**
* Reference to a field of type 'QueryMode'
*/
export type EnumQueryModeFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'QueryMode'>
/**
* Reference to a field of type 'Float'
*/
@@ -1458,6 +1718,8 @@ export type GlobalOmitConfig = {
tripReview?: Prisma.TripReviewOmit
tripImage?: Prisma.TripImageOmit
tripParticipant?: Prisma.TripParticipantOmit
booking?: Prisma.BookingOmit
payment?: Prisma.PaymentOmit
}
/* Types for Logging */
@@ -58,7 +58,9 @@ export const ModelName = {
Trip: 'Trip',
TripReview: 'TripReview',
TripImage: 'TripImage',
TripParticipant: 'TripParticipant'
TripParticipant: 'TripParticipant',
Booking: 'Booking',
Payment: 'Payment'
} as const
export type ModelName = (typeof ModelName)[keyof typeof ModelName]
@@ -213,6 +215,43 @@ export const TripParticipantScalarFieldEnum = {
export type TripParticipantScalarFieldEnum = (typeof TripParticipantScalarFieldEnum)[keyof typeof TripParticipantScalarFieldEnum]
export const BookingScalarFieldEnum = {
id: 'id',
tripId: 'tripId',
userId: 'userId',
participantId: 'participantId',
amount: 'amount',
currency: 'currency',
status: 'status',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
} as const
export type BookingScalarFieldEnum = (typeof BookingScalarFieldEnum)[keyof typeof BookingScalarFieldEnum]
export const PaymentScalarFieldEnum = {
id: 'id',
bookingId: 'bookingId',
provider: 'provider',
externalOrderId: 'externalOrderId',
externalTxId: 'externalTxId',
method: 'method',
amount: 'amount',
status: 'status',
rawCallback: 'rawCallback',
snapToken: 'snapToken',
expiresAt: 'expiresAt',
paidAt: 'paidAt',
failedAt: 'failedAt',
rejectionReason: 'rejectionReason',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
} as const
export type PaymentScalarFieldEnum = (typeof PaymentScalarFieldEnum)[keyof typeof PaymentScalarFieldEnum]
export const SortOrder = {
asc: 'asc',
desc: 'desc'
@@ -221,6 +260,14 @@ export const SortOrder = {
export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder]
export const NullableJsonNullValueInput = {
DbNull: DbNull,
JsonNull: JsonNull
} as const
export type NullableJsonNullValueInput = (typeof NullableJsonNullValueInput)[keyof typeof NullableJsonNullValueInput]
export const QueryMode = {
default: 'default',
insensitive: 'insensitive'
@@ -236,3 +283,12 @@ export const NullsOrder = {
export type NullsOrder = (typeof NullsOrder)[keyof typeof NullsOrder]
export const JsonNullValueFilter = {
DbNull: DbNull,
JsonNull: JsonNull,
AnyNull: AnyNull
} as const
export type JsonNullValueFilter = (typeof JsonNullValueFilter)[keyof typeof JsonNullValueFilter]
+2
View File
@@ -16,4 +16,6 @@ export type * from './models/Trip'
export type * from './models/TripReview'
export type * from './models/TripImage'
export type * from './models/TripParticipant'
export type * from './models/Booking'
export type * from './models/Payment'
export type * from './commonInputTypes'
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+190
View File
@@ -336,6 +336,7 @@ export type TripWhereInput = {
participants?: Prisma.TripParticipantListRelationFilter
images?: Prisma.TripImageListRelationFilter
reviews?: Prisma.TripReviewListRelationFilter
bookings?: Prisma.BookingListRelationFilter
}
export type TripOrderByWithRelationInput = {
@@ -362,6 +363,7 @@ export type TripOrderByWithRelationInput = {
participants?: Prisma.TripParticipantOrderByRelationAggregateInput
images?: Prisma.TripImageOrderByRelationAggregateInput
reviews?: Prisma.TripReviewOrderByRelationAggregateInput
bookings?: Prisma.BookingOrderByRelationAggregateInput
}
export type TripWhereUniqueInput = Prisma.AtLeast<{
@@ -391,6 +393,7 @@ export type TripWhereUniqueInput = Prisma.AtLeast<{
participants?: Prisma.TripParticipantListRelationFilter
images?: Prisma.TripImageListRelationFilter
reviews?: Prisma.TripReviewListRelationFilter
bookings?: Prisma.BookingListRelationFilter
}, "id">
export type TripOrderByWithAggregationInput = {
@@ -468,6 +471,7 @@ export type TripCreateInput = {
participants?: Prisma.TripParticipantCreateNestedManyWithoutTripInput
images?: Prisma.TripImageCreateNestedManyWithoutTripInput
reviews?: Prisma.TripReviewCreateNestedManyWithoutTripInput
bookings?: Prisma.BookingCreateNestedManyWithoutTripInput
}
export type TripUncheckedCreateInput = {
@@ -493,6 +497,7 @@ export type TripUncheckedCreateInput = {
participants?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutTripInput
images?: Prisma.TripImageUncheckedCreateNestedManyWithoutTripInput
reviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutTripInput
bookings?: Prisma.BookingUncheckedCreateNestedManyWithoutTripInput
}
export type TripUpdateInput = {
@@ -518,6 +523,7 @@ export type TripUpdateInput = {
participants?: Prisma.TripParticipantUpdateManyWithoutTripNestedInput
images?: Prisma.TripImageUpdateManyWithoutTripNestedInput
reviews?: Prisma.TripReviewUpdateManyWithoutTripNestedInput
bookings?: Prisma.BookingUpdateManyWithoutTripNestedInput
}
export type TripUncheckedUpdateInput = {
@@ -543,6 +549,7 @@ export type TripUncheckedUpdateInput = {
participants?: Prisma.TripParticipantUncheckedUpdateManyWithoutTripNestedInput
images?: Prisma.TripImageUncheckedUpdateManyWithoutTripNestedInput
reviews?: Prisma.TripReviewUncheckedUpdateManyWithoutTripNestedInput
bookings?: Prisma.BookingUncheckedUpdateManyWithoutTripNestedInput
}
export type TripCreateManyInput = {
@@ -801,6 +808,20 @@ export type TripUpdateOneRequiredWithoutParticipantsNestedInput = {
update?: Prisma.XOR<Prisma.XOR<Prisma.TripUpdateToOneWithWhereWithoutParticipantsInput, Prisma.TripUpdateWithoutParticipantsInput>, Prisma.TripUncheckedUpdateWithoutParticipantsInput>
}
export type TripCreateNestedOneWithoutBookingsInput = {
create?: Prisma.XOR<Prisma.TripCreateWithoutBookingsInput, Prisma.TripUncheckedCreateWithoutBookingsInput>
connectOrCreate?: Prisma.TripCreateOrConnectWithoutBookingsInput
connect?: Prisma.TripWhereUniqueInput
}
export type TripUpdateOneRequiredWithoutBookingsNestedInput = {
create?: Prisma.XOR<Prisma.TripCreateWithoutBookingsInput, Prisma.TripUncheckedCreateWithoutBookingsInput>
connectOrCreate?: Prisma.TripCreateOrConnectWithoutBookingsInput
upsert?: Prisma.TripUpsertWithoutBookingsInput
connect?: Prisma.TripWhereUniqueInput
update?: Prisma.XOR<Prisma.XOR<Prisma.TripUpdateToOneWithWhereWithoutBookingsInput, Prisma.TripUpdateWithoutBookingsInput>, Prisma.TripUncheckedUpdateWithoutBookingsInput>
}
export type TripCreateWithoutOrganizerInput = {
id?: string
title: string
@@ -823,6 +844,7 @@ export type TripCreateWithoutOrganizerInput = {
participants?: Prisma.TripParticipantCreateNestedManyWithoutTripInput
images?: Prisma.TripImageCreateNestedManyWithoutTripInput
reviews?: Prisma.TripReviewCreateNestedManyWithoutTripInput
bookings?: Prisma.BookingCreateNestedManyWithoutTripInput
}
export type TripUncheckedCreateWithoutOrganizerInput = {
@@ -847,6 +869,7 @@ export type TripUncheckedCreateWithoutOrganizerInput = {
participants?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutTripInput
images?: Prisma.TripImageUncheckedCreateNestedManyWithoutTripInput
reviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutTripInput
bookings?: Prisma.BookingUncheckedCreateNestedManyWithoutTripInput
}
export type TripCreateOrConnectWithoutOrganizerInput = {
@@ -922,6 +945,7 @@ export type TripCreateWithoutReviewsInput = {
organizer: Prisma.UserCreateNestedOneWithoutTripsInput
participants?: Prisma.TripParticipantCreateNestedManyWithoutTripInput
images?: Prisma.TripImageCreateNestedManyWithoutTripInput
bookings?: Prisma.BookingCreateNestedManyWithoutTripInput
}
export type TripUncheckedCreateWithoutReviewsInput = {
@@ -946,6 +970,7 @@ export type TripUncheckedCreateWithoutReviewsInput = {
organizerId: string
participants?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutTripInput
images?: Prisma.TripImageUncheckedCreateNestedManyWithoutTripInput
bookings?: Prisma.BookingUncheckedCreateNestedManyWithoutTripInput
}
export type TripCreateOrConnectWithoutReviewsInput = {
@@ -986,6 +1011,7 @@ export type TripUpdateWithoutReviewsInput = {
organizer?: Prisma.UserUpdateOneRequiredWithoutTripsNestedInput
participants?: Prisma.TripParticipantUpdateManyWithoutTripNestedInput
images?: Prisma.TripImageUpdateManyWithoutTripNestedInput
bookings?: Prisma.BookingUpdateManyWithoutTripNestedInput
}
export type TripUncheckedUpdateWithoutReviewsInput = {
@@ -1010,6 +1036,7 @@ export type TripUncheckedUpdateWithoutReviewsInput = {
organizerId?: Prisma.StringFieldUpdateOperationsInput | string
participants?: Prisma.TripParticipantUncheckedUpdateManyWithoutTripNestedInput
images?: Prisma.TripImageUncheckedUpdateManyWithoutTripNestedInput
bookings?: Prisma.BookingUncheckedUpdateManyWithoutTripNestedInput
}
export type TripCreateWithoutImagesInput = {
@@ -1034,6 +1061,7 @@ export type TripCreateWithoutImagesInput = {
organizer: Prisma.UserCreateNestedOneWithoutTripsInput
participants?: Prisma.TripParticipantCreateNestedManyWithoutTripInput
reviews?: Prisma.TripReviewCreateNestedManyWithoutTripInput
bookings?: Prisma.BookingCreateNestedManyWithoutTripInput
}
export type TripUncheckedCreateWithoutImagesInput = {
@@ -1058,6 +1086,7 @@ export type TripUncheckedCreateWithoutImagesInput = {
organizerId: string
participants?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutTripInput
reviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutTripInput
bookings?: Prisma.BookingUncheckedCreateNestedManyWithoutTripInput
}
export type TripCreateOrConnectWithoutImagesInput = {
@@ -1098,6 +1127,7 @@ export type TripUpdateWithoutImagesInput = {
organizer?: Prisma.UserUpdateOneRequiredWithoutTripsNestedInput
participants?: Prisma.TripParticipantUpdateManyWithoutTripNestedInput
reviews?: Prisma.TripReviewUpdateManyWithoutTripNestedInput
bookings?: Prisma.BookingUpdateManyWithoutTripNestedInput
}
export type TripUncheckedUpdateWithoutImagesInput = {
@@ -1122,6 +1152,7 @@ export type TripUncheckedUpdateWithoutImagesInput = {
organizerId?: Prisma.StringFieldUpdateOperationsInput | string
participants?: Prisma.TripParticipantUncheckedUpdateManyWithoutTripNestedInput
reviews?: Prisma.TripReviewUncheckedUpdateManyWithoutTripNestedInput
bookings?: Prisma.BookingUncheckedUpdateManyWithoutTripNestedInput
}
export type TripCreateWithoutParticipantsInput = {
@@ -1146,6 +1177,7 @@ export type TripCreateWithoutParticipantsInput = {
organizer: Prisma.UserCreateNestedOneWithoutTripsInput
images?: Prisma.TripImageCreateNestedManyWithoutTripInput
reviews?: Prisma.TripReviewCreateNestedManyWithoutTripInput
bookings?: Prisma.BookingCreateNestedManyWithoutTripInput
}
export type TripUncheckedCreateWithoutParticipantsInput = {
@@ -1170,6 +1202,7 @@ export type TripUncheckedCreateWithoutParticipantsInput = {
organizerId: string
images?: Prisma.TripImageUncheckedCreateNestedManyWithoutTripInput
reviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutTripInput
bookings?: Prisma.BookingUncheckedCreateNestedManyWithoutTripInput
}
export type TripCreateOrConnectWithoutParticipantsInput = {
@@ -1210,6 +1243,7 @@ export type TripUpdateWithoutParticipantsInput = {
organizer?: Prisma.UserUpdateOneRequiredWithoutTripsNestedInput
images?: Prisma.TripImageUpdateManyWithoutTripNestedInput
reviews?: Prisma.TripReviewUpdateManyWithoutTripNestedInput
bookings?: Prisma.BookingUpdateManyWithoutTripNestedInput
}
export type TripUncheckedUpdateWithoutParticipantsInput = {
@@ -1234,6 +1268,123 @@ export type TripUncheckedUpdateWithoutParticipantsInput = {
organizerId?: Prisma.StringFieldUpdateOperationsInput | string
images?: Prisma.TripImageUncheckedUpdateManyWithoutTripNestedInput
reviews?: Prisma.TripReviewUncheckedUpdateManyWithoutTripNestedInput
bookings?: Prisma.BookingUncheckedUpdateManyWithoutTripNestedInput
}
export type TripCreateWithoutBookingsInput = {
id?: string
title: string
description?: string | null
category?: $Enums.ActivityCategory
destination: string
location: string
meetingPoint?: string | null
itinerary?: string | null
whatsIncluded?: string | null
whatsExcluded?: string | null
date: Date | string
endDate?: Date | string | null
maxParticipants: number
price: number
vibe?: $Enums.Vibe | null
status?: $Enums.TripStatus
createdAt?: Date | string
updatedAt?: Date | string
organizer: Prisma.UserCreateNestedOneWithoutTripsInput
participants?: Prisma.TripParticipantCreateNestedManyWithoutTripInput
images?: Prisma.TripImageCreateNestedManyWithoutTripInput
reviews?: Prisma.TripReviewCreateNestedManyWithoutTripInput
}
export type TripUncheckedCreateWithoutBookingsInput = {
id?: string
title: string
description?: string | null
category?: $Enums.ActivityCategory
destination: string
location: string
meetingPoint?: string | null
itinerary?: string | null
whatsIncluded?: string | null
whatsExcluded?: string | null
date: Date | string
endDate?: Date | string | null
maxParticipants: number
price: number
vibe?: $Enums.Vibe | null
status?: $Enums.TripStatus
createdAt?: Date | string
updatedAt?: Date | string
organizerId: string
participants?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutTripInput
images?: Prisma.TripImageUncheckedCreateNestedManyWithoutTripInput
reviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutTripInput
}
export type TripCreateOrConnectWithoutBookingsInput = {
where: Prisma.TripWhereUniqueInput
create: Prisma.XOR<Prisma.TripCreateWithoutBookingsInput, Prisma.TripUncheckedCreateWithoutBookingsInput>
}
export type TripUpsertWithoutBookingsInput = {
update: Prisma.XOR<Prisma.TripUpdateWithoutBookingsInput, Prisma.TripUncheckedUpdateWithoutBookingsInput>
create: Prisma.XOR<Prisma.TripCreateWithoutBookingsInput, Prisma.TripUncheckedCreateWithoutBookingsInput>
where?: Prisma.TripWhereInput
}
export type TripUpdateToOneWithWhereWithoutBookingsInput = {
where?: Prisma.TripWhereInput
data: Prisma.XOR<Prisma.TripUpdateWithoutBookingsInput, Prisma.TripUncheckedUpdateWithoutBookingsInput>
}
export type TripUpdateWithoutBookingsInput = {
id?: Prisma.StringFieldUpdateOperationsInput | string
title?: Prisma.StringFieldUpdateOperationsInput | string
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
category?: Prisma.EnumActivityCategoryFieldUpdateOperationsInput | $Enums.ActivityCategory
destination?: Prisma.StringFieldUpdateOperationsInput | string
location?: Prisma.StringFieldUpdateOperationsInput | string
meetingPoint?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
itinerary?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
whatsIncluded?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
whatsExcluded?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
price?: Prisma.IntFieldUpdateOperationsInput | number
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
organizer?: Prisma.UserUpdateOneRequiredWithoutTripsNestedInput
participants?: Prisma.TripParticipantUpdateManyWithoutTripNestedInput
images?: Prisma.TripImageUpdateManyWithoutTripNestedInput
reviews?: Prisma.TripReviewUpdateManyWithoutTripNestedInput
}
export type TripUncheckedUpdateWithoutBookingsInput = {
id?: Prisma.StringFieldUpdateOperationsInput | string
title?: Prisma.StringFieldUpdateOperationsInput | string
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
category?: Prisma.EnumActivityCategoryFieldUpdateOperationsInput | $Enums.ActivityCategory
destination?: Prisma.StringFieldUpdateOperationsInput | string
location?: Prisma.StringFieldUpdateOperationsInput | string
meetingPoint?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
itinerary?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
whatsIncluded?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
whatsExcluded?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
endDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
maxParticipants?: Prisma.IntFieldUpdateOperationsInput | number
price?: Prisma.IntFieldUpdateOperationsInput | number
vibe?: Prisma.NullableEnumVibeFieldUpdateOperationsInput | $Enums.Vibe | null
status?: Prisma.EnumTripStatusFieldUpdateOperationsInput | $Enums.TripStatus
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
organizerId?: Prisma.StringFieldUpdateOperationsInput | string
participants?: Prisma.TripParticipantUncheckedUpdateManyWithoutTripNestedInput
images?: Prisma.TripImageUncheckedUpdateManyWithoutTripNestedInput
reviews?: Prisma.TripReviewUncheckedUpdateManyWithoutTripNestedInput
}
export type TripCreateManyOrganizerInput = {
@@ -1279,6 +1430,7 @@ export type TripUpdateWithoutOrganizerInput = {
participants?: Prisma.TripParticipantUpdateManyWithoutTripNestedInput
images?: Prisma.TripImageUpdateManyWithoutTripNestedInput
reviews?: Prisma.TripReviewUpdateManyWithoutTripNestedInput
bookings?: Prisma.BookingUpdateManyWithoutTripNestedInput
}
export type TripUncheckedUpdateWithoutOrganizerInput = {
@@ -1303,6 +1455,7 @@ export type TripUncheckedUpdateWithoutOrganizerInput = {
participants?: Prisma.TripParticipantUncheckedUpdateManyWithoutTripNestedInput
images?: Prisma.TripImageUncheckedUpdateManyWithoutTripNestedInput
reviews?: Prisma.TripReviewUncheckedUpdateManyWithoutTripNestedInput
bookings?: Prisma.BookingUncheckedUpdateManyWithoutTripNestedInput
}
export type TripUncheckedUpdateManyWithoutOrganizerInput = {
@@ -1335,12 +1488,14 @@ export type TripCountOutputType = {
participants: number
images: number
reviews: number
bookings: number
}
export type TripCountOutputTypeSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
participants?: boolean | TripCountOutputTypeCountParticipantsArgs
images?: boolean | TripCountOutputTypeCountImagesArgs
reviews?: boolean | TripCountOutputTypeCountReviewsArgs
bookings?: boolean | TripCountOutputTypeCountBookingsArgs
}
/**
@@ -1374,6 +1529,13 @@ export type TripCountOutputTypeCountReviewsArgs<ExtArgs extends runtime.Types.Ex
where?: Prisma.TripReviewWhereInput
}
/**
* TripCountOutputType without action
*/
export type TripCountOutputTypeCountBookingsArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
where?: Prisma.BookingWhereInput
}
export type TripSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
id?: boolean
@@ -1399,6 +1561,7 @@ export type TripSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = r
participants?: boolean | Prisma.Trip$participantsArgs<ExtArgs>
images?: boolean | Prisma.Trip$imagesArgs<ExtArgs>
reviews?: boolean | Prisma.Trip$reviewsArgs<ExtArgs>
bookings?: boolean | Prisma.Trip$bookingsArgs<ExtArgs>
_count?: boolean | Prisma.TripCountOutputTypeDefaultArgs<ExtArgs>
}, ExtArgs["result"]["trip"]>
@@ -1476,6 +1639,7 @@ export type TripInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs =
participants?: boolean | Prisma.Trip$participantsArgs<ExtArgs>
images?: boolean | Prisma.Trip$imagesArgs<ExtArgs>
reviews?: boolean | Prisma.Trip$reviewsArgs<ExtArgs>
bookings?: boolean | Prisma.Trip$bookingsArgs<ExtArgs>
_count?: boolean | Prisma.TripCountOutputTypeDefaultArgs<ExtArgs>
}
export type TripIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
@@ -1492,6 +1656,7 @@ export type $TripPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs =
participants: Prisma.$TripParticipantPayload<ExtArgs>[]
images: Prisma.$TripImagePayload<ExtArgs>[]
reviews: Prisma.$TripReviewPayload<ExtArgs>[]
bookings: Prisma.$BookingPayload<ExtArgs>[]
}
scalars: runtime.Types.Extensions.GetPayloadResult<{
id: string
@@ -1932,6 +2097,7 @@ export interface Prisma__TripClient<T, Null = never, ExtArgs extends runtime.Typ
participants<T extends Prisma.Trip$participantsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Trip$participantsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$TripParticipantPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
images<T extends Prisma.Trip$imagesArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Trip$imagesArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$TripImagePayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
reviews<T extends Prisma.Trip$reviewsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Trip$reviewsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$TripReviewPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
bookings<T extends Prisma.Trip$bookingsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Trip$bookingsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$BookingPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onfulfilled The callback to execute when the Promise is resolved.
@@ -2452,6 +2618,30 @@ export type Trip$reviewsArgs<ExtArgs extends runtime.Types.Extensions.InternalAr
distinct?: Prisma.TripReviewScalarFieldEnum | Prisma.TripReviewScalarFieldEnum[]
}
/**
* Trip.bookings
*/
export type Trip$bookingsArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
/**
* Select specific fields to fetch from the Booking
*/
select?: Prisma.BookingSelect<ExtArgs> | null
/**
* Omit specific fields from the Booking
*/
omit?: Prisma.BookingOmit<ExtArgs> | null
/**
* Choose, which related nodes to fetch as well
*/
include?: Prisma.BookingInclude<ExtArgs> | null
where?: Prisma.BookingWhereInput
orderBy?: Prisma.BookingOrderByWithRelationInput | Prisma.BookingOrderByWithRelationInput[]
cursor?: Prisma.BookingWhereUniqueInput
take?: number
skip?: number
distinct?: Prisma.BookingScalarFieldEnum | Prisma.BookingScalarFieldEnum[]
}
/**
* Trip without action
*/
+117 -2
View File
@@ -200,6 +200,7 @@ export type TripParticipantWhereInput = {
userId?: Prisma.StringFilter<"TripParticipant"> | string
trip?: Prisma.XOR<Prisma.TripScalarRelationFilter, Prisma.TripWhereInput>
user?: Prisma.XOR<Prisma.UserScalarRelationFilter, Prisma.UserWhereInput>
booking?: Prisma.XOR<Prisma.BookingNullableScalarRelationFilter, Prisma.BookingWhereInput> | null
}
export type TripParticipantOrderByWithRelationInput = {
@@ -212,6 +213,7 @@ export type TripParticipantOrderByWithRelationInput = {
userId?: Prisma.SortOrder
trip?: Prisma.TripOrderByWithRelationInput
user?: Prisma.UserOrderByWithRelationInput
booking?: Prisma.BookingOrderByWithRelationInput
}
export type TripParticipantWhereUniqueInput = Prisma.AtLeast<{
@@ -228,6 +230,7 @@ export type TripParticipantWhereUniqueInput = Prisma.AtLeast<{
userId?: Prisma.StringFilter<"TripParticipant"> | string
trip?: Prisma.XOR<Prisma.TripScalarRelationFilter, Prisma.TripWhereInput>
user?: Prisma.XOR<Prisma.UserScalarRelationFilter, Prisma.UserWhereInput>
booking?: Prisma.XOR<Prisma.BookingNullableScalarRelationFilter, Prisma.BookingWhereInput> | null
}, "id" | "tripId_userId">
export type TripParticipantOrderByWithAggregationInput = {
@@ -264,6 +267,7 @@ export type TripParticipantCreateInput = {
paymentConfirmedAt?: Date | string | null
trip: Prisma.TripCreateNestedOneWithoutParticipantsInput
user: Prisma.UserCreateNestedOneWithoutParticipationsInput
booking?: Prisma.BookingCreateNestedOneWithoutParticipantInput
}
export type TripParticipantUncheckedCreateInput = {
@@ -274,6 +278,7 @@ export type TripParticipantUncheckedCreateInput = {
paymentConfirmedAt?: Date | string | null
tripId: string
userId: string
booking?: Prisma.BookingUncheckedCreateNestedOneWithoutParticipantInput
}
export type TripParticipantUpdateInput = {
@@ -284,6 +289,7 @@ export type TripParticipantUpdateInput = {
paymentConfirmedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
trip?: Prisma.TripUpdateOneRequiredWithoutParticipantsNestedInput
user?: Prisma.UserUpdateOneRequiredWithoutParticipationsNestedInput
booking?: Prisma.BookingUpdateOneWithoutParticipantNestedInput
}
export type TripParticipantUncheckedUpdateInput = {
@@ -294,6 +300,7 @@ export type TripParticipantUncheckedUpdateInput = {
paymentConfirmedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
tripId?: Prisma.StringFieldUpdateOperationsInput | string
userId?: Prisma.StringFieldUpdateOperationsInput | string
booking?: Prisma.BookingUncheckedUpdateOneWithoutParticipantNestedInput
}
export type TripParticipantCreateManyInput = {
@@ -369,6 +376,11 @@ export type TripParticipantMinOrderByAggregateInput = {
userId?: Prisma.SortOrder
}
export type TripParticipantScalarRelationFilter = {
is?: Prisma.TripParticipantWhereInput
isNot?: Prisma.TripParticipantWhereInput
}
export type TripParticipantCreateNestedManyWithoutUserInput = {
create?: Prisma.XOR<Prisma.TripParticipantCreateWithoutUserInput, Prisma.TripParticipantUncheckedCreateWithoutUserInput> | Prisma.TripParticipantCreateWithoutUserInput[] | Prisma.TripParticipantUncheckedCreateWithoutUserInput[]
connectOrCreate?: Prisma.TripParticipantCreateOrConnectWithoutUserInput | Prisma.TripParticipantCreateOrConnectWithoutUserInput[]
@@ -457,6 +469,20 @@ export type EnumParticipantStatusFieldUpdateOperationsInput = {
set?: $Enums.ParticipantStatus
}
export type TripParticipantCreateNestedOneWithoutBookingInput = {
create?: Prisma.XOR<Prisma.TripParticipantCreateWithoutBookingInput, Prisma.TripParticipantUncheckedCreateWithoutBookingInput>
connectOrCreate?: Prisma.TripParticipantCreateOrConnectWithoutBookingInput
connect?: Prisma.TripParticipantWhereUniqueInput
}
export type TripParticipantUpdateOneRequiredWithoutBookingNestedInput = {
create?: Prisma.XOR<Prisma.TripParticipantCreateWithoutBookingInput, Prisma.TripParticipantUncheckedCreateWithoutBookingInput>
connectOrCreate?: Prisma.TripParticipantCreateOrConnectWithoutBookingInput
upsert?: Prisma.TripParticipantUpsertWithoutBookingInput
connect?: Prisma.TripParticipantWhereUniqueInput
update?: Prisma.XOR<Prisma.XOR<Prisma.TripParticipantUpdateToOneWithWhereWithoutBookingInput, Prisma.TripParticipantUpdateWithoutBookingInput>, Prisma.TripParticipantUncheckedUpdateWithoutBookingInput>
}
export type TripParticipantCreateWithoutUserInput = {
id?: string
status?: $Enums.ParticipantStatus
@@ -464,6 +490,7 @@ export type TripParticipantCreateWithoutUserInput = {
markedPaidAt?: Date | string | null
paymentConfirmedAt?: Date | string | null
trip: Prisma.TripCreateNestedOneWithoutParticipantsInput
booking?: Prisma.BookingCreateNestedOneWithoutParticipantInput
}
export type TripParticipantUncheckedCreateWithoutUserInput = {
@@ -473,6 +500,7 @@ export type TripParticipantUncheckedCreateWithoutUserInput = {
markedPaidAt?: Date | string | null
paymentConfirmedAt?: Date | string | null
tripId: string
booking?: Prisma.BookingUncheckedCreateNestedOneWithoutParticipantInput
}
export type TripParticipantCreateOrConnectWithoutUserInput = {
@@ -521,6 +549,7 @@ export type TripParticipantCreateWithoutTripInput = {
markedPaidAt?: Date | string | null
paymentConfirmedAt?: Date | string | null
user: Prisma.UserCreateNestedOneWithoutParticipationsInput
booking?: Prisma.BookingCreateNestedOneWithoutParticipantInput
}
export type TripParticipantUncheckedCreateWithoutTripInput = {
@@ -530,6 +559,7 @@ export type TripParticipantUncheckedCreateWithoutTripInput = {
markedPaidAt?: Date | string | null
paymentConfirmedAt?: Date | string | null
userId: string
booking?: Prisma.BookingUncheckedCreateNestedOneWithoutParticipantInput
}
export type TripParticipantCreateOrConnectWithoutTripInput = {
@@ -558,6 +588,62 @@ export type TripParticipantUpdateManyWithWhereWithoutTripInput = {
data: Prisma.XOR<Prisma.TripParticipantUpdateManyMutationInput, Prisma.TripParticipantUncheckedUpdateManyWithoutTripInput>
}
export type TripParticipantCreateWithoutBookingInput = {
id?: string
status?: $Enums.ParticipantStatus
createdAt?: Date | string
markedPaidAt?: Date | string | null
paymentConfirmedAt?: Date | string | null
trip: Prisma.TripCreateNestedOneWithoutParticipantsInput
user: Prisma.UserCreateNestedOneWithoutParticipationsInput
}
export type TripParticipantUncheckedCreateWithoutBookingInput = {
id?: string
status?: $Enums.ParticipantStatus
createdAt?: Date | string
markedPaidAt?: Date | string | null
paymentConfirmedAt?: Date | string | null
tripId: string
userId: string
}
export type TripParticipantCreateOrConnectWithoutBookingInput = {
where: Prisma.TripParticipantWhereUniqueInput
create: Prisma.XOR<Prisma.TripParticipantCreateWithoutBookingInput, Prisma.TripParticipantUncheckedCreateWithoutBookingInput>
}
export type TripParticipantUpsertWithoutBookingInput = {
update: Prisma.XOR<Prisma.TripParticipantUpdateWithoutBookingInput, Prisma.TripParticipantUncheckedUpdateWithoutBookingInput>
create: Prisma.XOR<Prisma.TripParticipantCreateWithoutBookingInput, Prisma.TripParticipantUncheckedCreateWithoutBookingInput>
where?: Prisma.TripParticipantWhereInput
}
export type TripParticipantUpdateToOneWithWhereWithoutBookingInput = {
where?: Prisma.TripParticipantWhereInput
data: Prisma.XOR<Prisma.TripParticipantUpdateWithoutBookingInput, Prisma.TripParticipantUncheckedUpdateWithoutBookingInput>
}
export type TripParticipantUpdateWithoutBookingInput = {
id?: Prisma.StringFieldUpdateOperationsInput | string
status?: Prisma.EnumParticipantStatusFieldUpdateOperationsInput | $Enums.ParticipantStatus
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
markedPaidAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
paymentConfirmedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
trip?: Prisma.TripUpdateOneRequiredWithoutParticipantsNestedInput
user?: Prisma.UserUpdateOneRequiredWithoutParticipationsNestedInput
}
export type TripParticipantUncheckedUpdateWithoutBookingInput = {
id?: Prisma.StringFieldUpdateOperationsInput | string
status?: Prisma.EnumParticipantStatusFieldUpdateOperationsInput | $Enums.ParticipantStatus
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
markedPaidAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
paymentConfirmedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
tripId?: Prisma.StringFieldUpdateOperationsInput | string
userId?: Prisma.StringFieldUpdateOperationsInput | string
}
export type TripParticipantCreateManyUserInput = {
id?: string
status?: $Enums.ParticipantStatus
@@ -574,6 +660,7 @@ export type TripParticipantUpdateWithoutUserInput = {
markedPaidAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
paymentConfirmedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
trip?: Prisma.TripUpdateOneRequiredWithoutParticipantsNestedInput
booking?: Prisma.BookingUpdateOneWithoutParticipantNestedInput
}
export type TripParticipantUncheckedUpdateWithoutUserInput = {
@@ -583,6 +670,7 @@ export type TripParticipantUncheckedUpdateWithoutUserInput = {
markedPaidAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
paymentConfirmedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
tripId?: Prisma.StringFieldUpdateOperationsInput | string
booking?: Prisma.BookingUncheckedUpdateOneWithoutParticipantNestedInput
}
export type TripParticipantUncheckedUpdateManyWithoutUserInput = {
@@ -610,6 +698,7 @@ export type TripParticipantUpdateWithoutTripInput = {
markedPaidAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
paymentConfirmedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
user?: Prisma.UserUpdateOneRequiredWithoutParticipationsNestedInput
booking?: Prisma.BookingUpdateOneWithoutParticipantNestedInput
}
export type TripParticipantUncheckedUpdateWithoutTripInput = {
@@ -619,6 +708,7 @@ export type TripParticipantUncheckedUpdateWithoutTripInput = {
markedPaidAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
paymentConfirmedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
userId?: Prisma.StringFieldUpdateOperationsInput | string
booking?: Prisma.BookingUncheckedUpdateOneWithoutParticipantNestedInput
}
export type TripParticipantUncheckedUpdateManyWithoutTripInput = {
@@ -642,6 +732,7 @@ export type TripParticipantSelect<ExtArgs extends runtime.Types.Extensions.Inter
userId?: boolean
trip?: boolean | Prisma.TripDefaultArgs<ExtArgs>
user?: boolean | Prisma.UserDefaultArgs<ExtArgs>
booking?: boolean | Prisma.TripParticipant$bookingArgs<ExtArgs>
}, ExtArgs["result"]["tripParticipant"]>
export type TripParticipantSelectCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
@@ -682,6 +773,7 @@ export type TripParticipantOmit<ExtArgs extends runtime.Types.Extensions.Interna
export type TripParticipantInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
trip?: boolean | Prisma.TripDefaultArgs<ExtArgs>
user?: boolean | Prisma.UserDefaultArgs<ExtArgs>
booking?: boolean | Prisma.TripParticipant$bookingArgs<ExtArgs>
}
export type TripParticipantIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
trip?: boolean | Prisma.TripDefaultArgs<ExtArgs>
@@ -697,17 +789,20 @@ export type $TripParticipantPayload<ExtArgs extends runtime.Types.Extensions.Int
objects: {
trip: Prisma.$TripPayload<ExtArgs>
user: Prisma.$UserPayload<ExtArgs>
booking: Prisma.$BookingPayload<ExtArgs> | null
}
scalars: runtime.Types.Extensions.GetPayloadResult<{
id: string
status: $Enums.ParticipantStatus
createdAt: Date
/**
* Peserta menekan "Saya sudah bayar" (pembayaran manual)
* @deprecated — sumber kebenaran pindah ke Booking/Payment. Tetap di-update
* untuk backward-compat selama transisi UI lama. Akan dihapus PR berikutnya.
*/
markedPaidAt: Date | null
/**
* Organizer mengonfirmasi uang sudah masuk
* @deprecated — sumber kebenaran pindah ke Booking/Payment. Tetap di-update
* untuk backward-compat selama transisi UI lama. Akan dihapus PR berikutnya.
*/
paymentConfirmedAt: Date | null
tripId: string
@@ -1108,6 +1203,7 @@ export interface Prisma__TripParticipantClient<T, Null = never, ExtArgs extends
readonly [Symbol.toStringTag]: "PrismaPromise"
trip<T extends Prisma.TripDefaultArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.TripDefaultArgs<ExtArgs>>): Prisma.Prisma__TripClient<runtime.Types.Result.GetResult<Prisma.$TripPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions>
user<T extends Prisma.UserDefaultArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.UserDefaultArgs<ExtArgs>>): Prisma.Prisma__UserClient<runtime.Types.Result.GetResult<Prisma.$UserPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions>
booking<T extends Prisma.TripParticipant$bookingArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.TripParticipant$bookingArgs<ExtArgs>>): Prisma.Prisma__BookingClient<runtime.Types.Result.GetResult<Prisma.$BookingPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions>
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onfulfilled The callback to execute when the Promise is resolved.
@@ -1544,6 +1640,25 @@ export type TripParticipantDeleteManyArgs<ExtArgs extends runtime.Types.Extensio
limit?: number
}
/**
* TripParticipant.booking
*/
export type TripParticipant$bookingArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
/**
* Select specific fields to fetch from the Booking
*/
select?: Prisma.BookingSelect<ExtArgs> | null
/**
* Omit specific fields from the Booking
*/
omit?: Prisma.BookingOmit<ExtArgs> | null
/**
* Choose, which related nodes to fetch as well
*/
include?: Prisma.BookingInclude<ExtArgs> | null
where?: Prisma.BookingWhereInput
}
/**
* TripParticipant without action
*/
+182
View File
@@ -226,6 +226,7 @@ export type UserWhereInput = {
trips?: Prisma.TripListRelationFilter
participations?: Prisma.TripParticipantListRelationFilter
tripReviews?: Prisma.TripReviewListRelationFilter
bookings?: Prisma.BookingListRelationFilter
organizerVerification?: Prisma.XOR<Prisma.OrganizerVerificationNullableScalarRelationFilter, Prisma.OrganizerVerificationWhereInput> | null
reviewedVerifications?: Prisma.OrganizerVerificationListRelationFilter
profile?: Prisma.XOR<Prisma.UserProfileNullableScalarRelationFilter, Prisma.UserProfileWhereInput> | null
@@ -246,6 +247,7 @@ export type UserOrderByWithRelationInput = {
trips?: Prisma.TripOrderByRelationAggregateInput
participations?: Prisma.TripParticipantOrderByRelationAggregateInput
tripReviews?: Prisma.TripReviewOrderByRelationAggregateInput
bookings?: Prisma.BookingOrderByRelationAggregateInput
organizerVerification?: Prisma.OrganizerVerificationOrderByWithRelationInput
reviewedVerifications?: Prisma.OrganizerVerificationOrderByRelationAggregateInput
profile?: Prisma.UserProfileOrderByWithRelationInput
@@ -269,6 +271,7 @@ export type UserWhereUniqueInput = Prisma.AtLeast<{
trips?: Prisma.TripListRelationFilter
participations?: Prisma.TripParticipantListRelationFilter
tripReviews?: Prisma.TripReviewListRelationFilter
bookings?: Prisma.BookingListRelationFilter
organizerVerification?: Prisma.XOR<Prisma.OrganizerVerificationNullableScalarRelationFilter, Prisma.OrganizerVerificationWhereInput> | null
reviewedVerifications?: Prisma.OrganizerVerificationListRelationFilter
profile?: Prisma.XOR<Prisma.UserProfileNullableScalarRelationFilter, Prisma.UserProfileWhereInput> | null
@@ -321,6 +324,7 @@ export type UserCreateInput = {
trips?: Prisma.TripCreateNestedManyWithoutOrganizerInput
participations?: Prisma.TripParticipantCreateNestedManyWithoutUserInput
tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput
bookings?: Prisma.BookingCreateNestedManyWithoutUserInput
organizerVerification?: Prisma.OrganizerVerificationCreateNestedOneWithoutUserInput
reviewedVerifications?: Prisma.OrganizerVerificationCreateNestedManyWithoutReviewedByInput
profile?: Prisma.UserProfileCreateNestedOneWithoutUserInput
@@ -341,6 +345,7 @@ export type UserUncheckedCreateInput = {
trips?: Prisma.TripUncheckedCreateNestedManyWithoutOrganizerInput
participations?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutUserInput
tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput
bookings?: Prisma.BookingUncheckedCreateNestedManyWithoutUserInput
organizerVerification?: Prisma.OrganizerVerificationUncheckedCreateNestedOneWithoutUserInput
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedCreateNestedManyWithoutReviewedByInput
profile?: Prisma.UserProfileUncheckedCreateNestedOneWithoutUserInput
@@ -361,6 +366,7 @@ export type UserUpdateInput = {
trips?: Prisma.TripUpdateManyWithoutOrganizerNestedInput
participations?: Prisma.TripParticipantUpdateManyWithoutUserNestedInput
tripReviews?: Prisma.TripReviewUpdateManyWithoutUserNestedInput
bookings?: Prisma.BookingUpdateManyWithoutUserNestedInput
organizerVerification?: Prisma.OrganizerVerificationUpdateOneWithoutUserNestedInput
reviewedVerifications?: Prisma.OrganizerVerificationUpdateManyWithoutReviewedByNestedInput
profile?: Prisma.UserProfileUpdateOneWithoutUserNestedInput
@@ -381,6 +387,7 @@ export type UserUncheckedUpdateInput = {
trips?: Prisma.TripUncheckedUpdateManyWithoutOrganizerNestedInput
participations?: Prisma.TripParticipantUncheckedUpdateManyWithoutUserNestedInput
tripReviews?: Prisma.TripReviewUncheckedUpdateManyWithoutUserNestedInput
bookings?: Prisma.BookingUncheckedUpdateManyWithoutUserNestedInput
organizerVerification?: Prisma.OrganizerVerificationUncheckedUpdateOneWithoutUserNestedInput
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedUpdateManyWithoutReviewedByNestedInput
profile?: Prisma.UserProfileUncheckedUpdateOneWithoutUserNestedInput
@@ -594,6 +601,20 @@ export type UserUpdateOneRequiredWithoutParticipationsNestedInput = {
update?: Prisma.XOR<Prisma.XOR<Prisma.UserUpdateToOneWithWhereWithoutParticipationsInput, Prisma.UserUpdateWithoutParticipationsInput>, Prisma.UserUncheckedUpdateWithoutParticipationsInput>
}
export type UserCreateNestedOneWithoutBookingsInput = {
create?: Prisma.XOR<Prisma.UserCreateWithoutBookingsInput, Prisma.UserUncheckedCreateWithoutBookingsInput>
connectOrCreate?: Prisma.UserCreateOrConnectWithoutBookingsInput
connect?: Prisma.UserWhereUniqueInput
}
export type UserUpdateOneRequiredWithoutBookingsNestedInput = {
create?: Prisma.XOR<Prisma.UserCreateWithoutBookingsInput, Prisma.UserUncheckedCreateWithoutBookingsInput>
connectOrCreate?: Prisma.UserCreateOrConnectWithoutBookingsInput
upsert?: Prisma.UserUpsertWithoutBookingsInput
connect?: Prisma.UserWhereUniqueInput
update?: Prisma.XOR<Prisma.XOR<Prisma.UserUpdateToOneWithWhereWithoutBookingsInput, Prisma.UserUpdateWithoutBookingsInput>, Prisma.UserUncheckedUpdateWithoutBookingsInput>
}
export type UserCreateWithoutProfileInput = {
id?: string
name: string
@@ -609,6 +630,7 @@ export type UserCreateWithoutProfileInput = {
trips?: Prisma.TripCreateNestedManyWithoutOrganizerInput
participations?: Prisma.TripParticipantCreateNestedManyWithoutUserInput
tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput
bookings?: Prisma.BookingCreateNestedManyWithoutUserInput
organizerVerification?: Prisma.OrganizerVerificationCreateNestedOneWithoutUserInput
reviewedVerifications?: Prisma.OrganizerVerificationCreateNestedManyWithoutReviewedByInput
}
@@ -628,6 +650,7 @@ export type UserUncheckedCreateWithoutProfileInput = {
trips?: Prisma.TripUncheckedCreateNestedManyWithoutOrganizerInput
participations?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutUserInput
tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput
bookings?: Prisma.BookingUncheckedCreateNestedManyWithoutUserInput
organizerVerification?: Prisma.OrganizerVerificationUncheckedCreateNestedOneWithoutUserInput
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedCreateNestedManyWithoutReviewedByInput
}
@@ -663,6 +686,7 @@ export type UserUpdateWithoutProfileInput = {
trips?: Prisma.TripUpdateManyWithoutOrganizerNestedInput
participations?: Prisma.TripParticipantUpdateManyWithoutUserNestedInput
tripReviews?: Prisma.TripReviewUpdateManyWithoutUserNestedInput
bookings?: Prisma.BookingUpdateManyWithoutUserNestedInput
organizerVerification?: Prisma.OrganizerVerificationUpdateOneWithoutUserNestedInput
reviewedVerifications?: Prisma.OrganizerVerificationUpdateManyWithoutReviewedByNestedInput
}
@@ -682,6 +706,7 @@ export type UserUncheckedUpdateWithoutProfileInput = {
trips?: Prisma.TripUncheckedUpdateManyWithoutOrganizerNestedInput
participations?: Prisma.TripParticipantUncheckedUpdateManyWithoutUserNestedInput
tripReviews?: Prisma.TripReviewUncheckedUpdateManyWithoutUserNestedInput
bookings?: Prisma.BookingUncheckedUpdateManyWithoutUserNestedInput
organizerVerification?: Prisma.OrganizerVerificationUncheckedUpdateOneWithoutUserNestedInput
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedUpdateManyWithoutReviewedByNestedInput
}
@@ -700,6 +725,7 @@ export type UserCreateWithoutAccountsInput = {
trips?: Prisma.TripCreateNestedManyWithoutOrganizerInput
participations?: Prisma.TripParticipantCreateNestedManyWithoutUserInput
tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput
bookings?: Prisma.BookingCreateNestedManyWithoutUserInput
organizerVerification?: Prisma.OrganizerVerificationCreateNestedOneWithoutUserInput
reviewedVerifications?: Prisma.OrganizerVerificationCreateNestedManyWithoutReviewedByInput
profile?: Prisma.UserProfileCreateNestedOneWithoutUserInput
@@ -719,6 +745,7 @@ export type UserUncheckedCreateWithoutAccountsInput = {
trips?: Prisma.TripUncheckedCreateNestedManyWithoutOrganizerInput
participations?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutUserInput
tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput
bookings?: Prisma.BookingUncheckedCreateNestedManyWithoutUserInput
organizerVerification?: Prisma.OrganizerVerificationUncheckedCreateNestedOneWithoutUserInput
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedCreateNestedManyWithoutReviewedByInput
profile?: Prisma.UserProfileUncheckedCreateNestedOneWithoutUserInput
@@ -754,6 +781,7 @@ export type UserUpdateWithoutAccountsInput = {
trips?: Prisma.TripUpdateManyWithoutOrganizerNestedInput
participations?: Prisma.TripParticipantUpdateManyWithoutUserNestedInput
tripReviews?: Prisma.TripReviewUpdateManyWithoutUserNestedInput
bookings?: Prisma.BookingUpdateManyWithoutUserNestedInput
organizerVerification?: Prisma.OrganizerVerificationUpdateOneWithoutUserNestedInput
reviewedVerifications?: Prisma.OrganizerVerificationUpdateManyWithoutReviewedByNestedInput
profile?: Prisma.UserProfileUpdateOneWithoutUserNestedInput
@@ -773,6 +801,7 @@ export type UserUncheckedUpdateWithoutAccountsInput = {
trips?: Prisma.TripUncheckedUpdateManyWithoutOrganizerNestedInput
participations?: Prisma.TripParticipantUncheckedUpdateManyWithoutUserNestedInput
tripReviews?: Prisma.TripReviewUncheckedUpdateManyWithoutUserNestedInput
bookings?: Prisma.BookingUncheckedUpdateManyWithoutUserNestedInput
organizerVerification?: Prisma.OrganizerVerificationUncheckedUpdateOneWithoutUserNestedInput
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedUpdateManyWithoutReviewedByNestedInput
profile?: Prisma.UserProfileUncheckedUpdateOneWithoutUserNestedInput
@@ -793,6 +822,7 @@ export type UserCreateWithoutOrganizerVerificationInput = {
trips?: Prisma.TripCreateNestedManyWithoutOrganizerInput
participations?: Prisma.TripParticipantCreateNestedManyWithoutUserInput
tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput
bookings?: Prisma.BookingCreateNestedManyWithoutUserInput
reviewedVerifications?: Prisma.OrganizerVerificationCreateNestedManyWithoutReviewedByInput
profile?: Prisma.UserProfileCreateNestedOneWithoutUserInput
}
@@ -812,6 +842,7 @@ export type UserUncheckedCreateWithoutOrganizerVerificationInput = {
trips?: Prisma.TripUncheckedCreateNestedManyWithoutOrganizerInput
participations?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutUserInput
tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput
bookings?: Prisma.BookingUncheckedCreateNestedManyWithoutUserInput
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedCreateNestedManyWithoutReviewedByInput
profile?: Prisma.UserProfileUncheckedCreateNestedOneWithoutUserInput
}
@@ -836,6 +867,7 @@ export type UserCreateWithoutReviewedVerificationsInput = {
trips?: Prisma.TripCreateNestedManyWithoutOrganizerInput
participations?: Prisma.TripParticipantCreateNestedManyWithoutUserInput
tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput
bookings?: Prisma.BookingCreateNestedManyWithoutUserInput
organizerVerification?: Prisma.OrganizerVerificationCreateNestedOneWithoutUserInput
profile?: Prisma.UserProfileCreateNestedOneWithoutUserInput
}
@@ -855,6 +887,7 @@ export type UserUncheckedCreateWithoutReviewedVerificationsInput = {
trips?: Prisma.TripUncheckedCreateNestedManyWithoutOrganizerInput
participations?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutUserInput
tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput
bookings?: Prisma.BookingUncheckedCreateNestedManyWithoutUserInput
organizerVerification?: Prisma.OrganizerVerificationUncheckedCreateNestedOneWithoutUserInput
profile?: Prisma.UserProfileUncheckedCreateNestedOneWithoutUserInput
}
@@ -890,6 +923,7 @@ export type UserUpdateWithoutOrganizerVerificationInput = {
trips?: Prisma.TripUpdateManyWithoutOrganizerNestedInput
participations?: Prisma.TripParticipantUpdateManyWithoutUserNestedInput
tripReviews?: Prisma.TripReviewUpdateManyWithoutUserNestedInput
bookings?: Prisma.BookingUpdateManyWithoutUserNestedInput
reviewedVerifications?: Prisma.OrganizerVerificationUpdateManyWithoutReviewedByNestedInput
profile?: Prisma.UserProfileUpdateOneWithoutUserNestedInput
}
@@ -909,6 +943,7 @@ export type UserUncheckedUpdateWithoutOrganizerVerificationInput = {
trips?: Prisma.TripUncheckedUpdateManyWithoutOrganizerNestedInput
participations?: Prisma.TripParticipantUncheckedUpdateManyWithoutUserNestedInput
tripReviews?: Prisma.TripReviewUncheckedUpdateManyWithoutUserNestedInput
bookings?: Prisma.BookingUncheckedUpdateManyWithoutUserNestedInput
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedUpdateManyWithoutReviewedByNestedInput
profile?: Prisma.UserProfileUncheckedUpdateOneWithoutUserNestedInput
}
@@ -939,6 +974,7 @@ export type UserUpdateWithoutReviewedVerificationsInput = {
trips?: Prisma.TripUpdateManyWithoutOrganizerNestedInput
participations?: Prisma.TripParticipantUpdateManyWithoutUserNestedInput
tripReviews?: Prisma.TripReviewUpdateManyWithoutUserNestedInput
bookings?: Prisma.BookingUpdateManyWithoutUserNestedInput
organizerVerification?: Prisma.OrganizerVerificationUpdateOneWithoutUserNestedInput
profile?: Prisma.UserProfileUpdateOneWithoutUserNestedInput
}
@@ -958,6 +994,7 @@ export type UserUncheckedUpdateWithoutReviewedVerificationsInput = {
trips?: Prisma.TripUncheckedUpdateManyWithoutOrganizerNestedInput
participations?: Prisma.TripParticipantUncheckedUpdateManyWithoutUserNestedInput
tripReviews?: Prisma.TripReviewUncheckedUpdateManyWithoutUserNestedInput
bookings?: Prisma.BookingUncheckedUpdateManyWithoutUserNestedInput
organizerVerification?: Prisma.OrganizerVerificationUncheckedUpdateOneWithoutUserNestedInput
profile?: Prisma.UserProfileUncheckedUpdateOneWithoutUserNestedInput
}
@@ -976,6 +1013,7 @@ export type UserCreateWithoutTripsInput = {
accounts?: Prisma.AccountCreateNestedManyWithoutUserInput
participations?: Prisma.TripParticipantCreateNestedManyWithoutUserInput
tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput
bookings?: Prisma.BookingCreateNestedManyWithoutUserInput
organizerVerification?: Prisma.OrganizerVerificationCreateNestedOneWithoutUserInput
reviewedVerifications?: Prisma.OrganizerVerificationCreateNestedManyWithoutReviewedByInput
profile?: Prisma.UserProfileCreateNestedOneWithoutUserInput
@@ -995,6 +1033,7 @@ export type UserUncheckedCreateWithoutTripsInput = {
accounts?: Prisma.AccountUncheckedCreateNestedManyWithoutUserInput
participations?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutUserInput
tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput
bookings?: Prisma.BookingUncheckedCreateNestedManyWithoutUserInput
organizerVerification?: Prisma.OrganizerVerificationUncheckedCreateNestedOneWithoutUserInput
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedCreateNestedManyWithoutReviewedByInput
profile?: Prisma.UserProfileUncheckedCreateNestedOneWithoutUserInput
@@ -1030,6 +1069,7 @@ export type UserUpdateWithoutTripsInput = {
accounts?: Prisma.AccountUpdateManyWithoutUserNestedInput
participations?: Prisma.TripParticipantUpdateManyWithoutUserNestedInput
tripReviews?: Prisma.TripReviewUpdateManyWithoutUserNestedInput
bookings?: Prisma.BookingUpdateManyWithoutUserNestedInput
organizerVerification?: Prisma.OrganizerVerificationUpdateOneWithoutUserNestedInput
reviewedVerifications?: Prisma.OrganizerVerificationUpdateManyWithoutReviewedByNestedInput
profile?: Prisma.UserProfileUpdateOneWithoutUserNestedInput
@@ -1049,6 +1089,7 @@ export type UserUncheckedUpdateWithoutTripsInput = {
accounts?: Prisma.AccountUncheckedUpdateManyWithoutUserNestedInput
participations?: Prisma.TripParticipantUncheckedUpdateManyWithoutUserNestedInput
tripReviews?: Prisma.TripReviewUncheckedUpdateManyWithoutUserNestedInput
bookings?: Prisma.BookingUncheckedUpdateManyWithoutUserNestedInput
organizerVerification?: Prisma.OrganizerVerificationUncheckedUpdateOneWithoutUserNestedInput
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedUpdateManyWithoutReviewedByNestedInput
profile?: Prisma.UserProfileUncheckedUpdateOneWithoutUserNestedInput
@@ -1068,6 +1109,7 @@ export type UserCreateWithoutTripReviewsInput = {
accounts?: Prisma.AccountCreateNestedManyWithoutUserInput
trips?: Prisma.TripCreateNestedManyWithoutOrganizerInput
participations?: Prisma.TripParticipantCreateNestedManyWithoutUserInput
bookings?: Prisma.BookingCreateNestedManyWithoutUserInput
organizerVerification?: Prisma.OrganizerVerificationCreateNestedOneWithoutUserInput
reviewedVerifications?: Prisma.OrganizerVerificationCreateNestedManyWithoutReviewedByInput
profile?: Prisma.UserProfileCreateNestedOneWithoutUserInput
@@ -1087,6 +1129,7 @@ export type UserUncheckedCreateWithoutTripReviewsInput = {
accounts?: Prisma.AccountUncheckedCreateNestedManyWithoutUserInput
trips?: Prisma.TripUncheckedCreateNestedManyWithoutOrganizerInput
participations?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutUserInput
bookings?: Prisma.BookingUncheckedCreateNestedManyWithoutUserInput
organizerVerification?: Prisma.OrganizerVerificationUncheckedCreateNestedOneWithoutUserInput
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedCreateNestedManyWithoutReviewedByInput
profile?: Prisma.UserProfileUncheckedCreateNestedOneWithoutUserInput
@@ -1122,6 +1165,7 @@ export type UserUpdateWithoutTripReviewsInput = {
accounts?: Prisma.AccountUpdateManyWithoutUserNestedInput
trips?: Prisma.TripUpdateManyWithoutOrganizerNestedInput
participations?: Prisma.TripParticipantUpdateManyWithoutUserNestedInput
bookings?: Prisma.BookingUpdateManyWithoutUserNestedInput
organizerVerification?: Prisma.OrganizerVerificationUpdateOneWithoutUserNestedInput
reviewedVerifications?: Prisma.OrganizerVerificationUpdateManyWithoutReviewedByNestedInput
profile?: Prisma.UserProfileUpdateOneWithoutUserNestedInput
@@ -1141,6 +1185,7 @@ export type UserUncheckedUpdateWithoutTripReviewsInput = {
accounts?: Prisma.AccountUncheckedUpdateManyWithoutUserNestedInput
trips?: Prisma.TripUncheckedUpdateManyWithoutOrganizerNestedInput
participations?: Prisma.TripParticipantUncheckedUpdateManyWithoutUserNestedInput
bookings?: Prisma.BookingUncheckedUpdateManyWithoutUserNestedInput
organizerVerification?: Prisma.OrganizerVerificationUncheckedUpdateOneWithoutUserNestedInput
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedUpdateManyWithoutReviewedByNestedInput
profile?: Prisma.UserProfileUncheckedUpdateOneWithoutUserNestedInput
@@ -1160,6 +1205,7 @@ export type UserCreateWithoutParticipationsInput = {
accounts?: Prisma.AccountCreateNestedManyWithoutUserInput
trips?: Prisma.TripCreateNestedManyWithoutOrganizerInput
tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput
bookings?: Prisma.BookingCreateNestedManyWithoutUserInput
organizerVerification?: Prisma.OrganizerVerificationCreateNestedOneWithoutUserInput
reviewedVerifications?: Prisma.OrganizerVerificationCreateNestedManyWithoutReviewedByInput
profile?: Prisma.UserProfileCreateNestedOneWithoutUserInput
@@ -1179,6 +1225,7 @@ export type UserUncheckedCreateWithoutParticipationsInput = {
accounts?: Prisma.AccountUncheckedCreateNestedManyWithoutUserInput
trips?: Prisma.TripUncheckedCreateNestedManyWithoutOrganizerInput
tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput
bookings?: Prisma.BookingUncheckedCreateNestedManyWithoutUserInput
organizerVerification?: Prisma.OrganizerVerificationUncheckedCreateNestedOneWithoutUserInput
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedCreateNestedManyWithoutReviewedByInput
profile?: Prisma.UserProfileUncheckedCreateNestedOneWithoutUserInput
@@ -1214,6 +1261,7 @@ export type UserUpdateWithoutParticipationsInput = {
accounts?: Prisma.AccountUpdateManyWithoutUserNestedInput
trips?: Prisma.TripUpdateManyWithoutOrganizerNestedInput
tripReviews?: Prisma.TripReviewUpdateManyWithoutUserNestedInput
bookings?: Prisma.BookingUpdateManyWithoutUserNestedInput
organizerVerification?: Prisma.OrganizerVerificationUpdateOneWithoutUserNestedInput
reviewedVerifications?: Prisma.OrganizerVerificationUpdateManyWithoutReviewedByNestedInput
profile?: Prisma.UserProfileUpdateOneWithoutUserNestedInput
@@ -1233,6 +1281,103 @@ export type UserUncheckedUpdateWithoutParticipationsInput = {
accounts?: Prisma.AccountUncheckedUpdateManyWithoutUserNestedInput
trips?: Prisma.TripUncheckedUpdateManyWithoutOrganizerNestedInput
tripReviews?: Prisma.TripReviewUncheckedUpdateManyWithoutUserNestedInput
bookings?: Prisma.BookingUncheckedUpdateManyWithoutUserNestedInput
organizerVerification?: Prisma.OrganizerVerificationUncheckedUpdateOneWithoutUserNestedInput
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedUpdateManyWithoutReviewedByNestedInput
profile?: Prisma.UserProfileUncheckedUpdateOneWithoutUserNestedInput
}
export type UserCreateWithoutBookingsInput = {
id?: string
name: string
email: string
password?: string | null
image?: string | null
emailVerified?: Date | string | null
acceptedTermsAndPrivacy?: boolean
acceptedAt?: Date | string | null
createdAt?: Date | string
updatedAt?: Date | string
accounts?: Prisma.AccountCreateNestedManyWithoutUserInput
trips?: Prisma.TripCreateNestedManyWithoutOrganizerInput
participations?: Prisma.TripParticipantCreateNestedManyWithoutUserInput
tripReviews?: Prisma.TripReviewCreateNestedManyWithoutUserInput
organizerVerification?: Prisma.OrganizerVerificationCreateNestedOneWithoutUserInput
reviewedVerifications?: Prisma.OrganizerVerificationCreateNestedManyWithoutReviewedByInput
profile?: Prisma.UserProfileCreateNestedOneWithoutUserInput
}
export type UserUncheckedCreateWithoutBookingsInput = {
id?: string
name: string
email: string
password?: string | null
image?: string | null
emailVerified?: Date | string | null
acceptedTermsAndPrivacy?: boolean
acceptedAt?: Date | string | null
createdAt?: Date | string
updatedAt?: Date | string
accounts?: Prisma.AccountUncheckedCreateNestedManyWithoutUserInput
trips?: Prisma.TripUncheckedCreateNestedManyWithoutOrganizerInput
participations?: Prisma.TripParticipantUncheckedCreateNestedManyWithoutUserInput
tripReviews?: Prisma.TripReviewUncheckedCreateNestedManyWithoutUserInput
organizerVerification?: Prisma.OrganizerVerificationUncheckedCreateNestedOneWithoutUserInput
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedCreateNestedManyWithoutReviewedByInput
profile?: Prisma.UserProfileUncheckedCreateNestedOneWithoutUserInput
}
export type UserCreateOrConnectWithoutBookingsInput = {
where: Prisma.UserWhereUniqueInput
create: Prisma.XOR<Prisma.UserCreateWithoutBookingsInput, Prisma.UserUncheckedCreateWithoutBookingsInput>
}
export type UserUpsertWithoutBookingsInput = {
update: Prisma.XOR<Prisma.UserUpdateWithoutBookingsInput, Prisma.UserUncheckedUpdateWithoutBookingsInput>
create: Prisma.XOR<Prisma.UserCreateWithoutBookingsInput, Prisma.UserUncheckedCreateWithoutBookingsInput>
where?: Prisma.UserWhereInput
}
export type UserUpdateToOneWithWhereWithoutBookingsInput = {
where?: Prisma.UserWhereInput
data: Prisma.XOR<Prisma.UserUpdateWithoutBookingsInput, Prisma.UserUncheckedUpdateWithoutBookingsInput>
}
export type UserUpdateWithoutBookingsInput = {
id?: Prisma.StringFieldUpdateOperationsInput | string
name?: Prisma.StringFieldUpdateOperationsInput | string
email?: Prisma.StringFieldUpdateOperationsInput | string
password?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
emailVerified?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
acceptedTermsAndPrivacy?: Prisma.BoolFieldUpdateOperationsInput | boolean
acceptedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
accounts?: Prisma.AccountUpdateManyWithoutUserNestedInput
trips?: Prisma.TripUpdateManyWithoutOrganizerNestedInput
participations?: Prisma.TripParticipantUpdateManyWithoutUserNestedInput
tripReviews?: Prisma.TripReviewUpdateManyWithoutUserNestedInput
organizerVerification?: Prisma.OrganizerVerificationUpdateOneWithoutUserNestedInput
reviewedVerifications?: Prisma.OrganizerVerificationUpdateManyWithoutReviewedByNestedInput
profile?: Prisma.UserProfileUpdateOneWithoutUserNestedInput
}
export type UserUncheckedUpdateWithoutBookingsInput = {
id?: Prisma.StringFieldUpdateOperationsInput | string
name?: Prisma.StringFieldUpdateOperationsInput | string
email?: Prisma.StringFieldUpdateOperationsInput | string
password?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
image?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
emailVerified?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
acceptedTermsAndPrivacy?: Prisma.BoolFieldUpdateOperationsInput | boolean
acceptedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
accounts?: Prisma.AccountUncheckedUpdateManyWithoutUserNestedInput
trips?: Prisma.TripUncheckedUpdateManyWithoutOrganizerNestedInput
participations?: Prisma.TripParticipantUncheckedUpdateManyWithoutUserNestedInput
tripReviews?: Prisma.TripReviewUncheckedUpdateManyWithoutUserNestedInput
organizerVerification?: Prisma.OrganizerVerificationUncheckedUpdateOneWithoutUserNestedInput
reviewedVerifications?: Prisma.OrganizerVerificationUncheckedUpdateManyWithoutReviewedByNestedInput
profile?: Prisma.UserProfileUncheckedUpdateOneWithoutUserNestedInput
@@ -1248,6 +1393,7 @@ export type UserCountOutputType = {
trips: number
participations: number
tripReviews: number
bookings: number
reviewedVerifications: number
}
@@ -1256,6 +1402,7 @@ export type UserCountOutputTypeSelect<ExtArgs extends runtime.Types.Extensions.I
trips?: boolean | UserCountOutputTypeCountTripsArgs
participations?: boolean | UserCountOutputTypeCountParticipationsArgs
tripReviews?: boolean | UserCountOutputTypeCountTripReviewsArgs
bookings?: boolean | UserCountOutputTypeCountBookingsArgs
reviewedVerifications?: boolean | UserCountOutputTypeCountReviewedVerificationsArgs
}
@@ -1297,6 +1444,13 @@ export type UserCountOutputTypeCountTripReviewsArgs<ExtArgs extends runtime.Type
where?: Prisma.TripReviewWhereInput
}
/**
* UserCountOutputType without action
*/
export type UserCountOutputTypeCountBookingsArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
where?: Prisma.BookingWhereInput
}
/**
* UserCountOutputType without action
*/
@@ -1320,6 +1474,7 @@ export type UserSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = r
trips?: boolean | Prisma.User$tripsArgs<ExtArgs>
participations?: boolean | Prisma.User$participationsArgs<ExtArgs>
tripReviews?: boolean | Prisma.User$tripReviewsArgs<ExtArgs>
bookings?: boolean | Prisma.User$bookingsArgs<ExtArgs>
organizerVerification?: boolean | Prisma.User$organizerVerificationArgs<ExtArgs>
reviewedVerifications?: boolean | Prisma.User$reviewedVerificationsArgs<ExtArgs>
profile?: boolean | Prisma.User$profileArgs<ExtArgs>
@@ -1371,6 +1526,7 @@ export type UserInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs =
trips?: boolean | Prisma.User$tripsArgs<ExtArgs>
participations?: boolean | Prisma.User$participationsArgs<ExtArgs>
tripReviews?: boolean | Prisma.User$tripReviewsArgs<ExtArgs>
bookings?: boolean | Prisma.User$bookingsArgs<ExtArgs>
organizerVerification?: boolean | Prisma.User$organizerVerificationArgs<ExtArgs>
reviewedVerifications?: boolean | Prisma.User$reviewedVerificationsArgs<ExtArgs>
profile?: boolean | Prisma.User$profileArgs<ExtArgs>
@@ -1386,6 +1542,7 @@ export type $UserPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs =
trips: Prisma.$TripPayload<ExtArgs>[]
participations: Prisma.$TripParticipantPayload<ExtArgs>[]
tripReviews: Prisma.$TripReviewPayload<ExtArgs>[]
bookings: Prisma.$BookingPayload<ExtArgs>[]
organizerVerification: Prisma.$OrganizerVerificationPayload<ExtArgs> | null
reviewedVerifications: Prisma.$OrganizerVerificationPayload<ExtArgs>[]
profile: Prisma.$UserProfilePayload<ExtArgs> | null
@@ -1811,6 +1968,7 @@ export interface Prisma__UserClient<T, Null = never, ExtArgs extends runtime.Typ
trips<T extends Prisma.User$tripsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$tripsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$TripPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
participations<T extends Prisma.User$participationsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$participationsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$TripParticipantPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
tripReviews<T extends Prisma.User$tripReviewsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$tripReviewsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$TripReviewPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
bookings<T extends Prisma.User$bookingsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$bookingsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$BookingPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
organizerVerification<T extends Prisma.User$organizerVerificationArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$organizerVerificationArgs<ExtArgs>>): Prisma.Prisma__OrganizerVerificationClient<runtime.Types.Result.GetResult<Prisma.$OrganizerVerificationPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions>
reviewedVerifications<T extends Prisma.User$reviewedVerificationsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$reviewedVerificationsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$OrganizerVerificationPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
profile<T extends Prisma.User$profileArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$profileArgs<ExtArgs>>): Prisma.Prisma__UserProfileClient<runtime.Types.Result.GetResult<Prisma.$UserProfilePayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions>
@@ -2341,6 +2499,30 @@ export type User$tripReviewsArgs<ExtArgs extends runtime.Types.Extensions.Intern
distinct?: Prisma.TripReviewScalarFieldEnum | Prisma.TripReviewScalarFieldEnum[]
}
/**
* User.bookings
*/
export type User$bookingsArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
/**
* Select specific fields to fetch from the Booking
*/
select?: Prisma.BookingSelect<ExtArgs> | null
/**
* Omit specific fields from the Booking
*/
omit?: Prisma.BookingOmit<ExtArgs> | null
/**
* Choose, which related nodes to fetch as well
*/
include?: Prisma.BookingInclude<ExtArgs> | null
where?: Prisma.BookingWhereInput
orderBy?: Prisma.BookingOrderByWithRelationInput | Prisma.BookingOrderByWithRelationInput[]
cursor?: Prisma.BookingWhereUniqueInput
take?: number
skip?: number
distinct?: Prisma.BookingScalarFieldEnum | Prisma.BookingScalarFieldEnum[]
}
/**
* User.organizerVerification
*/
+16 -9
View File
@@ -5,6 +5,7 @@ import Link from "next/link";
import Image from "next/image";
import { authOptions } from "@/lib/auth";
import { tripService } from "@/server/services/trip.service";
import { bookingService } from "@/server/services/booking.service";
import { trustService } from "@/server/services/trust.service";
import { formatRupiah } from "@/lib/utils";
import { formatTripCalendarDateRangeLong } from "@/lib/trip-dates";
@@ -18,6 +19,7 @@ import { ImageGallery } from "@/features/trip/components/image-gallery";
import { TripReviewSection } from "@/features/review/components/trip-review-section";
import { categoryMeta } from "@/lib/activity-category";
import { vibeMeta } from "@/lib/vibe";
import { isFreeTrip } from "@/lib/trip-pricing";
import {
isPastTripLastDayForReview,
isTripDepartureDayPast,
@@ -126,9 +128,14 @@ export default async function TripDetailPage({
) / 10
: null;
const paymentPendingParticipants = activeParticipants.filter(
(p) => p.markedPaidAt && !p.paymentConfirmedAt
);
const tripIsFree = isFreeTrip(trip);
// Antrian konfirmasi pembayaran: source dari Booking + Payment (B9).
// Hanya organizer yang butuh data ini, dan hanya untuk trip berbayar.
const paymentPendingBookings =
!tripIsFree && isOrganizer
? await bookingService.getAwaitingManualForTrip(trip.id)
: [];
const catMeta = categoryMeta(trip.category);
@@ -408,14 +415,13 @@ export default async function TripDetailPage({
/>
)}
{isOrganizer && paymentPendingParticipants.length > 0 && (
{isOrganizer && paymentPendingBookings.length > 0 && (
<OrganizerPaymentQueue
tripId={trip.id}
items={paymentPendingParticipants.map((p) => ({
id: p.id,
user: p.user,
joinStatus:
p.status === "PENDING" ? ("PENDING" as const) : ("CONFIRMED" as const),
items={paymentPendingBookings.map((b) => ({
id: b.participantId,
user: { name: b.user.name, image: b.user.image },
joinStatus: "CONFIRMED" as const,
}))}
/>
)}
@@ -426,6 +432,7 @@ export default async function TripDetailPage({
isLoggedIn={!!session?.user}
isOrganizer={isOrganizer}
isJoined={!!currentParticipation}
isFree={tripIsFree}
participationStatus={
currentParticipation?.status === "PENDING" ||
currentParticipation?.status === "CONFIRMED"
+411
View File
@@ -0,0 +1,411 @@
import type { Metadata } from "next";
import Link from "next/link";
import { notFound, redirect } from "next/navigation";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import { tripService } from "@/server/services/trip.service";
import { organizerService } from "@/server/services/organizer.service";
import { bookingService } from "@/server/services/booking.service";
import { formatRupiah } from "@/lib/utils";
import { formatTripCalendarDateRangeLong } from "@/lib/trip-dates";
import { isFreeTrip } from "@/lib/trip-pricing";
import { categoryMeta } from "@/lib/activity-category";
import { MarkPaidButton } from "@/features/booking/components/mark-paid-button";
import { CopyButton } from "@/features/booking/components/copy-button";
export const metadata: Metadata = {
title: "Detail Pembayaran",
robots: { index: false, follow: false },
};
interface PageProps {
params: Promise<{ id: string }>;
}
export default async function PaymentPage({ params }: PageProps) {
const { id } = await params;
const session = await getServerSession(authOptions);
if (!session?.user) {
redirect(`/login?callbackUrl=/trips/${id}/payment`);
}
let trip;
try {
trip = await tripService.getTripById(id);
} catch {
notFound();
}
// Organizer trip-nya sendiri tidak butuh halaman pembayaran.
if (trip.organizerId === session.user.id) {
redirect(`/trips/${id}`);
}
const booking = await bookingService.getByTripAndUser(
trip.id,
session.user.id
);
if (!booking || booking.status === "CANCELLED") {
return <NotJoinedNotice tripId={trip.id} title={trip.title} />;
}
const latestManualPayment = booking.payments.find(
(p) => p.provider === "MANUAL"
);
const tripIsFree = isFreeTrip(trip);
const catMeta = categoryMeta(trip.category);
const dateRange = formatTripCalendarDateRangeLong(trip.date, trip.endDate);
// Header info — sama untuk free vs paid
const tripHeader = (
<section className="mb-6 rounded-2xl border border-neutral-200 bg-white p-5 shadow-sm sm:p-6">
<div className="flex items-start gap-3">
<span className="flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-neutral-100 text-xl">
{catMeta.icon}
</span>
<div className="min-w-0 flex-1">
<p className="text-[11px] font-semibold uppercase tracking-wide text-neutral-500">
{catMeta.label}
</p>
<h1 className="mt-0.5 truncate text-base font-bold text-neutral-900 sm:text-lg">
{trip.title}
</h1>
<p className="mt-1 text-xs text-neutral-500 sm:text-sm">
📅 {dateRange} · 📍 {trip.location}
</p>
<p className="mt-1 text-xs text-neutral-500 sm:text-sm">
Organizer:{" "}
<Link
href={`/u/${trip.organizer.id}`}
className="font-medium text-neutral-700 hover:text-primary-600"
>
{trip.organizer.name}
</Link>
</p>
</div>
</div>
</section>
);
return (
<div className="mx-auto max-w-2xl px-4 py-6 sm:py-10">
<div className="mb-4 flex items-center gap-2 text-xs text-neutral-500 sm:text-sm">
<Link href={`/trips/${trip.id}`} className="hover:text-primary-600">
Kembali ke trip
</Link>
</div>
<h2 className="mb-1 text-xl font-bold text-neutral-900 sm:text-2xl">
Detail Pembayaran
</h2>
<p className="mb-5 text-sm text-neutral-500">
{tripIsFree
? "Trip ini gratis — kamu tidak perlu transfer apa-apa."
: "Transfer manual ke rekening organizer di bawah, lalu tandai sebagai sudah bayar."}
</p>
{tripHeader}
{tripIsFree ? (
<FreeTripSection
tripId={trip.id}
bookingStatus={booking.status}
/>
) : (
<PaidTripSection
tripId={trip.id}
organizerId={trip.organizerId}
organizerName={trip.organizer.name}
price={trip.price}
bookingStatus={booking.status}
paymentMarkedAt={
latestManualPayment?.status === "AWAITING"
? latestManualPayment.updatedAt
: null
}
paymentPaidAt={latestManualPayment?.paidAt ?? null}
/>
)}
</div>
);
}
function NotJoinedNotice({ tripId, title }: { tripId: string; title: string }) {
return (
<div className="mx-auto max-w-2xl px-4 py-12 text-center">
<h1 className="mb-2 text-xl font-bold text-neutral-900 sm:text-2xl">
Kamu belum terdaftar di trip ini
</h1>
<p className="mb-5 text-sm text-neutral-500">
Halaman pembayaran hanya tersedia untuk peserta trip{" "}
<span className="font-semibold text-neutral-700">{title}</span>.
</p>
<Link
href={`/trips/${tripId}`}
className="inline-block rounded-xl bg-primary-600 px-5 py-2.5 text-sm font-semibold text-white hover:bg-primary-700"
>
Lihat detail trip
</Link>
</div>
);
}
function FreeTripSection({
tripId,
bookingStatus,
}: {
tripId: string;
bookingStatus: "PENDING" | "AWAITING_PAY" | "PAID" | "CANCELLED" | "REFUNDED" | "EXPIRED";
}) {
return (
<section className="rounded-2xl border border-emerald-200 bg-emerald-50/60 p-6 text-center shadow-sm sm:p-8">
<div className="mx-auto mb-3 flex h-14 w-14 items-center justify-center rounded-full bg-emerald-100 text-2xl">
🎉
</div>
<h2 className="mb-1 text-lg font-bold text-emerald-900 sm:text-xl">
Trip ini gratis
</h2>
<p className="mb-5 text-sm text-emerald-900/80">
Tidak ada biaya yang perlu kamu transfer.
</p>
<div className="mx-auto inline-flex flex-col gap-1 rounded-xl border border-emerald-200 bg-white px-5 py-3 text-left">
<p className="text-[11px] font-semibold uppercase tracking-wide text-emerald-700">
Status keikutsertaan
</p>
<p className="text-sm font-bold text-neutral-800">
{bookingStatus === "PAID"
? "✅ Terkonfirmasi sebagai peserta"
: "⏳ Menunggu persetujuan organizer"}
</p>
</div>
<div className="mt-6">
<Link
href={`/trips/${tripId}`}
className="inline-block rounded-xl bg-emerald-600 px-5 py-2.5 text-sm font-semibold text-white hover:bg-emerald-700"
>
Kembali ke detail trip
</Link>
</div>
</section>
);
}
async function PaidTripSection({
tripId,
organizerId,
organizerName,
price,
bookingStatus,
paymentMarkedAt,
paymentPaidAt,
}: {
tripId: string;
organizerId: string;
organizerName: string;
price: number;
bookingStatus: "PENDING" | "AWAITING_PAY" | "PAID" | "CANCELLED" | "REFUNDED" | "EXPIRED";
paymentMarkedAt: Date | null;
paymentPaidAt: Date | null;
}) {
const verification = await organizerService.getStatusForUser(organizerId);
const bankAvailable = verification?.status === "APPROVED";
const isApproved = bookingStatus === "AWAITING_PAY" || bookingStatus === "PAID";
const isPendingApproval = bookingStatus === "PENDING";
const hasMarkedPaid = !!paymentMarkedAt || !!paymentPaidAt;
const isFullyPaid = bookingStatus === "PAID";
const canMarkPaid = bookingStatus === "AWAITING_PAY" && !paymentMarkedAt;
return (
<div className="space-y-5">
<PaymentTimeline
approved={isApproved}
markedPaid={hasMarkedPaid}
confirmedPaid={isFullyPaid}
/>
{!bankAvailable && (
<div className="rounded-2xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-900">
<p className="font-semibold">Rekening organizer belum tersedia</p>
<p className="mt-1 text-amber-800/90">
Organizer trip ini belum melengkapi data verifikasi (bank). Hubungi
organizer langsung lewat profilnya untuk koordinasi pembayaran.
</p>
</div>
)}
{bankAvailable && (
<section className="rounded-2xl border border-neutral-200 bg-white p-5 shadow-sm sm:p-6">
<h3 className="mb-1 text-sm font-bold text-neutral-900 sm:text-base">
Transfer ke rekening organizer
</h3>
<p className="mb-4 text-xs text-neutral-500 sm:text-sm">
Pastikan nominal persis seperti tercantum supaya organizer mudah
mencocokkan.
</p>
<div className="space-y-3 rounded-xl bg-neutral-50 p-4 sm:p-5">
<BankRow
label="Bank"
value={verification.bankName}
copyable
/>
<BankRow
label="Nomor rekening"
value={verification.bankAccountNumber}
copyable
mono
/>
<BankRow
label="Atas nama"
value={verification.bankAccountName}
/>
<div className="mt-2 border-t border-neutral-200 pt-3">
<BankRow
label="Nominal transfer"
value={formatRupiah(price)}
strong
copyable
copyValue={String(price)}
/>
</div>
</div>
<ul className="mt-4 space-y-1.5 text-[11px] text-neutral-500 sm:text-xs">
<li> Transfer dengan nominal pas, jangan dibulatkan.</li>
<li> Simpan bukti transfer untuk jaga-jaga jika ada konfirmasi.</li>
<li>
Setelah transfer, tekan tombol <em>Saya sudah bayar</em> di bawah
supaya organizer tahu dan bisa konfirmasi.
</li>
</ul>
</section>
)}
{isPendingApproval && (
<div className="rounded-2xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-900">
Kamu belum disetujui organizer untuk ikut trip ini. Tunggu persetujuan
dulu sebelum transfer supaya tidak perlu refund kalau ditolak.
</div>
)}
{canMarkPaid && bankAvailable && (
<MarkPaidButton tripId={tripId} />
)}
{hasMarkedPaid && (
<div className="rounded-2xl border border-neutral-200 bg-white p-4 text-sm text-neutral-600 shadow-sm sm:p-5">
{isFullyPaid ? (
<p>
Pembayaran kamu sudah dikonfirmasi oleh{" "}
<span className="font-semibold text-neutral-800">
{organizerName}
</span>
. Sampai jumpa di trip!
</p>
) : (
<p>
Kamu sudah menandai sudah bayar. Menunggu organizer mengecek
dan mengonfirmasi.
</p>
)}
</div>
)}
<div className="text-center">
<Link
href={`/trips/${tripId}`}
className="text-sm text-neutral-500 hover:text-primary-600"
>
Kembali ke detail trip
</Link>
</div>
</div>
);
}
function PaymentTimeline({
approved,
markedPaid,
confirmedPaid,
}: {
approved: boolean;
markedPaid: boolean;
confirmedPaid: boolean;
}) {
const steps = [
{ label: "Disetujui organizer", done: approved },
{ label: "Kamu menandai sudah bayar", done: markedPaid },
{ label: "Organizer konfirmasi pembayaran", done: confirmedPaid },
];
return (
<section className="rounded-2xl border border-neutral-200 bg-white p-4 shadow-sm sm:p-5">
<h3 className="mb-3 text-xs font-bold text-neutral-700 sm:text-sm">
Status pembayaran
</h3>
<ol className="space-y-2.5">
{steps.map((s, i) => (
<li key={i} className="flex items-start gap-3">
<span
className={`mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-[11px] font-bold ${
s.done
? "bg-emerald-500 text-white"
: "bg-neutral-200 text-neutral-500"
}`}
>
{s.done ? "✓" : i + 1}
</span>
<span
className={`text-sm ${
s.done
? "font-semibold text-neutral-800"
: "text-neutral-500"
}`}
>
{s.label}
</span>
</li>
))}
</ol>
</section>
);
}
function BankRow({
label,
value,
mono,
strong,
copyable,
copyValue,
}: {
label: string;
value: string;
mono?: boolean;
strong?: boolean;
copyable?: boolean;
copyValue?: string;
}) {
return (
<div className="flex items-center justify-between gap-3">
<div className="min-w-0">
<p className="text-[11px] font-semibold uppercase tracking-wide text-neutral-500">
{label}
</p>
<p
className={`mt-0.5 truncate text-sm text-neutral-800 ${
mono ? "font-mono" : ""
} ${strong ? "text-base font-bold text-primary-700" : ""}`}
>
{value}
</p>
</div>
{copyable && <CopyButton value={copyValue ?? value} />}
</div>
);
}
+19 -3
View File
@@ -72,7 +72,7 @@ export function Navbar() {
</Link>
<Link
href="/profile"
className="max-w-[140px] truncate text-sm font-medium text-neutral-700 hover:text-primary-600"
className="max-w-35 truncate text-sm font-medium text-neutral-700 hover:text-primary-600"
>
{session.user.name}
</Link>
@@ -109,11 +109,27 @@ export function Navbar() {
aria-label="Toggle menu"
>
{menuOpen ? (
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
>
<path d="M5 5l10 10M15 5L5 15" />
</svg>
) : (
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
>
<path d="M3 5h14M3 10h14M3 15h14" />
</svg>
)}
@@ -0,0 +1,32 @@
"use client";
import { useState } from "react";
interface CopyButtonProps {
value: string;
label?: string;
}
export function CopyButton({ value, label = "Salin" }: CopyButtonProps) {
const [copied, setCopied] = useState(false);
async function handleClick() {
try {
await navigator.clipboard.writeText(value);
setCopied(true);
setTimeout(() => setCopied(false), 1500);
} catch {
// ignore — user can copy manually
}
}
return (
<button
type="button"
onClick={handleClick}
className="rounded-lg border border-neutral-200 bg-white px-2.5 py-1 text-xs font-medium text-neutral-600 transition-colors hover:bg-neutral-50"
>
{copied ? "✓ Tersalin" : label}
</button>
);
}
@@ -0,0 +1,50 @@
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { markParticipantPaidAction } from "@/features/booking/actions";
interface MarkPaidButtonProps {
tripId: string;
disabled?: boolean;
}
export function MarkPaidButton({ tripId, disabled }: MarkPaidButtonProps) {
const router = useRouter();
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
async function handleClick() {
setLoading(true);
setError("");
const result = await markParticipantPaidAction(tripId);
setLoading(false);
if (result.error) {
setError(result.error);
return;
}
router.refresh();
}
return (
<div>
{error && (
<div className="mb-3 rounded-xl bg-red-50 px-4 py-3 text-sm font-medium text-red-600">
{error}
</div>
)}
<button
type="button"
onClick={handleClick}
disabled={loading || disabled}
className="w-full rounded-xl bg-primary-600 py-3 text-sm font-bold text-white shadow-lg shadow-primary-600/20 transition-colors hover:bg-primary-700 disabled:cursor-not-allowed disabled:opacity-50"
>
{loading ? "Memproses..." : "Saya sudah bayar"}
</button>
<p className="mt-2 text-center text-[11px] text-neutral-500">
Tekan setelah kamu transfer ke rekening di atas. Organizer akan cek &
konfirmasi pembayaran kamu.
</p>
</div>
);
}
+18 -14
View File
@@ -63,7 +63,8 @@ export function ReviewCard({ verification }: { verification: Verification }) {
{verification.user.name}
</h3>
<p className="text-xs text-neutral-500">
{verification.user.email} · diajukan {formatDate(verification.createdAt)}
{verification.user.email} · diajukan{" "}
{formatDate(verification.createdAt)}
</p>
</div>
<StatusPill status={verification.status} />
@@ -80,10 +81,7 @@ export function ReviewCard({ verification }: { verification: Verification }) {
label="Bank"
value={`${verification.bankName} · ${verification.bankAccountNumber}`}
/>
<Field
label="Pemilik Rekening"
value={verification.bankAccountName}
/>
<Field label="Pemilik Rekening" value={verification.bankAccountName} />
<Field label="Alamat" value={verification.address} fullWidth />
</div>
@@ -212,24 +210,30 @@ function ImagePreview({ label, src }: { label: string; src: string }) {
rel="noopener noreferrer"
className="block overflow-hidden rounded-xl border border-neutral-200 bg-neutral-100"
>
<div className="relative aspect-[4/3] w-full">
<div className="relative aspect-4/3 w-full">
{/* Secure endpoint sends Cache-Control: private,no-store. Use plain <img> to skip Next/Image optimizer. */}
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={src}
alt={label}
className="h-full w-full object-cover"
/>
<img src={src} alt={label} className="h-full w-full object-cover" />
</div>
</a>
</div>
);
}
function StatusPill({ status }: { status: "PENDING" | "APPROVED" | "REJECTED" }) {
function StatusPill({
status,
}: {
status: "PENDING" | "APPROVED" | "REJECTED";
}) {
const cfg = {
PENDING: { label: "Pending", cls: "bg-amber-50 text-amber-700 ring-amber-200" },
APPROVED: { label: "Disetujui", cls: "bg-primary-50 text-primary-700 ring-primary-200" },
PENDING: {
label: "Pending",
cls: "bg-amber-50 text-amber-700 ring-amber-200",
},
APPROVED: {
label: "Disetujui",
cls: "bg-primary-50 text-primary-700 ring-primary-200",
},
REJECTED: { label: "Ditolak", cls: "bg-red-50 text-red-700 ring-red-200" },
}[status];
return (
+19 -31
View File
@@ -4,16 +4,17 @@ import { useState } from "react";
import { useRouter } from "next/navigation";
import Link from "next/link";
import { joinTripAction, cancelJoinAction } from "@/features/trip/actions";
import { markParticipantPaidAction } from "@/features/booking/actions";
interface JoinTripButtonProps {
tripId: string;
isLoggedIn: boolean;
isOrganizer: boolean;
isJoined: boolean;
/** Trip gratis (price <= 0) — sembunyikan flow pembayaran */
isFree: boolean;
/** Status partisipasi user saat isJoined (bukan organizer) */
participationStatus?: "PENDING" | "CONFIRMED" | null;
/** Status pembayaran manual (peserta) */
/** Status pembayaran manual (peserta). Hanya relevan untuk trip berbayar. */
participantPayment?: {
markedPaidAt: string | Date | null;
paymentConfirmedAt: string | Date | null;
@@ -29,6 +30,7 @@ export function JoinTripButton({
isLoggedIn,
isOrganizer,
isJoined,
isFree,
participationStatus,
participantPayment,
isFull,
@@ -108,28 +110,11 @@ export function JoinTripButton({
}
}
async function handleMarkPaid() {
setLoading(true);
setError("");
const result = await markParticipantPaidAction(tripId);
setLoading(false);
if (result.error) {
setError(result.error);
} else {
router.refresh();
}
}
const pay = participantPayment;
const showMarkPaid =
isJoined &&
pay &&
!pay.paymentConfirmedAt &&
!pay.markedPaidAt &&
!isDeparturePast;
const showPaymentLink = !isFree && isJoined && !isDeparturePast;
const waitingPaymentConfirm =
isJoined && pay && pay.markedPaidAt && !pay.paymentConfirmedAt;
const paymentDone = isJoined && pay && pay.paymentConfirmedAt;
!isFree && isJoined && pay && pay.markedPaidAt && !pay.paymentConfirmedAt;
const paymentDone = !isFree && isJoined && pay && pay.paymentConfirmedAt;
return (
<div>
@@ -149,7 +134,8 @@ export function JoinTripButton({
<div className="mb-3 rounded-xl border border-secondary-200 bg-secondary-50 px-4 py-3 text-sm font-medium text-secondary-900">
Kamu sudah{" "}
<span className="font-semibold">terkonfirmasi</span> sebagai peserta
trip ini.
trip ini
{isFree && <span> trip gratis, tidak ada pembayaran 🎉</span>}.
</div>
)}
{waitingPaymentConfirm && (
@@ -164,15 +150,17 @@ export function JoinTripButton({
<span className="font-semibold">dikonfirmasi organizer</span>.
</div>
)}
{showMarkPaid && (
<button
type="button"
onClick={handleMarkPaid}
disabled={loading}
className="mb-3 w-full rounded-xl border-2 border-primary-500 bg-white py-3 text-sm font-bold text-primary-700 transition-colors hover:bg-primary-50 disabled:opacity-50"
{showPaymentLink && (
<Link
href={`/trips/${tripId}/payment`}
className="mb-3 block w-full rounded-xl border-2 border-primary-500 bg-white py-3 text-center text-sm font-bold text-primary-700 transition-colors hover:bg-primary-50"
>
{loading ? "Memproses..." : "Saya sudah bayar"}
</button>
{paymentDone
? "Lihat detail pembayaran"
: pay?.markedPaidAt
? "Lihat status pembayaran"
: "Buka detail pembayaran"}
</Link>
)}
{isJoined ? (
<button
+12
View File
@@ -0,0 +1,12 @@
/**
* Pricing helpers untuk trip. Single source of truth — semua gate "trip gratis"
* harus lewat sini, jangan compare `price === 0` inline.
*/
export function isFreeTrip(trip: { price: number }): boolean {
return trip.price <= 0;
}
export function isPaidTrip(trip: { price: number }): boolean {
return !isFreeTrip(trip);
}
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "setrip",
"version": "0.7.0",
"version": "0.9.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "setrip",
"version": "0.7.0",
"version": "0.9.0",
"dependencies": {
"@next-auth/prisma-adapter": "^1.0.7",
"@prisma/adapter-pg": "^7.7.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "setrip",
"version": "0.7.0",
"version": "0.9.0",
"private": true,
"scripts": {
"dev": "next dev",
+101
View File
@@ -0,0 +1,101 @@
/**
* Backfill Booking + Payment dari TripParticipant lama.
*
* Idempotent — jalan ulang aman. Skip baris yang sudah punya Booking.
*
* Mapping:
* - participant.status === "CANCELLED" → Booking CANCELLED, no Payment
* - participant.status === "PENDING" → Booking PENDING, no Payment
* - participant.status === "CONFIRMED" + free → Booking PAID, no Payment
* - participant.status === "CONFIRMED" + paid:
* - paymentConfirmedAt set → Booking PAID, Payment PAID (paidAt = paymentConfirmedAt)
* - markedPaidAt set, no confirm → Booking AWAITING_PAY, Payment AWAITING
* - neither → Booking AWAITING_PAY, no Payment
*
* Jalankan: `npx tsx prisma/backfill-bookings.ts`
*/
import { PrismaClient, Prisma } from "../app/generated/prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";
const adapter = new PrismaPg({
connectionString: process.env.DATABASE_URL!,
});
const prisma = new PrismaClient({ adapter });
async function main() {
const participants = await prisma.tripParticipant.findMany({
include: {
trip: { select: { price: true } },
booking: { select: { id: true } },
},
orderBy: { createdAt: "asc" },
});
let createdBookings = 0;
let createdPayments = 0;
let skipped = 0;
for (const p of participants) {
if (p.booking) {
skipped++;
continue;
}
const isFree = p.trip.price <= 0;
let bookingStatus: Prisma.BookingCreateInput["status"];
if (p.status === "CANCELLED") {
bookingStatus = "CANCELLED";
} else if (p.status === "PENDING") {
bookingStatus = "PENDING";
} else if (isFree) {
bookingStatus = "PAID";
} else if (p.paymentConfirmedAt) {
bookingStatus = "PAID";
} else {
bookingStatus = "AWAITING_PAY";
}
const booking = await prisma.booking.create({
data: {
tripId: p.tripId,
userId: p.userId,
participantId: p.id,
amount: p.trip.price,
status: bookingStatus,
},
});
createdBookings++;
// Payment row hanya kalau ada jejak pembayaran manual
if (!isFree && (p.markedPaidAt || p.paymentConfirmedAt)) {
const paymentStatus: Prisma.PaymentCreateInput["status"] =
p.paymentConfirmedAt ? "PAID" : "AWAITING";
await prisma.payment.create({
data: {
bookingId: booking.id,
provider: "MANUAL",
externalOrderId: `manual-${booking.id}`,
amount: p.trip.price,
status: paymentStatus,
method: "manual_transfer",
paidAt: p.paymentConfirmedAt ?? null,
},
});
createdPayments++;
}
}
console.log(
`✅ Backfill selesai. Booking dibuat: ${createdBookings}, Payment dibuat: ${createdPayments}, dilewati (sudah ada): ${skipped}`
);
}
main()
.catch((e) => {
console.error("❌ Backfill gagal:", e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
@@ -0,0 +1,75 @@
-- CreateEnum
CREATE TYPE "BookingStatus" AS ENUM ('PENDING', 'AWAITING_PAY', 'PAID', 'CANCELLED', 'REFUNDED', 'EXPIRED');
-- CreateEnum
CREATE TYPE "PaymentProvider" AS ENUM ('MANUAL', 'MIDTRANS');
-- CreateEnum
CREATE TYPE "PaymentStatus" AS ENUM ('PENDING', 'AWAITING', 'PAID', 'FAILED', 'EXPIRED', 'CANCELLED', 'REFUNDED');
-- CreateTable
CREATE TABLE "Booking" (
"id" TEXT NOT NULL,
"tripId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"participantId" TEXT NOT NULL,
"amount" INTEGER NOT NULL,
"currency" TEXT NOT NULL DEFAULT 'IDR',
"status" "BookingStatus" NOT NULL DEFAULT 'PENDING',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Booking_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Payment" (
"id" TEXT NOT NULL,
"bookingId" TEXT NOT NULL,
"provider" "PaymentProvider" NOT NULL,
"externalOrderId" TEXT NOT NULL,
"externalTxId" TEXT,
"method" TEXT,
"amount" INTEGER NOT NULL,
"status" "PaymentStatus" NOT NULL DEFAULT 'PENDING',
"rawCallback" JSONB,
"snapToken" TEXT,
"expiresAt" TIMESTAMP(3),
"paidAt" TIMESTAMP(3),
"failedAt" TIMESTAMP(3),
"rejectionReason" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Payment_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "Booking_participantId_key" ON "Booking"("participantId");
-- CreateIndex
CREATE INDEX "Booking_tripId_status_idx" ON "Booking"("tripId", "status");
-- CreateIndex
CREATE INDEX "Booking_userId_idx" ON "Booking"("userId");
-- CreateIndex
CREATE UNIQUE INDEX "Payment_externalOrderId_key" ON "Payment"("externalOrderId");
-- CreateIndex
CREATE INDEX "Payment_bookingId_status_idx" ON "Payment"("bookingId", "status");
-- CreateIndex
CREATE INDEX "Payment_provider_status_idx" ON "Payment"("provider", "status");
-- AddForeignKey
ALTER TABLE "Booking" ADD CONSTRAINT "Booking_tripId_fkey" FOREIGN KEY ("tripId") REFERENCES "Trip"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Booking" ADD CONSTRAINT "Booking_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Booking" ADD CONSTRAINT "Booking_participantId_fkey" FOREIGN KEY ("participantId") REFERENCES "TripParticipant"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Payment" ADD CONSTRAINT "Payment_bookingId_fkey" FOREIGN KEY ("bookingId") REFERENCES "Booking"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+136 -42
View File
@@ -8,38 +8,39 @@ datasource db {
}
model User {
id String @id @default(cuid())
name String
email String @unique
id String @id @default(cuid())
name String
email String @unique
/// Hash bcrypt. Null untuk user yang sign-in via OAuth (mis. Google).
password String?
image String?
password String?
image String?
/// Diisi PrismaAdapter NextAuth saat email diverifikasi provider OAuth (Google selalu sudah verified).
emailVerified DateTime?
emailVerified DateTime?
/// Apakah user telah menyetujui Syarat & Ketentuan dan Kebijakan Privasi
acceptedTermsAndPrivacy Boolean @default(false)
acceptedTermsAndPrivacy Boolean @default(false)
/// Waktu user menyetujui Syarat & Ketentuan dan Kebijakan Privasi
acceptedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
acceptedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
accounts Account[]
trips Trip[]
participations TripParticipant[]
tripReviews TripReview[]
bookings Booking[]
organizerVerification OrganizerVerification? @relation("OrganizerVerificationOwner")
reviewedVerifications OrganizerVerification[] @relation("OrganizerVerificationReviewer")
organizerVerification OrganizerVerification? @relation("OrganizerVerificationOwner")
reviewedVerifications OrganizerVerification[] @relation("OrganizerVerificationReviewer")
profile UserProfile?
profile UserProfile?
}
/// Profil sosial publik. Berisi info yang user pilih untuk dibagikan ke peserta lain
/// (bio, kota, minat, vibe). Tidak menyimpan data sensitif — KYC tetap di OrganizerVerification.
model UserProfile {
id String @id @default(cuid())
userId String @unique
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
id String @id @default(cuid())
userId String @unique
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
/// Bio singkat, teks bebas
bio String?
@@ -89,13 +90,13 @@ model OrganizerVerification {
user User @relation("OrganizerVerificationOwner", fields: [userId], references: [id], onDelete: Cascade)
/// Nama lengkap sesuai KTP
fullName String
fullName String
/// NIK terenkripsi (AES-256-GCM, base64). Plaintext tidak disimpan.
nikEncrypted String
/// HMAC-SHA256(NIK + pepper) untuk uniqueness lookup tanpa membuka plaintext.
nikHash String @unique
birthDate DateTime
address String
nikHash String @unique
birthDate DateTime
address String
/// Storage key foto KTP (mis. `ktp/<id>.jpg`). File disimpan terenkripsi di luar /public.
ktpImageKey String
@@ -111,7 +112,7 @@ model OrganizerVerification {
rejectionReason String?
reviewedAt DateTime?
reviewedById String?
reviewedBy User? @relation("OrganizerVerificationReviewer", fields: [reviewedById], references: [id])
reviewedBy User? @relation("OrganizerVerificationReviewer", fields: [reviewedById], references: [id])
verifiedAt DateTime?
createdAt DateTime @default(now())
@@ -125,7 +126,7 @@ enum VerificationStatus {
}
model Trip {
id String @id @default(cuid())
id String @id @default(cuid())
title String
description String?
/// Kategori aktivitas.
@@ -147,16 +148,17 @@ model Trip {
price Int
/// Ritme/energi trip — dipakai untuk matching dengan vibe user.
vibe Vibe?
status TripStatus @default(OPEN)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
status TripStatus @default(OPEN)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
organizerId String
organizer User @relation(fields: [organizerId], references: [id])
organizer User @relation(fields: [organizerId], references: [id])
participants TripParticipant[]
images TripImage[]
reviews TripReview[]
bookings Booking[]
@@index([category, status, date])
@@index([vibe, status, date])
@@ -170,38 +172,42 @@ model TripReview {
updatedAt DateTime @updatedAt
tripId String
trip Trip @relation(fields: [tripId], references: [id], onDelete: Cascade)
trip Trip @relation(fields: [tripId], references: [id], onDelete: Cascade)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([tripId, userId])
}
model TripImage {
id String @id @default(cuid())
url String
caption String?
order Int @default(0)
id String @id @default(cuid())
url String
caption String?
order Int @default(0)
tripId String
trip Trip @relation(fields: [tripId], references: [id], onDelete: Cascade)
trip Trip @relation(fields: [tripId], references: [id], onDelete: Cascade)
}
model TripParticipant {
id String @id @default(cuid())
status ParticipantStatus @default(PENDING)
createdAt DateTime @default(now())
/// Peserta menekan "Saya sudah bayar" (pembayaran manual)
markedPaidAt DateTime?
/// Organizer mengonfirmasi uang sudah masuk
paymentConfirmedAt DateTime?
id String @id @default(cuid())
status ParticipantStatus @default(PENDING)
createdAt DateTime @default(now())
/// @deprecated — sumber kebenaran pindah ke Booking/Payment. Tetap di-update
/// untuk backward-compat selama transisi UI lama. Akan dihapus PR berikutnya.
markedPaidAt DateTime?
/// @deprecated — sumber kebenaran pindah ke Booking/Payment. Tetap di-update
/// untuk backward-compat selama transisi UI lama. Akan dihapus PR berikutnya.
paymentConfirmedAt DateTime?
tripId String
trip Trip @relation(fields: [tripId], references: [id])
trip Trip @relation(fields: [tripId], references: [id])
userId String
user User @relation(fields: [userId], references: [id])
user User @relation(fields: [userId], references: [id])
booking Booking?
@@unique([tripId, userId])
}
@@ -231,3 +237,91 @@ enum ParticipantStatus {
CONFIRMED
CANCELLED
}
/// Booking 1-1 ke TripParticipant. Lifecycle ikut peserta:
/// - join → Booking PENDING (menunggu approve organizer)
/// - organizer confirm → AWAITING_PAY (paid trip) atau PAID (free trip)
/// - peserta + organizer rampungkan pembayaran → PAID
/// - cancel/reject → CANCELLED
/// `amount` adalah snapshot harga saat booking dibuat — protect dari perubahan trip.price.
model Booking {
id String @id @default(cuid())
tripId String
trip Trip @relation(fields: [tripId], references: [id])
userId String
user User @relation(fields: [userId], references: [id])
participantId String @unique
participant TripParticipant @relation(fields: [participantId], references: [id], onDelete: Cascade)
amount Int
currency String @default("IDR")
status BookingStatus @default(PENDING)
payments Payment[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([tripId, status])
@@index([userId])
}
enum BookingStatus {
PENDING
AWAITING_PAY
PAID
CANCELLED
REFUNDED
EXPIRED
}
/// Satu attempt pembayaran. Satu Booking bisa punya banyak Payment kalau retry
/// (di Phase MIDTRANS nanti). Untuk MANUAL biasanya cukup 1 Payment.
model Payment {
id String @id @default(cuid())
bookingId String
booking Booking @relation(fields: [bookingId], references: [id], onDelete: Cascade)
provider PaymentProvider
/// order_id eksternal (unik per attempt). Format MANUAL: `manual-<bookingId>`.
/// Format MIDTRANS nanti: `midtrans-<bookingId>-<retry>`.
externalOrderId String @unique
/// transaction_id dari gateway. Kosong untuk MANUAL atau sebelum first callback.
externalTxId String?
/// Metode konkret: bca_va, gopay, qris, manual_transfer, dst.
method String?
amount Int
status PaymentStatus @default(PENDING)
/// Snapshot mentah callback gateway (untuk audit & dispute).
rawCallback Json?
/// Snap token Midtrans / redirect URL.
snapToken String?
/// Kapan attempt ini kadaluarsa (Midtrans default 24 jam).
expiresAt DateTime?
paidAt DateTime?
failedAt DateTime?
rejectionReason String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([bookingId, status])
@@index([provider, status])
}
enum PaymentProvider {
MANUAL
MIDTRANS
}
enum PaymentStatus {
PENDING
AWAITING
PAID
FAILED
EXPIRED
CANCELLED
REFUNDED
}
+74
View File
@@ -0,0 +1,74 @@
import { prisma } from "@/lib/prisma";
import { Prisma } from "@/app/generated/prisma/client";
export const bookingRepo = {
async findById(id: string) {
return prisma.booking.findUnique({ where: { id } });
},
async findByParticipantId(participantId: string) {
return prisma.booking.findUnique({
where: { participantId },
include: { payments: { orderBy: { createdAt: "desc" } } },
});
},
async findByTripAndUser(tripId: string, userId: string) {
return prisma.booking.findFirst({
where: { tripId, userId },
include: { payments: { orderBy: { createdAt: "desc" } } },
});
},
/**
* Daftar booking di trip ini yang masih menunggu konfirmasi pembayaran
* dari organizer (Payment MANUAL status AWAITING).
*/
async findAwaitingManualConfirmation(tripId: string) {
return prisma.booking.findMany({
where: {
tripId,
status: "AWAITING_PAY",
payments: {
some: { provider: "MANUAL", status: "AWAITING" },
},
},
include: {
participant: true,
user: { select: { id: true, name: true, image: true } },
payments: {
where: { provider: "MANUAL", status: "AWAITING" },
orderBy: { createdAt: "desc" },
take: 1,
},
},
orderBy: { updatedAt: "asc" },
});
},
async create(
data: Pick<
Prisma.BookingUncheckedCreateInput,
"tripId" | "userId" | "participantId" | "amount" | "status"
>
) {
return prisma.booking.create({ data });
},
async updateStatus(id: string, status: Prisma.BookingUpdateInput["status"]) {
return prisma.booking.update({
where: { id },
data: { status },
});
},
async updateStatusByParticipantId(
participantId: string,
status: Prisma.BookingUpdateInput["status"]
) {
return prisma.booking.updateMany({
where: { participantId },
data: { status },
});
},
};
+42
View File
@@ -0,0 +1,42 @@
import { prisma } from "@/lib/prisma";
import { Prisma } from "@/app/generated/prisma/client";
export const paymentRepo = {
async findById(id: string) {
return prisma.payment.findUnique({ where: { id } });
},
async findByExternalOrderId(externalOrderId: string) {
return prisma.payment.findUnique({ where: { externalOrderId } });
},
async findActiveManualForBooking(bookingId: string) {
return prisma.payment.findFirst({
where: {
bookingId,
provider: "MANUAL",
status: { in: ["PENDING", "AWAITING"] },
},
orderBy: { createdAt: "desc" },
});
},
async create(
data: Pick<
Prisma.PaymentUncheckedCreateInput,
| "bookingId"
| "provider"
| "externalOrderId"
| "amount"
| "status"
| "method"
| "expiresAt"
>
) {
return prisma.payment.create({ data });
},
async update(id: string, data: Prisma.PaymentUpdateInput) {
return prisma.payment.update({ where: { id }, data });
},
};
+216
View File
@@ -0,0 +1,216 @@
import { Prisma } from "@/app/generated/prisma/client";
import { prisma } from "@/lib/prisma";
import { bookingRepo } from "@/server/repositories/booking.repo";
import { paymentRepo } from "@/server/repositories/payment.repo";
const SERIAL_TX_ATTEMPTS = 6;
function isSerializationConflict(err: unknown): boolean {
return (
typeof err === "object" &&
err !== null &&
"code" in err &&
(err as { code: string }).code === "P2034"
);
}
function manualOrderId(bookingId: string): string {
return `manual-${bookingId}`;
}
export const bookingService = {
async getByParticipantId(participantId: string) {
return bookingRepo.findByParticipantId(participantId);
},
async getByTripAndUser(tripId: string, userId: string) {
return bookingRepo.findByTripAndUser(tripId, userId);
},
/**
* Peserta tandai sudah transfer manual. Idempotent: kalau sudah ada Payment
* MANUAL aktif, biarkan; kalau Booking sudah PAID, tolak.
*/
async markPaidManual(bookingId: string, userId: string) {
let lastErr: unknown;
for (let attempt = 0; attempt < SERIAL_TX_ATTEMPTS; attempt++) {
try {
return await prisma.$transaction(
async (tx) => {
const booking = await tx.booking.findUnique({
where: { id: bookingId },
include: { trip: { select: { price: true } } },
});
if (!booking) {
throw new Error("Booking tidak ditemukan");
}
if (booking.userId !== userId) {
throw new Error("Booking ini bukan milikmu");
}
if (booking.amount <= 0) {
throw new Error(
"Booking ini tidak butuh pembayaran (gratis)"
);
}
if (booking.status === "PAID") {
throw new Error("Pembayaran sudah dikonfirmasi");
}
if (booking.status !== "AWAITING_PAY") {
throw new Error(
"Booking belum siap menerima pembayaran (tunggu approve organizer)"
);
}
const existing = await tx.payment.findFirst({
where: {
bookingId,
provider: "MANUAL",
status: { in: ["PENDING", "AWAITING"] },
},
orderBy: { createdAt: "desc" },
});
if (existing && existing.status === "AWAITING") {
return existing;
}
const payment = existing
? await tx.payment.update({
where: { id: existing.id },
data: { status: "AWAITING" },
})
: await tx.payment.create({
data: {
bookingId,
provider: "MANUAL",
externalOrderId: manualOrderId(bookingId),
amount: booking.amount,
status: "AWAITING",
},
});
// Backward-compat: tetap update timestamp di TripParticipant
// selama UI lama masih membaca kolom ini.
await tx.tripParticipant.update({
where: { id: booking.participantId },
data: { markedPaidAt: new Date() },
});
return payment;
},
{
isolationLevel: Prisma.TransactionIsolationLevel.Serializable,
maxWait: 5000,
timeout: 15000,
}
);
} catch (e) {
lastErr = e;
if (isSerializationConflict(e) && attempt < SERIAL_TX_ATTEMPTS - 1) {
continue;
}
throw e;
}
}
throw lastErr instanceof Error
? lastErr
: new Error("Gagal menandai pembayaran. Coba lagi sebentar.");
},
/**
* Organizer konfirmasi pembayaran manual masuk.
* Idempotent: kalau sudah PAID, tolak (UI lama bisa muncul tombol dua kali).
*/
async confirmPaidManual(bookingId: string, organizerId: string) {
let lastErr: unknown;
for (let attempt = 0; attempt < SERIAL_TX_ATTEMPTS; attempt++) {
try {
return await prisma.$transaction(
async (tx) => {
const booking = await tx.booking.findUnique({
where: { id: bookingId },
include: { trip: { select: { organizerId: true, price: true } } },
});
if (!booking) {
throw new Error("Booking tidak ditemukan");
}
if (booking.trip.organizerId !== organizerId) {
throw new Error(
"Hanya organizer trip ini yang bisa mengonfirmasi pembayaran"
);
}
if (booking.amount <= 0) {
throw new Error(
"Booking ini gratis — tidak ada pembayaran yang perlu dikonfirmasi"
);
}
if (booking.status === "PAID") {
throw new Error("Pembayaran sudah dikonfirmasi sebelumnya");
}
const awaitingPayment = await tx.payment.findFirst({
where: {
bookingId,
provider: "MANUAL",
status: "AWAITING",
},
orderBy: { createdAt: "desc" },
});
if (!awaitingPayment) {
throw new Error(
"Peserta belum menandai sudah membayar"
);
}
const now = new Date();
await tx.payment.update({
where: { id: awaitingPayment.id },
data: {
status: "PAID",
paidAt: now,
method: "manual_transfer",
},
});
await tx.booking.update({
where: { id: bookingId },
data: { status: "PAID" },
});
// Backward-compat: tetap update timestamp di TripParticipant.
await tx.tripParticipant.update({
where: { id: booking.participantId },
data: { paymentConfirmedAt: now },
});
return { ok: true as const };
},
{
isolationLevel: Prisma.TransactionIsolationLevel.Serializable,
maxWait: 5000,
timeout: 15000,
}
);
} catch (e) {
lastErr = e;
if (isSerializationConflict(e) && attempt < SERIAL_TX_ATTEMPTS - 1) {
continue;
}
throw e;
}
}
throw lastErr instanceof Error
? lastErr
: new Error("Gagal mengonfirmasi pembayaran. Coba lagi sebentar.");
},
/**
* Daftar booking yang masih menunggu konfirmasi organizer di trip tertentu.
* Dipakai OrganizerPaymentQueue.
*/
async getAwaitingManualForTrip(tripId: string) {
return bookingRepo.findAwaitingManualConfirmation(tripId);
},
};
+77 -58
View File
@@ -3,8 +3,11 @@ import type { ActivityCategory, Vibe } from "@/app/generated/prisma/enums";
import { prisma } from "@/lib/prisma";
import { tripRepo, type TripFilters } from "@/server/repositories/trip.repo";
import { participantRepo } from "@/server/repositories/participant.repo";
import { bookingRepo } from "@/server/repositories/booking.repo";
import { bookingService } from "@/server/services/booking.service";
import { LIMITS } from "@/lib/limits";
import { utcStartOfDay, isTripDepartureDayPast } from "@/lib/trip-dates";
import { isFreeTrip } from "@/lib/trip-pricing";
const SERIAL_TX_ATTEMPTS = 6;
@@ -139,6 +142,7 @@ export const tripService = {
date: true,
organizerId: true,
maxParticipants: true,
price: true,
},
});
@@ -185,6 +189,22 @@ export const tripService = {
data: { tripId, userId, status: "PENDING" },
});
// Booking 1-1 ke participant. Upsert untuk handle re-join setelah CANCELLED.
await tx.booking.upsert({
where: { participantId: participant.id },
create: {
tripId,
userId,
participantId: participant.id,
amount: trip.price,
status: "PENDING",
},
update: {
status: "PENDING",
amount: trip.price,
},
});
const newCount = await tx.tripParticipant.count({
where: { tripId, status: { not: "CANCELLED" } },
});
@@ -233,7 +253,21 @@ export const tripService = {
throw new Error("Kamu tidak terdaftar di trip ini");
}
const result = await participantRepo.cancel(tripId, userId);
const result = await prisma.$transaction(async (tx) => {
const cancelled = await tx.tripParticipant.update({
where: { tripId_userId: { tripId, userId } },
data: {
status: "CANCELLED",
markedPaidAt: null,
paymentConfirmedAt: null,
},
});
await tx.booking.updateMany({
where: { participantId: existing.id },
data: { status: "CANCELLED" },
});
return cancelled;
});
if (trip.status === "FULL") {
const count = await participantRepo.countByTrip(tripId);
@@ -266,7 +300,19 @@ export const tripService = {
throw new Error("Peserta ini tidak dalam status menunggu persetujuan");
}
return participantRepo.setStatus(participantId, "CONFIRMED");
// Trip gratis: Booking langsung PAID. Trip berbayar: AWAITING_PAY (tinggal bayar).
const nextBookingStatus = isFreeTrip(trip) ? "PAID" : "AWAITING_PAY";
return prisma.$transaction(async (tx) => {
await tx.booking.updateMany({
where: { participantId },
data: { status: nextBookingStatus },
});
return tx.tripParticipant.update({
where: { id: participantId },
data: { status: "CONFIRMED" },
});
});
},
async rejectParticipant(
@@ -290,10 +336,20 @@ export const tripService = {
throw new Error("Hanya permintaan yang masih menunggu yang bisa ditolak");
}
await participantRepo.setStatusAndClearPayment(
participantId,
"CANCELLED"
);
await prisma.$transaction(async (tx) => {
await tx.tripParticipant.update({
where: { id: participantId },
data: {
status: "CANCELLED",
markedPaidAt: null,
paymentConfirmedAt: null,
},
});
await tx.booking.updateMany({
where: { participantId },
data: { status: "CANCELLED" },
});
});
if (trip.status === "FULL") {
const count = await participantRepo.countByTrip(tripId);
@@ -311,44 +367,22 @@ export const tripService = {
throw new Error("Trip tidak ditemukan");
}
if (isFreeTrip(trip)) {
throw new Error(
"Trip ini gratis — tidak ada pembayaran yang perlu ditandai"
);
}
if (isTripDepartureDayPast(trip.date)) {
throw new Error("Trip sudah lewat — pembayaran tidak perlu ditandai");
}
const p = await participantRepo.findByTripAndUser(tripId, userId);
if (!p || p.status === "CANCELLED") {
const booking = await bookingRepo.findByTripAndUser(tripId, userId);
if (!booking || booking.status === "CANCELLED") {
throw new Error("Kamu tidak terdaftar di trip ini");
}
if (p.paymentConfirmedAt) {
throw new Error("Pembayaran kamu sudah dikonfirmasi organizer");
}
if (p.markedPaidAt) {
throw new Error("Kamu sudah menandai sudah bayar — tunggu konfirmasi organizer");
}
const updated = await participantRepo.tryMarkPaidByUser(tripId, userId);
if (updated.count === 0) {
const again = await participantRepo.findByTripAndUser(tripId, userId);
if (!again || again.status === "CANCELLED") {
throw new Error("Kamu tidak terdaftar di trip ini");
}
if (again.paymentConfirmedAt) {
throw new Error("Pembayaran kamu sudah dikonfirmasi organizer");
}
if (again.markedPaidAt) {
throw new Error(
"Kamu sudah menandai sudah bayar — tunggu konfirmasi organizer"
);
}
throw new Error("Tidak bisa menandai pembayaran. Coba lagi sebentar.");
}
const row = await participantRepo.findByTripAndUser(tripId, userId);
if (!row) {
throw new Error("Data peserta tidak ditemukan setelah update");
}
return row;
return bookingService.markPaidManual(booking.id, userId);
},
async confirmParticipantPayment(
@@ -363,30 +397,15 @@ export const tripService = {
if (trip.organizerId !== organizerId) {
throw new Error("Hanya organizer yang bisa mengonfirmasi pembayaran");
}
const participant = await participantRepo.findById(participantId);
if (!participant || participant.tripId !== tripId) {
throw new Error("Peserta tidak ditemukan");
}
if (participant.status === "CANCELLED") {
throw new Error("Peserta sudah tidak aktif");
}
if (!participant.markedPaidAt) {
throw new Error("Peserta belum menandai sudah membayar");
}
if (participant.paymentConfirmedAt) {
throw new Error("Pembayaran peserta ini sudah dikonfirmasi");
if (isFreeTrip(trip)) {
throw new Error("Trip ini gratis — tidak ada pembayaran yang perlu dikonfirmasi");
}
const updated = await participantRepo.tryConfirmPaymentByOrganizer(
participantId
);
if (updated.count === 0) {
throw new Error(
"Konfirmasi tidak diproses — mungkin sudah dikonfirmasi atau pembayaran belum ditandai peserta."
);
const booking = await bookingRepo.findByParticipantId(participantId);
if (!booking || booking.tripId !== tripId) {
throw new Error("Booking tidak ditemukan");
}
return participantRepo.findById(participantId);
return bookingService.confirmPaidManual(booking.id, organizerId);
},
};