Kulcare Docs
DeveloperTeleconsultation

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

  1. Prerequisites
  2. Test File Locations
  3. Running Automated Tests
  4. Video Session Tests
  5. Notification Service Tests
  6. Background Job Tests
  7. Shared Notification Service Tests
  8. Manual API Testing
  9. Rails Console Verification
  10. Troubleshooting
  11. 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 delivery

Running 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.rb

Run 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-failures

Video 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::Encounter and Clinical::VideoSessionParticipant
bundle exec rspec domains/clinical/spec/models/video_session_spec.rb -fd

File: 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 -fd

Request Specs (API Integration)

File: domains/clinical/spec/requests/video_sessions_spec.rb

Tests the full API lifecycle:

EndpointWhat is tested
GET /video_sessionReturns current session status for an encounter
POST /video_sessionCreates a new session, assigns provider, schedules notifications
POST /video_session/joinAdds participant, generates provider token
POST /video_session/tokenRefreshes provider token for existing participant
POST /video_session/startTransitions session to active, fires Ably event
POST /video_session/completeEnds session, records duration, sends Slack alert
POST /video_session/declineRecords participant decline, fires Ably event
bundle exec rspec domains/clinical/spec/requests/video_sessions_spec.rb -fd

Notification 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_until values for each job
bundle exec rspec domains/clinical/spec/services/video_sessions/schedule_notifications_service_spec.rb -fd

SlackNotificationService

File: domains/clinical/spec/services/video_sessions/slack_notification_service_spec.rb

Tests Slack alert formatting and dispatch:

Event TypeExpected ColorWhen
session_createdGreenNew session
check_delayOrange10 minutes after creation, session still not active
session_completedGreenSession ended
session_timed_outRed30-minute timeout
session_cancelledRedSession cancelled
bundle exec rspec domains/clinical/spec/services/video_sessions/slack_notification_service_spec.rb -fd

Background Job Tests

TeleconsultationReminderJob

File: domains/clinical/spec/jobs/teleconsultation_reminder_job_spec.rb

Tests pre-call reminder delivery across channels.

Reminder types tested:

TypeDoctorPatientChannels
doctor_15minSMS + Push-Twilio/MSG91 + AWS SNS
patient_10min-SMS + WhatsAppTwilio/MSG91 + WhatsApp
at_timePushPush (voice_call: true)AWS SNS

Guard clause tests: Job silently exits if:

  • Encounter not found or not virtual class
  • 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 -fd

SlackNotificationJob

File: domains/clinical/spec/jobs/slack_notification_job_spec.rb

Tests async Slack dispatch:

  • Queues to slack_notifications queue
  • Dispatches correct notification type to SlackNotificationService
  • Handles missing video session gracefully
bundle exec rspec domains/clinical/spec/jobs/slack_notification_job_spec.rb -fd

Shared 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 -fd

PushService

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 -fd

SlackService

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 -fd

WhatsappService

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::TelemedicineReminder provider
bundle exec rspec domains/shared/spec/services/notifications/whatsapp_service_spec.rb -fd

NotificationLog 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 -fd

Manual API Testing

Start the Server

cd backend
rails server

Video 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" | jq

Expected 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.count

Verify 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}"
  end

Verify 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}"
end

Test 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?
end

Fix 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)"
  )
end

Notification 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 env

SMS Service Returns Failure

Common causes:

  • Missing DLT ID for India: Indian SMS requires dlt_id parameter
  • Test number filtering: Numbers starting with +1555 are 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 attempted

Slack 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 false

Timeout 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 --profile

Spec Count by Area

AreaFile(s)Description
VideoSession modelvideo_session_spec.rbStatus, scopes, methods
VideoSessionParticipant modelvideo_session_participant_spec.rbTypes, roles, state methods
Video Session APIvideo_sessions_spec.rbAll 7 endpoints
Schedule Notificationsschedule_notifications_service_spec.rbJob scheduling orchestrator
Slack Notificationsslack_notification_service_spec.rbMonitoring alerts
Teleconsultation Remindersteleconsultation_reminder_job_spec.rbSMS/Push/WhatsApp reminders
Slack Jobslack_notification_job_spec.rbAsync Slack dispatch
NotificationLognotification_log_spec.rbAudit trail model
SMS Servicesms_service_spec.rbSMS delivery
Push Servicepush_service_spec.rbPush notifications
Slack Serviceslack_service_spec.rbSlack webhooks
WhatsApp Servicewhatsapp_service_spec.rbWhatsApp templates

Video Session Status Lifecycle

created --> active --> completed
   |           |
   |           +--> failed
   |
   +--> timed_out (30-min no-show)

API Endpoints Quick Reference

MethodPathDescription
GET/clinical/encounters/:id/video_sessionGet session status
POST/clinical/encounters/:id/video_sessionCreate session
POST/clinical/encounters/:id/video_session/joinJoin session
POST/clinical/encounters/:id/video_session/tokenRefresh token
POST/clinical/encounters/:id/video_session/startStart session
POST/clinical/encounters/:id/video_session/completeEnd session
POST/clinical/encounters/:id/video_session/declineDecline call

AFTER Payment Flow Testing

Overview

For appointments with payment_option = 'AFTER', payment is collected post-consultation via a token-based SMS link.

Prerequisites

  1. Database migration must be run:

    bundle exec rake db:migrate

    This adds the payment_token column to clinical.appointments.

  2. Feature flag (if exists): Ensure AFTER payment flow is enabled.

Manual Test Steps

BEFORE Payment Flow (Already Working)

  1. Book telemedicine appointment with BEFORE payment via CMS
  2. Patient joins → sees "Pay Now" button
  3. Patient pays via Cashfree/Razorpay
  4. Webhook marks order as paid
  5. Patient enters waiting room → doctor admits → consultation

AFTER Payment Flow

  1. Book appointment with AFTER payment:

    • In CMS, when creating telemedicine appointment, set payment option to AFTER
  2. Patient joins waiting room:

    • Patient uses join link → enters waiting room (no payment gate for AFTER)
  3. Doctor admits and completes consultation:

    • Doctor opens encounter → admits patient → consultation
    • Doctor clicks "Complete Consultation"
  4. Verify SMS sent (check logs or SMS provider):

    • Patient receives SMS with payment link:
    • With short URL configured (patient_short_url or care_short_url credential set to an https:// 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_urls row 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_urls with expires_at equal to the token's exp. 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 keep expires_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_url uses the first https base (patient_short_url, then care_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.
  5. 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
  6. Verify payment recorded:

    • Check clinical.telemedicine_orders for appointment_id → status should be paid

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 payment

Key Files

ComponentFile
Payment servicebackend/domains/clinical/services/appointments/post_consultation_payment_service.rb
CompleteService triggerbackend/domains/clinical/services/video_sessions/complete_service.rb
Payment endpointbackend/domains/clinical/controllers/appointments_controller.rb
Payment pagepatient/src/features/payment/pages/PostConsultationPaymentPage.tsx
Migrationdb/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 CompleteService calls PostConsultationPaymentService.
  • Payment endpoint 401: Token mismatch. Ensure ?token= matches appointment.payment_token.

On this page