feat: implement e-Aadhaar KYC upload and verification service using Surepass API
Some checks failed
Deploy Beta (NATIVE) / deploy (push) Failing after 3s

This commit is contained in:
vickytechkey 2026-08-28 22:12:59 +05:30
parent d27643cf05
commit b49eb17027
7 changed files with 225 additions and 2 deletions

View file

@ -0,0 +1,31 @@
# Generated by Django 6.1 on 2026-08-28 14:22
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0005_product_additional_images_product_compare_at_price_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='SellerKYC',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('aadhaar_number', models.CharField(blank=True, max_length=20, null=True)),
('name', models.CharField(blank=True, max_length=255, null=True)),
('dob', models.CharField(blank=True, max_length=50, null=True)),
('gender', models.CharField(blank=True, max_length=10, null=True)),
('address', models.TextField(blank=True, null=True)),
('face_base64', models.TextField(blank=True, null=True)),
('verification_response', models.JSONField(blank=True, default=dict, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('supplier', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='kyc', to=settings.AUTH_USER_MODEL)),
],
),
]

View file

@ -61,6 +61,20 @@ class SupplierProfile(models.Model):
def __str__(self): def __str__(self):
return f"{self.user.username}'s Profile" return f"{self.user.username}'s Profile"
class SellerKYC(models.Model):
supplier = models.OneToOneField(User, on_delete=models.CASCADE, related_name='kyc')
aadhaar_number = models.CharField(max_length=20, blank=True, null=True)
name = models.CharField(max_length=255, blank=True, null=True)
dob = models.CharField(max_length=50, blank=True, null=True)
gender = models.CharField(max_length=10, blank=True, null=True)
address = models.TextField(blank=True, null=True)
face_base64 = models.TextField(blank=True, null=True)
verification_response = models.JSONField(default=dict, blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"{self.supplier.username} - KYC"
class OTPRecord(models.Model): class OTPRecord(models.Model):
TYPE_CHOICES = [ TYPE_CHOICES = [
('phone', 'Phone'), ('phone', 'Phone'),

View file

@ -1,7 +1,7 @@
import re import re
from rest_framework import serializers from rest_framework import serializers
from django.contrib.auth.models import User from django.contrib.auth.models import User
from .models import SupplierProfile, Product, Order, ReturnRequest, Wallet, WalletTransaction, Category from .models import SupplierProfile, Product, Order, ReturnRequest, Wallet, WalletTransaction, Category, SellerKYC
class CategoryListField(serializers.RelatedField): class CategoryListField(serializers.RelatedField):
def to_representation(self, value): def to_representation(self, value):
@ -11,17 +11,28 @@ class CategoryListField(serializers.RelatedField):
category, _ = Category.objects.get_or_create(name=data) category, _ = Category.objects.get_or_create(name=data)
return category return category
class SellerKYCSerializer(serializers.ModelSerializer):
class Meta:
model = SellerKYC
fields = ('aadhaar_number', 'name', 'dob', 'gender', 'address', 'created_at')
class SupplierProfileSerializer(serializers.ModelSerializer): class SupplierProfileSerializer(serializers.ModelSerializer):
onboarding_step = serializers.SerializerMethodField() onboarding_step = serializers.SerializerMethodField()
categories = CategoryListField(many=True, queryset=Category.objects.all(), required=False) categories = CategoryListField(many=True, queryset=Category.objects.all(), required=False)
aadhar_url = serializers.SerializerMethodField() aadhar_url = serializers.SerializerMethodField()
pan_url = serializers.SerializerMethodField() pan_url = serializers.SerializerMethodField()
logo_url = serializers.SerializerMethodField() logo_url = serializers.SerializerMethodField()
kyc = serializers.SerializerMethodField()
class Meta: class Meta:
model = SupplierProfile model = SupplierProfile
exclude = ('user',) exclude = ('user',)
def get_kyc(self, obj):
if hasattr(obj.user, 'kyc'):
return SellerKYCSerializer(obj.user.kyc).data
return None
def get_aadhar_url(self, obj): def get_aadhar_url(self, obj):
from .views import get_document_url from .views import get_document_url
request = self.context.get('request') request = self.context.get('request')

View file

@ -4,7 +4,8 @@ from rest_framework_simplejwt.views import TokenRefreshView
from .views import ( from .views import (
RegisterView, LoginView, LogoutView, VerifyOtpView, ProfileView, RegisterView, LoginView, LogoutView, VerifyOtpView, ProfileView,
ProductViewSet, BulkUploadView, OrderViewSet, ReturnRequestViewSet, ProductViewSet, BulkUploadView, OrderViewSet, ReturnRequestViewSet,
WalletView, WithdrawView, SubmitGstinView, PresignedUrlView, MockUploadView, MockDownloadView WalletView, WithdrawView, SubmitGstinView, PresignedUrlView, MockUploadView, MockDownloadView,
UploadEAadhaarView
) )
router = DefaultRouter() router = DefaultRouter()
@ -23,6 +24,7 @@ urlpatterns = [
path('profile/presigned-url/', PresignedUrlView.as_view(), name='presigned-url'), path('profile/presigned-url/', PresignedUrlView.as_view(), name='presigned-url'),
path('profile/mock-upload/', MockUploadView.as_view(), name='mock-upload'), path('profile/mock-upload/', MockUploadView.as_view(), name='mock-upload'),
path('profile/mock-download/', MockDownloadView.as_view(), name='mock-download'), path('profile/mock-download/', MockDownloadView.as_view(), name='mock-download'),
path('profile/kyc/upload-eaadhaar/', UploadEAadhaarView.as_view(), name='kyc_upload_eaadhaar'),
path('products/bulk-upload/', BulkUploadView.as_view(), name='bulk-upload'), path('products/bulk-upload/', BulkUploadView.as_view(), name='bulk-upload'),
path('wallet/', WalletView.as_view(), name='wallet'), path('wallet/', WalletView.as_view(), name='wallet'),
path('wallet/withdraw/', WithdrawView.as_view(), name='withdraw'), path('wallet/withdraw/', WithdrawView.as_view(), name='withdraw'),

View file

@ -597,3 +597,148 @@ class WithdrawView(APIView):
logger.info('Withdraw successful: user=%s amount=%.2f tx_id=%s', user.username, amount, tx.tx_id) logger.info('Withdraw successful: user=%s amount=%.2f tx_id=%s', user.username, amount, tx.tx_id)
return Response(WalletSerializer(wallet).data) return Response(WalletSerializer(wallet).data)
import requests
from django.core.mail import send_mail
from rest_framework.parsers import MultiPartParser, FormParser
from .models import SellerKYC
class UploadEAadhaarView(APIView):
permission_classes = [IsAuthenticated]
parser_classes = [MultiPartParser, FormParser]
def post(self, request):
file_obj = request.FILES.get('file')
if not file_obj:
return Response({'error': 'file is required'}, status=status.HTTP_400_BAD_REQUEST)
user = request.user
profile, _ = SupplierProfile.objects.get_or_create(user=user)
# 1. Store the file locally (mock S3) or to S3 directly
ext = 'pdf'
if file_obj.name.endswith('.xml'):
ext = 'xml'
version_name = f"version_{secrets.token_hex(4)}"
s3_key = f"supplierdocument/{user.id}/eaadhaar/{version_name}.{ext}"
aws_access = os.environ.get('AWS_ACCESS_KEY_ID') or os.environ.get('AWS_ACCESS_KEY')
aws_secret = os.environ.get('AWS_SECRET_ACCESS_KEY') or os.environ.get('AWS_SECRET_KEY')
bucket_name = os.environ.get('AWS_STORAGE_BUCKET_NAME') or os.environ.get('AWS_BUCKET_NAME') or 'betasupplierdocumentstorage'
region_name = os.environ.get('AWS_REGION', 'ap-south-2')
use_s3 = getattr(settings, 'USE_S3', False) or (aws_access and aws_secret)
if use_s3:
try:
import boto3
s3_client = boto3.client(
's3',
region_name=region_name,
aws_access_key_id=aws_access,
aws_secret_access_key=aws_secret
)
s3_client.upload_fileobj(file_obj, bucket_name, s3_key)
except Exception as e:
logger.error(f"S3 upload failed: {e}")
return Response({'error': 'Failed to upload document to storage.'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
else:
base_dir = os.environ.get('UPLOAD_MOUNT_DIR', '/home/ubuntu/mnt/s3files/supplierdocuments')
if not os.path.exists('/home/ubuntu/mnt/s3files/supplierdocuments') and not os.environ.get('UPLOAD_MOUNT_DIR'):
base_dir = os.path.join(settings.BASE_DIR, 'mnt/s3files/supplierdocuments')
file_path = os.path.join(base_dir, s3_key)
os.makedirs(os.path.dirname(file_path), exist_ok=True)
with open(file_path, 'wb') as f:
for chunk in file_obj.chunks():
f.write(chunk)
# Update profile with s3 key
profile.aadhar_s3_key = s3_key
profile.save()
# 2. Call Surepass API
surepass_token = os.environ.get('SUREPASS_JWT_TOKEN')
if not surepass_token:
logger.warning("Surepass token not configured. Skipping actual verification.")
mock_success_data = {
"aadhaar_number": "XXXX XXXX 1234",
"name": request.data.get('full_name', "Mock User"),
"dob": request.data.get('yob', "1990"),
"gender": "M",
"address": "123, Mock Address",
"face_status": False,
"face": "",
}
kyc, _ = SellerKYC.objects.update_or_create(
supplier=user,
defaults={
'aadhaar_number': mock_success_data['aadhaar_number'],
'name': mock_success_data['name'],
'dob': mock_success_data['dob'],
'gender': mock_success_data['gender'],
'address': mock_success_data['address'],
'face_base64': mock_success_data['face'],
'verification_response': {"mock": True, "data": mock_success_data}
}
)
# Send Email
send_mail(
'Aadhaar Verification Successful',
'Your Aadhaar document has been successfully verified.',
'noreply@sellercentral.com',
[user.email],
fail_silently=True,
)
return Response({'success': True, 'kyc_data': mock_success_data})
file_obj.seek(0)
url = "https://kyc-api.surepass.io/api/v1/aadhaar/upload/eaadhaar"
headers = {'Authorization': f'Bearer {surepass_token}'}
# Build payload
payload = {}
if request.data.get('yob'):
payload['yob'] = request.data.get('yob')
if request.data.get('full_name'):
payload['full_name'] = request.data.get('full_name')
if request.data.get('base64'):
payload['base64'] = request.data.get('base64')
files = [
('file', ('eaadhaar.pdf', file_obj, 'application/pdf'))
]
try:
response = requests.request("POST", url, headers=headers, data=payload, files=files)
resp_json = response.json()
if response.status_code == 200 and resp_json.get('success'):
data = resp_json.get('data', {})
kyc, _ = SellerKYC.objects.update_or_create(
supplier=user,
defaults={
'aadhaar_number': data.get('aadhaar_number'),
'name': data.get('name'),
'dob': data.get('dob'),
'gender': data.get('gender'),
'address': data.get('address'),
'face_base64': data.get('face'),
'verification_response': resp_json
}
)
send_mail(
'Aadhaar Verification Successful',
'Your Aadhaar document has been successfully verified.',
'noreply@sellercentral.com',
[user.email],
fail_silently=True,
)
return Response({'success': True, 'kyc_data': data})
else:
return Response({
'success': False,
'error': resp_json.get('message', 'Verification failed')
}, status=status.HTTP_422_UNPROCESSABLE_ENTITY)
except Exception as e:
logger.error(f"Error calling Surepass API: {e}")
return Response({'error': 'Internal error verifying Aadhaar'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

View file

@ -340,6 +340,25 @@ def load_s3_secrets():
s3_secrets = load_s3_secrets() s3_secrets = load_s3_secrets()
def load_surepass_secrets():
secret_name = "arn:aws:secretsmanager:ap-south-2:764709663363:secret:surpassbeta-r6EcKi"
region_name = "ap-south-2"
try:
session = boto3.session.Session()
client = session.client(service_name='secretsmanager', region_name=region_name)
get_secret_value_response = client.get_secret_value(SecretId=secret_name)
secrets = json.loads(get_secret_value_response['SecretString'])
if 'apikey' in secrets:
os.environ['SUREPASS_JWT_TOKEN'] = str(secrets['apikey'])
return secrets
except Exception as e:
print(f"Warning: Failed to load Surepass secrets from Secrets Manager: {e}")
return {}
surepass_secrets = load_surepass_secrets()
AWS_CLOUDFRONT_DOMAIN = os.environ.get('AWS_CLOUDFRONT_DOMAIN', '') AWS_CLOUDFRONT_DOMAIN = os.environ.get('AWS_CLOUDFRONT_DOMAIN', '')
USE_S3 = os.environ.get('USE_S3', 'False').lower() == 'true' USE_S3 = os.environ.get('USE_S3', 'False').lower() == 'true'
AWS_STORAGE_BUCKET_NAME = os.environ.get('AWS_STORAGE_BUCKET_NAME', 'betasupplierdocumentstorage') AWS_STORAGE_BUCKET_NAME = os.environ.get('AWS_STORAGE_BUCKET_NAME', 'betasupplierdocumentstorage')

View file

@ -12,3 +12,4 @@ psycopg2-binary
boto3 boto3
djangorestframework-simplejwt djangorestframework-simplejwt
requests