Skip to content

Thai Visa Submissions

The submission system handles the workflow for Nepali clients applying for Thai visas through Shanvi Travels. A client submits passport documents and a payment receipt; the office processes the application and sends back a bank transfer confirmation.

1. Client fills ThaiSubmission form (public, no login required)
└── Provides: name, passport number, phone, email
└── Uploads: passport photo(s), receipt PDF(s)
└── Can add multiple participants (family members, group)
2. Submission created → status: null/Pending
└── System generates a displayId: "08212001", "08212002", etc.
└── Confirmation email sent to client
3. Client submits payment proof (public, no login)
└── POST /api/submissions/:id/submit-payment
└── Uploads payment proof image or transaction ID
└── System generates a companyId: "SHV-2025-A3F2"
└── Status → "Processing"
4. Admin/Agent reviews and verifies
└── PUT /api/submissions/:id/verify
└── Sets status (Verified/Rejected) + paymentStatus (Success/Failed)
└── Optionally uploads bank proof image
5. Client tracks status
└── Public search by displayId, companyId, or passport number
└── GET /api/submissions/public/search/:id
MethodPathAuthDescription
POST/api/submissionsNoneCreate submission with files
GET/api/submissionsTokenList submissions (scoped by role)
GET/api/submissions/:idTokenGet single submission
PUT/api/submissions/:idTokenUpdate submission details
DELETE/api/submissions/:idADMINDelete submission
POST/api/submissions/:id/submit-paymentNoneSubmit payment proof
PUT/api/submissions/:id/verifyADMIN/AGENTVerify/reject submission
POST/api/submissions/:id/bank-proofTokenUpload bank proof
POST/api/submissions/:id/bank-statusTokenUpdate bank status + message
GET/api/submissions/public/search/:idNonePublic status lookup
  • ADMIN + HEAD_OFFICE agents see all submissions
  • Other agents see only submissions assigned to them (agentId = req.user.id)
  • Role is re-fetched from the DB on every list/update call (not trusted from JWT)
  • Submission reassignment (changing agentId) is only allowed for ADMIN/HEAD_OFFICE
submissions
id INT (auto)
name TEXT
passportNumber TEXT
phoneNumber TEXT
email TEXT
passportPhoto TEXT (file key)
receiptPdf TEXT (file key)
paymentProof TEXT (file key)
bankProof TEXT (file key)
bankMessage TEXT
transactionId TEXT
companyId TEXT ← "SHV-2025-A3F2"
status TEXT ← null | "Processing" | "Verified" | "Rejected"
paymentStatus TEXT ← null | "Success" | "Failed"
participantCount INT
totalAmount DECIMAL
agentId INT (FK → users)
createdAt DATETIME
submission_participants
id INT
submissionId INT (FK → submissions)
name TEXT
passportNumber TEXT
passportPhoto TEXT (file key)
receiptPdf TEXT (file key)

The total amount is calculated at submission time using settings from the settings table:

totalAmount = (visa_fee + service_fee + bank_fee) × participantCount

Default values if settings are missing: visa_fee = 3500, service_fee = 0, bank_fee = 0.

ID TypeFormatWhen assignedPurpose
displayId08212001At creation (derived from id)Public-facing reference
companyIdSHV-2025-A3F2At payment submissionTracks payment verification

The displayId is not stored in the database — it’s computed as "08212" + id.padStart(3, '0') every time. This means it’s fragile: if records are deleted and IDs are reused, display IDs could collide. Recommendation: Store displayId as a proper unique column.

  1. /submit-payment is unauthenticated — any caller who knows a sequential submission ID can submit payment on someone else’s submission. The status guard (AND status = 'Pending') limits the damage, but rate limiting should be added and optionally the endpoint should require a token.

  2. Participant file order is assumed — if the client sends files out of order, participant photos/receipts will be mismatched. The API should accept named fields per participant rather than relying on array index.