Teleconsultation Testing Guide
How to test video sessions, notifications, payment flows, and background jobs in the new schema teleconsultation system.
Payment Flow Testing: See AFTER Payment Flow Plan for the complete flow. Quick reference below.
Table of Contents
- Prerequisites
- Test File Locations
- Running Automated Tests
- Video Session Tests
- Notification Service Tests
- Background Job Tests
- Shared Notification Service Tests
- Manual API Testing
- Rails Console Verification
- Troubleshooting
- AFTER Payment Flow Testing
Prerequisites
Before testing, ensure:
- PostgreSQL 14+ running with clinical and shared schemas migrated
- Redis running (for Sidekiq job testing)
- Backend dependencies installed (
bundle install) - Test database migrated (
RAILS_ENV=test bundle exec rake db:migrate)
cd backend
# Verify schemas exist
bundle exec rails runner "
puts 'clinical.video_sessions: ' + Clinical::VideoSession.table_exists?.to_s
puts 'clinical.video_session_participants: ' + Clinical::VideoSessionParticipant.table_exists?.to_s
puts 'shared.notification_logs: ' + Shared::NotificationLog.table_exists?.to_s
"Test File Locations
domains/
clinical/spec/
models/
video_session_spec.rb # VideoSession model
video_session_participant_spec.rb # VideoSessionParticipant model
requests/
video_sessions_spec.rb # API integration tests
services/video_sessions/
schedule_notifications_service_spec.rb # Notification scheduling orchestrator
slack_notification_service_spec.rb # Slack monitoring alerts
jobs/
teleconsultation_reminder_job_spec.rb # SMS/Push/WhatsApp reminders
slack_notification_job_spec.rb # Async Slack dispatch
shared/spec/
models/
notification_log_spec.rb # NotificationLog model
services/notifications/
sms_service_spec.rb # SMS delivery (Twilio/MSG91)
push_service_spec.rb # Push notifications (AWS SNS)
slack_service_spec.rb # Slack webhook delivery
whatsapp_service_spec.rb # WhatsApp template deliveryRunning Automated Tests
Run All Teleconsultation Tests
# All video session and notification specs (169 specs total)
bundle exec rspec \
domains/clinical/spec/models/video_session_spec.rb \
domains/clinical/spec/models/video_session_participant_spec.rb \
domains/clinical/spec/requests/video_sessions_spec.rb \
domains/clinical/spec/services/video_sessions/ \
domains/clinical/spec/jobs/teleconsultation_reminder_job_spec.rb \
domains/clinical/spec/jobs/slack_notification_job_spec.rb \
domains/shared/spec/models/notification_log_spec.rb \
domains/shared/spec/services/notifications/Run by Layer
# Models only
bundle exec rspec \
domains/clinical/spec/models/video_session_spec.rb \
domains/clinical/spec/models/video_session_participant_spec.rb \
domains/shared/spec/models/notification_log_spec.rb
# API integration (request specs)
bundle exec rspec domains/clinical/spec/requests/video_sessions_spec.rb
# Services only
bundle exec rspec \
domains/clinical/spec/services/video_sessions/ \
domains/shared/spec/services/notifications/
# Background jobs only
bundle exec rspec \
domains/clinical/spec/jobs/teleconsultation_reminder_job_spec.rb \
domains/clinical/spec/jobs/slack_notification_job_spec.rbRun Individual Test Files
# Single file with verbose output
bundle exec rspec domains/clinical/spec/models/video_session_spec.rb -fd
# Specific test by line number
bundle exec rspec domains/clinical/spec/requests/video_sessions_spec.rb:42
# Run only failures from previous run
bundle exec rspec --only-failuresVideo Session Tests
Model Specs
File: domains/clinical/spec/models/video_session_spec.rb
Tests the Clinical::VideoSession model:
- Status validations (created, active, completed, failed, timed_out)
- Provider validations (vonage, twilio, agora)
- Scopes:
for_encounter,by_status,active_sessions,created_sessions,terminal - Instance methods:
duration_seconds,terminal?,joinable?,active_participants - Association to
Clinical::EncounterandClinical::VideoSessionParticipant
bundle exec rspec domains/clinical/spec/models/video_session_spec.rb -fdFile: domains/clinical/spec/models/video_session_participant_spec.rb
Tests the Clinical::VideoSessionParticipant model:
- Participant type validations (practitioner, patient, family)
- Role validations (host, participant, observer)
- Uniqueness constraint: one entry per (video_session, participant_type, participant_id)
- Instance methods:
active?,token_expired?,declined?
bundle exec rspec domains/clinical/spec/models/video_session_participant_spec.rb -fdRequest Specs (API Integration)
File: domains/clinical/spec/requests/video_sessions_spec.rb
Tests the full API lifecycle:
| Endpoint | What is tested |
|---|---|
GET /video_session | Returns current session status for an encounter |
POST /video_session | Creates a new session, assigns provider, schedules notifications |
POST /video_session/join | Adds participant, generates provider token |
POST /video_session/token | Refreshes provider token for existing participant |
POST /video_session/start | Transitions session to active, fires Ably event |
POST /video_session/complete | Ends session, records duration, sends Slack alert |
POST /video_session/decline | Records participant decline, fires Ably event |
bundle exec rspec domains/clinical/spec/requests/video_sessions_spec.rb -fdNotification Service Tests
ScheduleNotificationsService
File: domains/clinical/spec/services/video_sessions/schedule_notifications_service_spec.rb
Tests the orchestrator that schedules all notification jobs when a session is created.
Scenarios tested:
- Future appointments: Schedules doctor_15min, patient_10min, at_time reminders plus Slack alerts
- Instant appointments: Schedules only at_time reminder and Slack alerts (skips advance reminders)
- Time guards: Reminders scheduled for the past are skipped
- Job timing: Verifies correct
wait_untilvalues for each job
bundle exec rspec domains/clinical/spec/services/video_sessions/schedule_notifications_service_spec.rb -fdSlackNotificationService
File: domains/clinical/spec/services/video_sessions/slack_notification_service_spec.rb
Tests Slack alert formatting and dispatch:
| Event Type | Expected Color | When |
|---|---|---|
session_created | Green | New session |
check_delay | Orange | 10 minutes after creation, session still not active |
session_completed | Green | Session ended |
session_timed_out | Red | 30-minute timeout |
session_cancelled | Red | Session cancelled |
bundle exec rspec domains/clinical/spec/services/video_sessions/slack_notification_service_spec.rb -fdBackground Job Tests
TeleconsultationReminderJob
File: domains/clinical/spec/jobs/teleconsultation_reminder_job_spec.rb
Tests pre-call reminder delivery across channels.
Reminder types tested:
| Type | Doctor | Patient | Channels |
|---|---|---|---|
doctor_15min | SMS + Push | - | Twilio/MSG91 + AWS SNS |
patient_10min | - | SMS + WhatsApp | Twilio/MSG91 + WhatsApp |
at_time | Push | Push (voice_call: true) | AWS SNS |
Guard clause tests: Job silently exits if:
- Encounter not found or not
virtualclass - Encounter in terminal state (cancelled, entered_in_error, finished)
- Video session already in terminal state (completed, failed, timed_out)
bundle exec rspec domains/clinical/spec/jobs/teleconsultation_reminder_job_spec.rb -fdSlackNotificationJob
File: domains/clinical/spec/jobs/slack_notification_job_spec.rb
Tests async Slack dispatch:
- Queues to
slack_notificationsqueue - Dispatches correct notification type to
SlackNotificationService - Handles missing video session gracefully
bundle exec rspec domains/clinical/spec/jobs/slack_notification_job_spec.rb -fdShared Notification Service Tests
These test the reusable notification delivery services used by teleconsultation and other features.
SmsService
File: domains/shared/spec/services/notifications/sms_service_spec.rb
- Provider routing: India (+91) via MSG91, US/UAE via Twilio
- DLT template ID support for India
- Duplicate detection (10-second window)
- Test number filtering
- Dev/staging mode (routes to Slack instead of real SMS)
- NotificationLog creation on send
bundle exec rspec domains/shared/spec/services/notifications/sms_service_spec.rb -fdPushService
File: domains/shared/spec/services/notifications/push_service_spec.rb
- Device endpoint ARN management
- Platform-specific formatting (Android vs iOS sound files)
- Voice call mode with 10-second TTL on Android
- App type routing (patient, doctor, pharmacy, cms, clinic)
- NotificationLog creation
bundle exec rspec domains/shared/spec/services/notifications/push_service_spec.rb -fdSlackService
File: domains/shared/spec/services/notifications/slack_service_spec.rb
- Channel routing (:telemedicine, :support, :bots, :billing)
- Attachment formatting with color, title, and fields
- Webhook URL resolution
- NotificationLog creation
bundle exec rspec domains/shared/spec/services/notifications/slack_service_spec.rb -fdWhatsappService
File: domains/shared/spec/services/notifications/whatsapp_service_spec.rb
- Template-based messaging (e.g.,
telemed10) - Parameter substitution (patient_name, doctor_name, link)
- Delegation to legacy
WhatsApp::TelemedicineReminderprovider
bundle exec rspec domains/shared/spec/services/notifications/whatsapp_service_spec.rb -fdNotificationLog Model
File: domains/shared/spec/models/notification_log_spec.rb
- Channel validations (sms, push, whatsapp, slack, email)
- Status transitions (pending, sent, delivered, failed, bounced)
- Scopes:
by_channel,failed,recent - Deduplication index verification
bundle exec rspec domains/shared/spec/models/notification_log_spec.rb -fdManual API Testing
Start the Server
cd backend
rails serverVideo Session API Endpoints
All endpoints are scoped under /clinical/encounters/:encounter_id/video_session and require JWT authentication.
TOKEN="your-jwt-token-here"
ENCOUNTER_ID="your-encounter-uuid"
BASE_URL="http://localhost:3000/clinical/encounters/$ENCOUNTER_ID/video_session"
# 1. Create a video session
curl -s -X POST "$BASE_URL" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" | jq
# 2. Get session status
curl -s "$BASE_URL" \
-H "Authorization: Bearer $TOKEN" | jq
# 3. Join the session
curl -s -X POST "$BASE_URL/join" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" | jq
# 4. Generate/refresh provider token
curl -s -X POST "$BASE_URL/token" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" | jq
# 5. Start the session (transition to active)
curl -s -X POST "$BASE_URL/start" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" | jq
# 6. Complete the session
curl -s -X POST "$BASE_URL/complete" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" | jq
# 7. Decline a call invitation
curl -s -X POST "$BASE_URL/decline" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" | jqExpected Response Formats
Create/Show response:
{
"success": true,
"data": {
"id": "uuid",
"encounter_id": "uuid",
"provider": "vonage",
"status": "created",
"started_at": null,
"completed_at": null,
"participants": []
}
}Join response (includes provider token):
{
"success": true,
"data": {
"id": "uuid",
"participant_type": "practitioner",
"role": "host",
"token": "eyJ...",
"token_expires_at": "2026-03-05T15:30:00Z"
}
}Verify Notification Side Effects
After creating a session, check that background jobs were enqueued:
# If using Sidekiq web UI (typically at /sidekiq)
# Check the "Scheduled" tab for upcoming jobs:
# - TeleconsultationReminderJob (doctor_15min, patient_10min, at_time)
# - SlackNotificationJob (new, delay)
# - VideoSessionTimeoutJob
# Or check via Rails console (see next section)Rails Console Verification
Check Video Session State
bundle exec rails console# Find a video session
session = Clinical::VideoSession.last
puts "Status: #{session.status}"
puts "Provider: #{session.provider}"
puts "Duration: #{session.duration_seconds}s"
puts "Terminal? #{session.terminal?}"
puts "Joinable? #{session.joinable?}"
# Check participants
session.participants.each do |p|
puts " #{p.participant_type} (#{p.role}): active=#{p.active?}, declined=#{p.declined?}"
end
# Active participants only
session.active_participants.countVerify Notification Logs
# Recent notification logs
Shared::NotificationLog.order(created_at: :desc).limit(10).each do |log|
puts "#{log.channel} | #{log.status} | #{log.recipient} | #{log.created_at}"
end
# Filter by channel
Shared::NotificationLog.by_channel('sms').recent.each do |log|
puts "SMS to #{log.recipient}: #{log.status} (#{log.provider})"
end
# Check failed notifications
Shared::NotificationLog.failed.each do |log|
puts "FAILED #{log.channel} to #{log.recipient}: #{log.failure_reason}"
end
# Check teleconsultation-related logs
Shared::NotificationLog.where("metadata->>'encounter_id' = ?", encounter_id)
.order(created_at: :desc)
.each do |log|
puts "#{log.channel} | #{log.status} | #{log.template_id}"
endVerify Scheduled Jobs
# Check Sidekiq scheduled jobs
require 'sidekiq/api'
Sidekiq::ScheduledSet.new.each do |job|
next unless job.klass.include?('Teleconsultation') || job.klass.include?('SlackNotification') || job.klass.include?('VideoSessionTimeout')
puts "#{job.klass} | args: #{job.args} | scheduled: #{job.at}"
endTest Notification Services Directly
# Set up tenant context first
tenant = Identity::Tenant.find_by(slug: 'your-tenant')
Shared::Current.tenant = tenant
# Test SMS (dev/staging routes to Slack)
result = Shared::Notifications::SmsService.call(
to: '+919876543210',
message: 'Test teleconsultation reminder',
tenant_id: tenant.id
)
puts "SMS: #{result.success?} | Log: #{result.log&.id}"
# Test Push
user = Identity::User.find('user-uuid')
Shared::Notifications::PushService.call(
user: user,
title: 'Test Teleconsultation',
body: 'Your video call is ready',
key: 'consultation_video',
app_type: 'doctor'
)
# Test Slack
Shared::Notifications::SlackService.call(
channel: :telemedicine,
title: 'Test Alert',
color: '#7CD197',
fields: [
{ title: 'Doctor', value: 'Dr. Test', short: true },
{ title: 'Patient', value: 'Test Patient', short: true }
]
)Simulate Full Video Session Lifecycle
# 1. Set up context
tenant = Identity::Tenant.find_by(slug: 'your-tenant')
Shared::Current.tenant = tenant
encounter = Clinical::Encounter.find('encounter-uuid')
# 2. Create session
session = Clinical::VideoSession.create!(
tenant: tenant,
encounter: encounter,
provider: 'vonage',
status: 'created'
)
puts "Created: #{session.id}"
# 3. Add practitioner participant
practitioner_participant = Clinical::VideoSessionParticipant.create!(
tenant: tenant,
video_session: session,
participant_type: 'practitioner',
participant_id: 'practitioner-uuid',
role: 'host',
joined_at: Time.current
)
# 4. Add patient participant
patient_participant = Clinical::VideoSessionParticipant.create!(
tenant: tenant,
video_session: session,
participant_type: 'patient',
participant_id: 'patient-uuid',
role: 'participant',
joined_at: Time.current
)
# 5. Start session
session.update!(status: 'active', started_at: Time.current)
puts "Active: #{session.status}"
# 6. Complete session
session.update!(status: 'completed', completed_at: Time.current)
puts "Completed: duration=#{session.duration_seconds}s"
# 7. Verify
puts "Terminal? #{session.terminal?}"
puts "Joinable? #{session.joinable?}"
puts "Active participants: #{session.active_participants.count}"Troubleshooting
Tests Skip with "Clinical schema not available"
The specs include guards that skip if tables don't exist:
before do
skip 'Clinical schema not available' unless Clinical::VideoSession.table_exists?
endFix by running migrations:
RAILS_ENV=test bundle exec rake db:migrate"Table does not exist" for video_sessions
Verify the clinical schema tables exist:
bundle exec rails runner "
puts Clinical::VideoSession.table_exists?
puts Clinical::VideoSessionParticipant.table_exists?
puts Shared::NotificationLog.table_exists?
"If false, run the clinical and shared schema migrations.
RLS Errors in Video Session Tests
Video session tables have RLS enabled. Ensure tenant context is set:
# In specs, ensure tenant context is established
let(:tenant) { Identity::Tenant.find_or_create_by!(slug: 'test-clinic') { |t| ... } }
before do
ActiveRecord::Base.connection.execute(
"SELECT set_config('app.current_tenant_id', '#{tenant.id}', true)"
)
endNotification Jobs Not Enqueuing
If have_enqueued_job matchers fail:
# Ensure ActiveJob test adapter is configured
# In rails_helper.rb or spec config:
config.include ActiveJob::TestHelper
# Check queue adapter
Rails.application.config.active_job.queue_adapter # Should be :test in test envSMS Service Returns Failure
Common causes:
- Missing DLT ID for India: Indian SMS requires
dlt_idparameter - Test number filtering: Numbers starting with
+1555are filtered out - Duplicate detection: Same message to same number within 10 seconds is deduplicated
# Check the NotificationLog for details
log = Shared::NotificationLog.order(created_at: :desc).first
puts log.status # "failed"
puts log.failure_reason # Reason for failure
puts log.provider # Which provider was attemptedSlack Notifications Not Appearing
In dev/staging, SMS is routed to Slack. For Slack-specific issues:
# Verify Slack webhook URL is configured
puts Rails.application.credentials.dig(:slack, :telemedicine_webhook)
# Test directly
Shared::Notifications::SlackService.call(
channel: :telemedicine,
title: 'Debug Test',
color: '#FF0000',
fields: [{ title: 'Test', value: 'Value', short: true }]
)Guard Clauses Causing Jobs to Exit Silently
The TeleconsultationReminderJob exits early if conditions aren't met. To debug:
# Verify encounter is valid for reminders
encounter = Clinical::Encounter.find('uuid')
puts "Class: #{encounter.encounter_class}" # Must be 'virtual'
puts "Status: #{encounter.status}" # Must not be cancelled/entered_in_error/finished
# Verify video session is not terminal
session = encounter.video_sessions.last
puts "Session status: #{session.status}" # Must not be completed/failed/timed_out
puts "Terminal? #{session.terminal?}" # Must be falseTimeout Job Not Firing
The VideoSessionTimeoutJob is scheduled for 30 minutes after session creation:
# Check if job is in scheduled queue
require 'sidekiq/api'
scheduled = Sidekiq::ScheduledSet.new
timeout_jobs = scheduled.select { |j| j.klass == 'Clinical::VideoSessionTimeoutJob' }
timeout_jobs.each { |j| puts "#{j.args} at #{j.at}" }
# Manually trigger for testing
Clinical::VideoSessionTimeoutJob.perform_now(session.id)Quick Reference
Test Commands
# All teleconsultation specs
bundle exec rspec \
domains/clinical/spec/models/video_session_spec.rb \
domains/clinical/spec/models/video_session_participant_spec.rb \
domains/clinical/spec/requests/video_sessions_spec.rb \
domains/clinical/spec/services/video_sessions/ \
domains/clinical/spec/jobs/teleconsultation_reminder_job_spec.rb \
domains/clinical/spec/jobs/slack_notification_job_spec.rb \
domains/shared/spec/models/notification_log_spec.rb \
domains/shared/spec/services/notifications/
# Verbose output
# Append -fd to any command above
# Only failures
# Append --only-failures
# With timing profile
# Append --profileSpec Count by Area
| Area | File(s) | Description |
|---|---|---|
| VideoSession model | video_session_spec.rb | Status, scopes, methods |
| VideoSessionParticipant model | video_session_participant_spec.rb | Types, roles, state methods |
| Video Session API | video_sessions_spec.rb | All 7 endpoints |
| Schedule Notifications | schedule_notifications_service_spec.rb | Job scheduling orchestrator |
| Slack Notifications | slack_notification_service_spec.rb | Monitoring alerts |
| Teleconsultation Reminders | teleconsultation_reminder_job_spec.rb | SMS/Push/WhatsApp reminders |
| Slack Job | slack_notification_job_spec.rb | Async Slack dispatch |
| NotificationLog | notification_log_spec.rb | Audit trail model |
| SMS Service | sms_service_spec.rb | SMS delivery |
| Push Service | push_service_spec.rb | Push notifications |
| Slack Service | slack_service_spec.rb | Slack webhooks |
| WhatsApp Service | whatsapp_service_spec.rb | WhatsApp templates |
Video Session Status Lifecycle
created --> active --> completed
| |
| +--> failed
|
+--> timed_out (30-min no-show)API Endpoints Quick Reference
| Method | Path | Description |
|---|---|---|
| GET | /clinical/encounters/:id/video_session | Get session status |
| POST | /clinical/encounters/:id/video_session | Create session |
| POST | /clinical/encounters/:id/video_session/join | Join session |
| POST | /clinical/encounters/:id/video_session/token | Refresh token |
| POST | /clinical/encounters/:id/video_session/start | Start session |
| POST | /clinical/encounters/:id/video_session/complete | End session |
| POST | /clinical/encounters/:id/video_session/decline | Decline call |
AFTER Payment Flow Testing
Overview
For appointments with payment_option = 'AFTER', payment is collected post-consultation via a token-based SMS link.
Prerequisites
-
Database migration must be run:
bundle exec rake db:migrateThis adds the
payment_tokencolumn toclinical.appointments. -
Feature flag (if exists): Ensure AFTER payment flow is enabled.
Manual Test Steps
BEFORE Payment Flow (Already Working)
- Book telemedicine appointment with
BEFOREpayment via CMS - Patient joins → sees "Pay Now" button
- Patient pays via Cashfree/Razorpay
- Webhook marks order as
paid - Patient enters waiting room → doctor admits → consultation
AFTER Payment Flow
-
Book appointment with AFTER payment:
- In CMS, when creating telemedicine appointment, set payment option to
AFTER
- In CMS, when creating telemedicine appointment, set payment option to
-
Patient joins waiting room:
- Patient uses join link → enters waiting room (no payment gate for AFTER)
-
Doctor admits and completes consultation:
- Doctor opens encounter → admits patient → consultation
- Doctor clicks "Complete Consultation"
-
Verify SMS sent (check logs or SMS provider):
- Patient receives SMS with payment link:
- With short URL configured (
patient_short_urlorcare_short_urlcredential set to anhttps://URL):https://ps.kulcare.com/s/{short_key} - No fallback (#2760): with no short URL credential, or when shortening
fails, no SMS is sent and the job retries. When the token has already
expired, no SMS is sent and the job logs why. An SMS never carries the
long
.../appointments/{appointment_id}/payment?token=...URL. - Retries (#2723): the job stores the link in one transaction and
sends the SMS in a second one. When the send fails after MSG91 took
the SMS, the link in that SMS still works. The retry sends the same
link. One
shared.short_urlsrow exists for each appointment and token version (metadata->>'reuse_key'). - Expired link (#2723): after the link expires,
/s/{short_key}opens.../appointments/{appointment_id}/payment?expired=1, with no token. The payment page shows "Payment Link Expired". It does not go to/. The expand endpoint still answers 404 for an expired link. - Regenerated or cancelled (#2723): when the token version changes between building the link and sending it, the old link is not sent. The job runs again and sends a link for the new version, or stops when the appointment is cancelled.
- Short links expire with their token (#2722): every join-link short URL
(booking confirmation, spouse confirmation, t-10 reminder, doctor-ready and
payment) is stored in
shared.short_urlswithexpires_atequal to the token'sexp. Appointment join and payment links expire at the earlier of slot end + 7 days and 14 days after the send. The doctor-ready link expires 2 hours after the send. An expired short link does not resolve; this is expected, not a bug. Rows created before #2722 keepexpires_at = NULL. - https base only (#2721): outside development and test, a short URL
base that is not
https://counts as not configured.Shared::ShortenUrl.resolve_base_urluses the first https base (patient_short_url, thencare_short_url). With none, the SMS is not sent (the rule above), WhatsApp and email carry the long link, and the log shows one error line per process:[Shared::ShortenUrl] no https short URL base for patient; links stay long (no SMS link) until it is fixed.
-
Patient pays via token link:
- Patient opens the payment link (unauthenticated)
- Payment form displays amount and Pay button
- Patient pays via Cashfree/Razorpay
- Webhook marks order as
paid
-
Verify payment recorded:
- Check
clinical.telemedicine_ordersforappointment_id→ status should bepaid
- Check
Backend Verification via Rails Console
# Find the appointment
appt = Clinical::Appointment.find_by(id: 'your-appointment-id')
puts appt.payment_option # Should be 'AFTER'
puts appt.payment_token # Should be a UUID after consultation completes
# Check telemedicine order
order = Clinical::TelemedicineOrder.find_by(appointment_id: appt.id)
puts order.status # Should be 'paid' after paymentKey Files
| Component | File |
|---|---|
| Payment service | backend/domains/clinical/services/appointments/post_consultation_payment_service.rb |
| CompleteService trigger | backend/domains/clinical/services/video_sessions/complete_service.rb |
| Payment endpoint | backend/domains/clinical/controllers/appointments_controller.rb |
| Payment page | patient/src/features/payment/pages/PostConsultationPaymentPage.tsx |
| Migration | db/migrate/20260722000001_add_payment_token_to_appointments.rb |
Troubleshooting
- Token empty in SMS link: Migration not run. Run
bundle exec rake db:migrate. - No SMS sent after consultation: Check
CompleteServicecallsPostConsultationPaymentService. - Payment endpoint 401: Token mismatch. Ensure
?token=matchesappointment.payment_token.