Card Verification Flow - Complete Implementation

Overview

This document describes the complete card verification flow that automatically verifies or rejects cards based on IBAN ownership.

Flow Summary

User (TMA) → Gateway → Payment Service → PayStar APIs → Auto-Approve/Reject

Detailed Flow

1. User Submits Card (TMA)

Endpoint: POST /api/v1/card/verify

Request:

{
  "card_number": "6104337952695920"
}

Headers: - Authorization: Bearer <JWT_TOKEN> - X-Telegram-ID: 39459186

2. Gateway Handler Processing

File: services/gateway/internal/handlers/card.go

Steps: 1. Validate card number (16 digits) 2. Get user info from database (first_name, last_name from Level 1 verification) 3. Call Payment Service /api/v1/payment/card-to-iban 4. Receive IBAN + owners list 5. Auto-verify logic: - Compare user’s first_name + last_name with all IBAN owners - If match found → status = "verified", set verified_at - If no match → status = "rejected", set rejection_reason - If no owners returned → status = "pending_verification" (manual review) 6. Insert into card_verifications table 7. Return result to user

3. Payment Service Processing

File: services/payment/internal/payment/service/payment_service.go

Method: GetIBANFromCard(ctx, cardNumber) -> CardVerificationResult

Steps: 1. Get PayStar provider 2. Step 1: Call CardToIban API - Endpoint: https://core.paystar.ir/api/open-banking/service/bank-inquiry/card-to-iban - Get IBAN from card number 3. Step 2: Call IbanInquiry API - Endpoint: https://core.paystar.ir/api/open-banking/service/bank-inquiry/iban-inquiry - Get owners list and bank name 4. Return combined result

4. PayStar Provider Implementation

File: services/payment/internal/providers/paystar/statement_client.go

Card-to-IBAN API

func (p *PaystarProvider) CardToIban(ctx context.Context, cardNumber string) (string, error)

Request:

{
  "application_id": "pq5np",
  "access_password": "WbBeLbadDbEOakhpigaGlgFVUrwtB3iCOABRQPhZq6o02yODST",
  "card": "6104337952695920"
}

Response:

{
  "status": 1,
  "data": {
    "iban": "IR340190000000120287328006",
    "response_variant": 0
  },
  "message": "درخواست با موفقیت انجام شد"
}

IBAN Inquiry API

func (p *PaystarProvider) IbanInquiry(ctx context.Context, iban string) (*providers.IBANInquiryResult, error)

Request:

{
  "application_id": "pq5np",
  "access_password": "WbBeLbadDbEOakhpigaGlgFVUrwtB3iCOABRQPhZq6o02yODST",
  "iban": "IR340190000000120287328006"
}

Response:

{
  "status": 1,
  "data": {
    "iban": "IR340190000000120287328006",
    "owners_info": [
      {
        "firstName": "علی",
        "lastName": "احمدی"
      }
    ],
    "bank_title": "بانک ملی"
  },
  "message": "درخواست با موفقیت انجام شد"
}

5. Name Matching Logic

Function: namesMatch(firstName1, lastName1, firstName2, lastName2) -> bool

Matching Rules: 1. Exact match (case-insensitive) 2. Reversed order (family name first vs last) 3. Full name concatenated match 4. Normalized (trimmed, lowercased)

Example Matches: - “Ali” + “Ahmadi” = “ali” + “ahmadi” ✓ - “Ali” + “Ahmadi” = “Ahmadi” + “Ali” ✓ (reversed) - “علی” + “احمدی” = “علی” + “احمدی” ✓

6. Database Schema

Table: card_verifications

CREATE TABLE card_verifications (
    id UUID PRIMARY KEY,
    user_id UUID NOT NULL,
    card_number TEXT NOT NULL,
    card_number_hash TEXT NOT NULL,
    cardholder_name TEXT,
    iban TEXT,
    bank_name TEXT,
    status TEXT NOT NULL, -- 'pending_verification', 'verified', 'rejected'
    verified_at TIMESTAMP,
    rejection_reason TEXT,
    created_at TIMESTAMP NOT NULL,
    updated_at TIMESTAMP NOT NULL,
    expires_at TIMESTAMP
);

7. Response to User

Success (Auto-Verified):

{
  "id": "uuid",
  "status": "verified",
  "card_number": "6104337952695920",
  "cardholder_name": "علی احمدی",
  "iban": "IR340190000000120287328006",
  "bank_name": "بانک ملی",
  "verified_at": "2025-11-20T19:00:00Z",
  "created_at": "2025-11-20T19:00:00Z",
  "message": "Card verified successfully"
}

