Kulcare Docs
DeveloperAuthentication Documentation04 featuresAbha_phr

PHR Profile Module Implementation Plan

Overview

This document outlines the implementation plan for the PHR Profile module in the ABDM system. The module handles user profile management including viewing, updating, and managing ABHA number linkages. Some APIs are already implemented (Get Profile, QR Code, ABHA Card) and need to be extended with additional functionality.

Key Architecture Decision: Following existing ABDM system patterns, this implementation uses a stateless architecture with no database persistence for sessions or transactions. All state management is handled by the frontend via API responses.

Existing Implementation Status

Already Implemented APIs

  1. Get Profile - FetchPatientProfile service
  2. Get QR Code - FetchPatientQrCode service
  3. Get ABHA Card - FetchPatientCard service

All existing implementations follow these patterns:

  • Stateless design: No database storage for API responses or session data
  • X-Token authentication in headers
  • AbdmRequest logging for all API calls
  • HTTParty for API communication
  • Service classes in parts/abdm/services/abdm/abha/profile/
  • Interactors in parts/abdm/interactors/abdm/abha/patient_methods/
  • Controller in parts/abdm/controllers/abdm/abha/patient_profiles_controller.rb

APIs to Implement (Based on Postman Collection)

1. Update Profile

Update basic profile information (name, DOB, gender, address)

  • Endpoint: POST /profile/updateProfile

2. Update Email

Two-step process with OTP verification

  • Send OTP: POST /profile/updateEmail/sendOtp
  • Verify OTP: POST /profile/updateEmail/verifyOtp

3. Update Mobile

Two-step process with OTP verification

  • Send OTP: POST /profile/updateMobile/sendOtp
  • Verify OTP: POST /profile/updateMobile/verifyOtp

4. Update Password

Direct password update with current password verification

  • Endpoint: POST /profile/updatePassword

Multiple flows for linking ABHA numbers:

Via ABHA OTP:

  • Send OTP: POST /profile/linkAbha/sendOtp
  • Verify OTP: POST /profile/linkAbha/verifyOtp
  • Link Request: POST /profile/linkAbha/linkRequest

Via Aadhaar OTP:

  • Send OTP: POST /profile/linkAadhaar/sendOtp
  • Verify OTP: POST /profile/linkAadhaar/verifyOtp
  • Link Request: POST /profile/linkAadhaar/linkRequest

6. Switch Profile

Switch between multiple linked profiles

  • Switch Profile: POST /profile/switch
  • Verify Switch: POST /profile/verifySwitch

7. Logout

Terminate user session

  • Endpoint: POST /logout

EXCLUDED from Implementation:

  • PHR Certificate API
  • Refresh Token API

Database Schema

No Database Changes Required - Following the existing ABDM stateless architecture:

  • No new tables needed
  • No new columns in existing tables
  • No session/token persistence
  • No transaction tracking in database
  • All state managed by frontend via API responses

API Endpoints

Routes Configuration

# parts/abdm/routes.rb - Extend existing patients resource
resources :patients,
          only: %i[],
          controller: 'patient_profiles' do
  collection do
    # Existing endpoints (already implemented)
    post :profile
    post :qr_code  
    post :card
    
    # New endpoints - Profile Management
    post :update_profile
    
    # Email Update Flow
    post :update_email_send_otp
    post :update_email_verify_otp
    
    # Mobile Update Flow
    post :update_mobile_send_otp
    post :update_mobile_verify_otp
    
    # Password Management
    post :update_password
    
    # ABHA Number Linking - ABHA OTP Flow
    post :link_abha_send_otp
    post :link_abha_verify_otp
    post :link_abha_request
    
    # ABHA Number Linking - Aadhaar OTP Flow
    post :link_aadhaar_send_otp
    post :link_aadhaar_verify_otp
    post :link_aadhaar_request
    
    # Profile Switching
    post :switch_profile
    post :verify_switch_profile
    
    # Session Management
    post :logout
  end
end

Service Classes Implementation

Directory Structure

