diff --git a/api/migrations/0006_sellerkyc.py b/api/migrations/0006_sellerkyc.py new file mode 100644 index 0000000..7b0f90c --- /dev/null +++ b/api/migrations/0006_sellerkyc.py @@ -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)), + ], + ), + ] diff --git a/api/models.py b/api/models.py index cd84265..38f9d21 100644 --- a/api/models.py +++ b/api/models.py @@ -61,6 +61,20 @@ class SupplierProfile(models.Model): def __str__(self): 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): TYPE_CHOICES = [ ('phone', 'Phone'), diff --git a/api/serializers.py b/api/serializers.py index fbdeed7..0bb207a 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -1,7 +1,7 @@ import re from rest_framework import serializers 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): def to_representation(self, value): @@ -11,17 +11,28 @@ class CategoryListField(serializers.RelatedField): category, _ = Category.objects.get_or_create(name=data) return category +class SellerKYCSerializer(serializers.ModelSerializer): + class Meta: + model = SellerKYC + fields = ('aadhaar_number', 'name', 'dob', 'gender', 'address', 'created_at') + class SupplierProfileSerializer(serializers.ModelSerializer): onboarding_step = serializers.SerializerMethodField() categories = CategoryListField(many=True, queryset=Category.objects.all(), required=False) aadhar_url = serializers.SerializerMethodField() pan_url = serializers.SerializerMethodField() logo_url = serializers.SerializerMethodField() + kyc = serializers.SerializerMethodField() class Meta: model = SupplierProfile 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): from .views import get_document_url request = self.context.get('request') diff --git a/api/urls.py b/api/urls.py index cffcf39..567c86c 100644 --- a/api/urls.py +++ b/api/urls.py @@ -4,7 +4,8 @@ from rest_framework_simplejwt.views import TokenRefreshView from .views import ( RegisterView, LoginView, LogoutView, VerifyOtpView, ProfileView, ProductViewSet, BulkUploadView, OrderViewSet, ReturnRequestViewSet, - WalletView, WithdrawView, SubmitGstinView, PresignedUrlView, MockUploadView, MockDownloadView + WalletView, WithdrawView, SubmitGstinView, PresignedUrlView, MockUploadView, MockDownloadView, + UploadEAadhaarView ) router = DefaultRouter() @@ -23,6 +24,7 @@ urlpatterns = [ path('profile/presigned-url/', PresignedUrlView.as_view(), name='presigned-url'), path('profile/mock-upload/', MockUploadView.as_view(), name='mock-upload'), 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('wallet/', WalletView.as_view(), name='wallet'), path('wallet/withdraw/', WithdrawView.as_view(), name='withdraw'), diff --git a/api/views.py b/api/views.py index edf1ec3..0461abd 100644 --- a/api/views.py +++ b/api/views.py @@ -597,3 +597,148 @@ class WithdrawView(APIView): logger.info('Withdraw successful: user=%s amount=%.2f tx_id=%s', user.username, amount, tx.tx_id) 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) diff --git a/config/settings.py b/config/settings.py index 5798982..d95da71 100644 --- a/config/settings.py +++ b/config/settings.py @@ -340,6 +340,25 @@ def 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', '') USE_S3 = os.environ.get('USE_S3', 'False').lower() == 'true' AWS_STORAGE_BUCKET_NAME = os.environ.get('AWS_STORAGE_BUCKET_NAME', 'betasupplierdocumentstorage') diff --git a/requirements.txt b/requirements.txt index 825b620..62f552d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,3 +12,4 @@ psycopg2-binary boto3 djangorestframework-simplejwt +requests