Rejected (Name Mismatch):

{
  "id": "uuid",
  "status": "rejected",
  "card_number": "6104337952695920",
  "cardholder_name": "علی احمدی",
  "iban": "IR340190000000120287328006",
  "bank_name": "بانک ملی",
  "verified_at": null,
  "created_at": "2025-11-20T19:00:00Z",
  "message": "Card verification rejected: cardholder name does not match IBAN owner"
}

Configuration

Environment Variables

# PayStar Configuration
PAYSTAR_APPLICATION_ID=pq5np
PAYSTAR_ACCESS_PASSWORD=WbBeLbadDbEOakhpigaGlgFVUrwtB3iCOABRQPhZq6o02yODST
PAYSTAR_REFRESH_TOKEN=oPcEe6kFVGNyjlwfegnJu1WwLhN8aNuAE1lVtG0uiV5C1KrsbEUowg4rxhZu2zwt6rE0xG2pbCQjBZ15nnpKXAC0A2xmHCjsjaNx
PAYSTAR_BASE_URL=https://core.paystar.ir
PAYSTAR_ENABLED=true
PAYSTAR_COMPANY_ACCOUNT=0120287328006
PAYSTAR_COMPANY_IBAN=IR340190000000120287328006

Status Values

Status Description
pending_verification Awaiting manual admin review (no owners returned)
verified Automatically verified (name matched)
rejected Automatically rejected (name mismatch)

TMA Integration

Check Card Status

Endpoint: GET /api/v1/card/verify?id=<verification_id>

Response:

{
  "id": "uuid",
  "card_number": "6104337952695920",
  "iban": "IR340190000000120287328006",
  "bank_name": "بانک ملی",
  "status": "verified",
  "verified_at": "2025-11-20T19:00:00Z",
  "created_at": "2025-11-20T19:00:00Z"
}

List User Cards

Endpoint: GET /api/v1/card/list

Response:

{
  "cards": [
    {
      "id": "uuid",
      "card_number": "6104337952695920",
      "iban": "IR340190000000120287328006",
      "bank_name": "بانک ملی",
      "status": "verified",
      "verified_at": "2025-11-20T19:00:00Z",
      "created_at": "2025-11-20T19:00:00Z"
    }
  ]
}

Testing

Test Card Verification

# 1. Get JWT token from TMA login
TOKEN="your_jwt_token"
TELEGRAM_ID=39459186

# 2. Submit card verification
curl -X POST https://api.nextgiti.cloud/api/v1/card/verify \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Telegram-ID: $TELEGRAM_ID" \
  -H "Content-Type: application/json" \
  -d '{"card_number": "6104337952695920"}'

# 3. Check status
curl -X GET "https://api.nextgiti.cloud/api/v1/card/verify?id=<verification_id>" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Telegram-ID: $TELEGRAM_ID"

Next Steps (Fiat Deposit/Withdrawal)

After card verification is working in production:

  1. Fiat Deposit: User deposits IRT to verified IBAN, system credits wallet
  2. Fiat Withdrawal: User withdraws IRT from wallet to verified card/IBAN
  3. PayStar Bank Transfer: Use verified IBANs for payouts

Files Changed

  1. /services/gateway/internal/handlers/card.go - Card verification handler with auto-approve/reject
  2. /services/payment/internal/payment/service/payment_service.go - Card-to-IBAN + IBAN inquiry service
  3. /services/payment/internal/payment/transport/http/handler.go - Card-to-IBAN HTTP handler
  4. /services/payment/internal/payment/transport/http/routes.go - Added card-to-IBAN route
  5. /services/payment/internal/providers/provider.go - Added IBANInquiry interface
  6. /services/payment/internal/providers/paystar/statement_client.go - Implemented PayStar APIs

Deployment

# Build and deploy gateway
cd /opt/gitinext/stacks/gitinext-golang
make build-gateway && make docker-build-gateway && make docker-push-gateway
docker service update --force gitinext-golang_gateway

# Build and deploy payment service
cd services/payment
CGO_ENABLED=0 GOOS=linux go build -o bin/payment ./cmd/server/main.go
cd ../..
docker build -t registry.nextgiti.cloud/payment:latest -f services/payment/Dockerfile .
docker push registry.nextgiti.cloud/payment:latest
docker service update --force gitinext-golang_payment

© 2025 GitiNext - Enterprise Crypto Infrastructure | GitHub | Website