parts/abdm/services/abdm/abha/profile/
├── existing/
│   ├── fetch_patient_profile.rb      # ✅ Already exists
│   ├── fetch_patient_qr_code.rb      # ✅ Already exists  
│   └── fetch_patient_card.rb         # ✅ Already exists
└── new/
    ├── update_profile.rb             # 📝 To implement
    ├── email/
    │   ├── send_otp_for_email.rb     # 📝 To implement
    │   └── verify_otp_for_email.rb   # 📝 To implement
    ├── mobile/
    │   ├── send_otp_for_mobile.rb    # 📝 To implement
    │   └── verify_otp_for_mobile.rb  # 📝 To implement
    ├── password/
    │   └── update_password.rb        # 📝 To implement
    ├── linking/
    │   ├── send_abha_otp_for_link.rb     # 📝 To implement
    │   ├── verify_abha_otp_for_link.rb   # 📝 To implement
    │   ├── send_aadhaar_otp_for_link.rb  # 📝 To implement
    │   ├── verify_aadhaar_otp_for_link.rb # 📝 To implement
    │   ├── abha_link_request.rb          # 📝 To implement
    │   └── aadhaar_link_request.rb       # 📝 To implement
    ├── switch/
    │   ├── switch_profile.rb         # 📝 To implement
    │   └── verify_switch_profile.rb  # 📝 To implement
    └── logout.rb                     # 📝 To implement

Service Class Template (Following Existing Pattern)

module Abdm
  module Abha
    module Profile
      class ServiceName
        def self.perform(params, user_id)
          token = Abdm::FetchGatewayTokenV3.perform
          
          url = "#{Rails.application.credentials.abdm_phr_host}/profile/..."
          headers = {
            'Content-Type' => 'application/json',
            'Authorization' => "Bearer #{token}",
            'X-Token' => "Bearer #{params[:x_token]}",
            'REQUEST-ID' => SecureRandom.uuid,
            'TIMESTAMP' => Time.now.utc.iso8601(3)
          }
          
          body = build_request_body(params)
          
          begin
            result = HTTParty.post(url, body: body.to_json, headers: headers)
          rescue StandardError => e
            error = e.to_s
          end
          
          # Log Request (Following existing AbdmRequest pattern)
          abdm_request = AbdmRequest.create!(
            url: url,
            payload: body,
            requested_by_id: user_id,
            requested_by_type: 'User'
          )
          
          # Handle Response (Following existing pattern)
          if result.present? && result.success?
            abdm_request.update!(
              response: result.parsed_response,
              status_code: result.code,
              success: true
            )
          else
            abdm_request.update!(
              response: error || result&.parsed_response,
              status_code: result&.code,
              success: false
            )
          end
          
          result
        end
        
        private
        
        def self.build_request_body(params)
          # Implementation specific to each service
        end
      end
    end
  end
end

Interactor Classes Implementation

Directory Structure

parts/abdm/interactors/abdm/abha/patient_methods/
├── existing/
│   └── profile.rb                # ✅ Already exists
└── new/
    ├── update_profile.rb         # 📝 To implement
    ├── update_email.rb           # 📝 To implement (handles both send/verify)
    ├── update_mobile.rb          # 📝 To implement (handles both send/verify)
    ├── update_password.rb        # 📝 To implement
    ├── link_abha.rb             # 📝 To implement (handles ABHA OTP flow)
    ├── link_aadhaar.rb          # 📝 To implement (handles Aadhaar OTP flow)
    ├── switch_profile.rb        # 📝 To implement (handles switch/verify)
    └── logout.rb                # 📝 To implement

Interactor Template (Following Existing Pattern)

module Abdm
  module Abha
    module PatientMethods
      class InteractorName < BaseInteractor
        def call
          require_context(:x_token, :other_required_params)
          
          validate_input!
          process_request!
          
          # Success message
          context.message = 'Success message'
        end
        
        private
        
        def validate_input!
          # Input validation logic specific to each interactor
        end
        
        def process_request!
          result = Abdm::Abha::Profile::ServiceName.perform(
            context.to_h,
            context.current_user&.id
          )
          
          if result&.success?
            context.records = result.parsed_response
          else
            error_response = result&.parsed_response || { error: 'Operation failed' }
            context.message = error_response['message'] || error_response['error'] || 'Operation failed'
            context.fail!
          end
        end
      end
    end
  end
end

Controller Implementation

PatientProfilesController Updates

module Abdm
  module Abha
    class PatientProfilesController < ApiController
      before_action :policy_check
      
      # Existing actions (already implemented)
      def profile
        # ✅ Already exists
      end
      
      def qr_code 
        # ✅ Already exists
      end
      
      def card
        # ✅ Already exists  
      end
      
      # NEW ACTIONS TO IMPLEMENT:
      
      # Profile Update
      def update_profile
        result = PatientMethods::UpdateProfile.call(
          update_profile_params.merge(x_token: extract_x_token)
        )
        render_result(result)
      end
      
      # Email Update Flow
      def update_email_send_otp
        result = PatientMethods::UpdateEmail.call(
          email_send_params.merge(action: 'send_otp', x_token: extract_x_token)
        )
        render_result(result)
      end
      
      def update_email_verify_otp
        result = PatientMethods::UpdateEmail.call(
          email_verify_params.merge(action: 'verify_otp', x_token: extract_x_token)
        )
        render_result(result)
      end
      
      # Mobile Update Flow
      def update_mobile_send_otp
        result = PatientMethods::UpdateMobile.call(
          mobile_send_params.merge(action: 'send_otp', x_token: extract_x_token)
        )
        render_result(result)
      end
      
      def update_mobile_verify_otp
        result = PatientMethods::UpdateMobile.call(
          mobile_verify_params.merge(action: 'verify_otp', x_token: extract_x_token)
        )
        render_result(result)
      end
      
      # Password Update
      def update_password
        result = PatientMethods::UpdatePassword.call(
          password_params.merge(x_token: extract_x_token)
        )
        render_result(result)
      end
      
      # ABHA Link Flow (ABHA OTP)
      def link_abha_send_otp
        result = PatientMethods::LinkAbha.call(
          abha_send_params.merge(action: 'send_otp', x_token: extract_x_token)
        )
        render_result(result)
      end
      
      def link_abha_verify_otp
        result = PatientMethods::LinkAbha.call(
          abha_verify_params.merge(action: 'verify_otp', x_token: extract_x_token)
        )
        render_result(result)
      end
      
      def link_abha_request
        result = PatientMethods::LinkAbha.call(
          link_request_params.merge(action: 'link_request', x_token: extract_x_token)
        )
        render_result(result)
      end
      
      # ABHA Link Flow (Aadhaar OTP)
      def link_aadhaar_send_otp
        result = PatientMethods::LinkAadhaar.call(
          aadhaar_send_params.merge(action: 'send_otp', x_token: extract_x_token)
        )
        render_result(result)
      end
      
      def link_aadhaar_verify_otp
        result = PatientMethods::LinkAadhaar.call(
          aadhaar_verify_params.merge(action: 'verify_otp', x_token: extract_x_token)
        )
        render_result(result)
      end
      
      def link_aadhaar_request
        result = PatientMethods::LinkAadhaar.call(
          link_request_params.merge(action: 'link_request', x_token: extract_x_token)
        )
        render_result(result)
      end
      
      # Profile Switch Flow
      def switch_profile
        result = PatientMethods::SwitchProfile.call(
          switch_params.merge(action: 'switch', x_token: extract_x_token)
        )
        render_result(result)
      end
      
      def verify_switch_profile
        result = PatientMethods::SwitchProfile.call(
          verify_switch_params.merge(action: 'verify', x_token: extract_x_token)
        )
        render_result(result)
      end
      
      # Logout
      def logout
        result = PatientMethods::Logout.call(x_token: extract_x_token)
        render_result(result)
      end
      
      private
      
      def extract_x_token
        request.headers['X-Token']&.gsub('Bearer ', '')
      end
      
      def update_profile_params
        params.permit(
          :firstName, :middleName, :lastName,
          :dayOfBirth, :monthOfBirth, :yearOfBirth,
          :gender, :email, :mobile, :countryCode,
          address: [:addressLine, :country, :district, :pincode, :state, :town]
        )
      end
      
      def email_send_params
        params.permit(:email, :loginHint, :otpSystem, :scope)
      end
      
      def email_verify_params
        params.permit(:transactionId, :otp, :authMethods, :scope)
      end
      
      def mobile_send_params
        params.permit(:mobile, :loginHint, :otpSystem, :scope)
      end
      
      def mobile_verify_params
        params.permit(:transactionId, :otp, :authMethods, :scope)
      end
      
      def password_params
        params.permit(:password, :abhaAddress, :authMethods, :scope)
      end
      
      def abha_send_params
        params.permit(:abhaNumber, :loginHint, :otpSystem, :scope)
      end
      
      def abha_verify_params
        params.permit(:transactionId, :otp, :authMethods, :scope)
      end
      
      def aadhaar_send_params
        params.permit(:loginId, :loginHint, :otpSystem, :scope)
      end
      
      def aadhaar_verify_params
        params.permit(:transactionId, :otp, :authMethods, :scope)
      end
      
      def link_request_params
        params.permit(:transactionId, :accountAction)
      end
      
      def switch_params
        params.permit(:abhaAddress, :scope, :loginHint)
      end
      
      def verify_switch_params
        params.permit(:transactionId, :abhaAddress)
      end
      
      def policy_check
        authorize params, policy_class: PatientProfilesControllerPolicy
      end
    end
  end
end

Security Considerations

Authentication & Authorization

  • X-Token Validation: All endpoints require valid X-Token header
  • Existing Policy: Use existing PatientProfilesControllerPolicy
  • No Additional Authorization: Follow existing pattern (no patient record validation)

Error Handling Pattern

Stateless Error Handling:

  • All errors returned immediately via API response
  • Use context.message for error messages
  • Use context.fail! for failures
  • No database error tracking or persistence

API Security

  • Request Logging: All calls logged to abdm_requests table
  • Token Validation: X-Token validated by ABDM API directly
  • No Rate Limiting: Follow existing implementation (no additional rate limiting)

API URL Patterns (From Postman Collection)

Based on the collection analysis, the ABDM PHR Profile API URLs follow this pattern:

Base URL: {abdm_phr_host}/profile/

Update Profile: POST /profile/updateProfile
Update Email: POST /profile/updateEmail/sendOtp, /profile/updateEmail/verifyOtp  
Update Mobile: POST /profile/updateMobile/sendOtp, /profile/updateMobile/verifyOtp
Update Password: POST /profile/updatePassword
Link ABHA: POST /profile/linkAbha/sendOtp, /profile/linkAbha/verifyOtp, /profile/linkAbha/linkRequest
Link Aadhaar: POST /profile/linkAadhaar/sendOtp, /profile/linkAadhaar/verifyOtp, /profile/linkAadhaar/linkRequest  
Switch Profile: POST /profile/switch, /profile/verifySwitch
Logout: POST /logout

Testing Strategy

Request Specs

# spec/requests/abdm/patient_profiles_spec.rb
RSpec.describe 'Abdm::PatientProfiles', type: :request do
  describe 'POST /api/v3/abdm/abha/patients/update_profile' do
    it 'updates profile with valid data'
    it 'returns error for invalid X-Token'
    it 'validates required fields'
  end
  
  describe 'Email Update Flow' do
    it 'sends OTP to new email'
    it 'verifies OTP and updates email'
    it 'handles invalid OTP gracefully'
  end
  
  describe 'Mobile Update Flow' do
    it 'sends OTP to new mobile'
    it 'verifies OTP and updates mobile'
    it 'handles invalid mobile format'
  end
  
  describe 'Password Update' do
    it 'updates password with valid current password'
    it 'rejects invalid current password'
  end
  
  describe 'ABHA Linking Flow' do
    it 'sends ABHA OTP successfully'
    it 'verifies ABHA OTP and creates link request'
    it 'completes linking process'
  end
  
  describe 'Profile Switching' do
    it 'switches to different profile'
    it 'verifies switch with transaction ID'
  end
  
  describe 'Logout' do
    it 'logs out user successfully'
    it 'handles logout errors gracefully'
  end
end

Error Handling

Standard Error Response Format

Following existing ABDM pattern - errors returned directly from API:

{
  "success": false,
  "message": "The OTP provided is invalid or expired",
  "records": null
}

Common Error Scenarios

  • Invalid X-Token: Token validation failed
  • Invalid OTP: OTP verification failed
  • Invalid Input: Field validation errors
  • API Errors: ABDM service errors passed through directly

Implementation Summary

Files to Create: 14 Service Classes + 8 Interactor Classes = 22 New Files

Files to Modify: 2 Existing Files

  • PatientProfilesController (add 13 new actions)
  • routes.rb (add 13 new routes)

Key Architectural Decisions

  1. Stateless Design: No database persistence, following existing ABDM pattern
  2. Existing Service Pattern: Use HTTParty with AbdmRequest logging
  3. X-Token Authentication: All APIs require X-Token header like existing profile APIs
  4. Interactor Pattern: Use BaseInteractor with context passing and fail! pattern
  5. Error Handling: Simple context.fail! with message, no complex error objects
  6. API URLs: Use abdm_phr_host credential like existing profile services

Excluded from Implementation

  • ❌ PHR Certificate API
  • ❌ Refresh Token API
  • ❌ Database migrations/models
  • ❌ Token persistence
  • ❌ Session management
  • ❌ Transaction tracking tables

On this page