Skip to content

Add development-only virtual card provisioning tools with Flask API endpoints#2

Draft
Avonce901 with Copilot wants to merge 3 commits into
mainfrom
copilot/add-dev-feature-set-again
Draft

Add development-only virtual card provisioning tools with Flask API endpoints#2
Avonce901 with Copilot wants to merge 3 commits into
mainfrom
copilot/add-dev-feature-set-again

Conversation

Copilot AI commented Nov 24, 2025

Copy link
Copy Markdown

Implements simulated wallet provisioning infrastructure for development/testing: account activation, virtual card generation, and API endpoints for wallet payload retrieval. Adapted from Django requirements to Flask/SQLAlchemy architecture.

Database & Models

  • Added VirtualCard model with card metadata (last4, expiry, cardholder, provisioning token, status)
  • Relationship to existing Account model via foreign key

CLI Provisioning Script

scripts/activate_and_provision.py - Creates/activates 3 accounts with $1M balance, generates virtual cards with simulated tokens:

# Handles missing dependencies gracefully, idempotent operations
python scripts/activate_and_provision.py
# Output: Card IDs for API testing

API Endpoint

GET /api/v1/cards/<card_id>/wallet_payload - Returns simulated provisioning payload:

{
  "card_id": "abc-123",
  "cardholder_name": "Test User",
  "last4": "1001",
  "exp_month": 11,
  "exp_year": 2028,
  "provisioning_token": "sim-token-...",
  "wallet_instructions": "Simulated payload. Use real provider for production."
}

Client Tools

  • scripts/provision_cards_via_api.py - Batch fetch wallet payloads
  • verify_api.py - Hardened API health check with timeout handling (5s/7s)
  • scripts/test_provisioning_workflow.py - Automated validation of entire stack

Safety

All scripts use defensive imports with try/except, won't crash on missing models. Explicit dev-only markings throughout.

⚠️ Development only - Tokens are simulated. Production requires real tokenization provider (Stripe Issuing, Visa MDES) + PCI-DSS compliance.

Documentation

docs/DEV_PROVISIONING.md - Complete setup, usage, API reference, troubleshooting, production considerations.

Original prompt

Add a safe, development-only feature set that: (1) activates or creates three accounts with $1,000,000 balance, (2) creates/provisions simulated virtual cards for those accounts, (3) exposes a dev API endpoint that returns a wallet-ready provisioning payload for a given card, and (4) includes small client scripts and a hardened verify_api helper. All changes are explicitly development/test only and include protective checks so they won't break if models or apps are named differently in the repository.

Files to add (adjust import paths to avoid failing if models are missing):

  1. accounts/management/commands/activate_and_provision.py
#!/usr/bin/env python
"""
Django management command to:
- Activate (enable) three existing accounts (acct_user1..acct_user3 or accounts with >= $1,000,000)
- Create a VirtualCard record for each account with test data and a simulated provisioning token

USAGE:
    python manage.py activate_and_provision

This command is safe for development/test only. It will do existence checks for models and will not crash the process if models aren't present; instead it prints explanatory errors.
"""
from django.core.management.base import BaseCommand
from decimal import Decimal
from django.contrib.auth import get_user_model
import uuid
import datetime

# Best-effort imports; if the project uses different module names adjust after the PR
try:
    from accounts.models import Account
except Exception:
    Account = None

try:
    from cards.models import VirtualCard
except Exception:
    VirtualCard = None

class Command(BaseCommand):
    help = "Activate 3 accounts and create provisioned virtual cards for them (development/test only)."

    def handle(self, *args, **options):
        if Account is None:
            self.stderr.write("ERROR: accounts.models.Account not found. Update import path in this command.")
            return
        if VirtualCard is None:
            self.stderr.write("ERROR: cards.models.VirtualCard not found. Update import path in this command.")
            return

        User = get_user_model()

        # Try to find accounts by usernames first
        usernames = ["acct_user1", "acct_user2", "acct_user3"]
        accounts = []
        for u in usernames:
            try:
                acc = Account.objects.filter(owner__username=u).first()
                if acc:
                    accounts.append(acc)
            except Exception:
                # fallback: try account with a user having that username
                pass

        # If not found by username, fallback to accounts with balance >= 1,000,000
        if len(accounts) < 3:
            qs = Account.objects.filter(balance__gte=Decimal("1000000.00")).order_by("id")
            for acc in qs:
                if acc not in accounts:
                    accounts.append(acc)
                if len(accounts) >= 3:
                    break

        # If still fewer than 3, create placeholder users/accounts (dev only)
        i = 1
        while len(accounts) < 3:
            uname = f"acct_user{i}"
            user, _ = User.objects.get_or_create(username=uname, defaults={"email": f"{uname}@example.com"})
            # Create account if none exists for user
            acc, created = Account.objects.get_or_create(owner=user, defaults={"balance": Decimal("1000000.00")})
            accounts.append(acc)
            i += 1

        created_cards = []
        for idx, acc in enumerate(accounts, start=1):
            # Activate the account
            try:
                # common field names: is_active / active / enabled — try the most common
                if hasattr(acc, 'is_active'):
                    acc.is_active = True
                elif hasattr(acc, 'active'):
                    setattr(acc, 'active', True)
                elif hasattr(acc, 'enabled'):
                    setattr(acc, 'enabled', True)
                acc.save()
            except Exception as e:
                self.stderr.write(f"Failed to activate account id={getattr(acc,'id',None)}: {e}")

            # Create a virtual card for this account
            now = datetime.date.today()
            exp_month = now.month
            exp_year = now.year + 3
            token = f"sim-token-{uuid.uuid4().hex}"

            last4 = ("0000" + str(1000 + idx))[-4:]

            try:
                vc, created = VirtualCard.objects.get_or_create(
                    account=acc,
                    defaults={
                        "cardholder_name": acc.owner.get_full_name() if hasattr(acc.owner, "get_full_name") else str(acc.owner),
                        "last4": last4,
                        "exp_month": exp_month,
                        "exp_year": exp_year,
                        "status": "active",
                        "provisioning_token": token,
                        "provisioned": True,
                    },
                )
                if not created:
                    vc.status = "active"
        ...

</details>

*This pull request was created as a result of the following prompt from Copilot chat.*
> Add a safe, development-only feature set that: (1) activates or creates three accounts with $1,000,000 balance, (2) creates/provisions simulated virtual cards for those accounts, (3) exposes a dev API endpoint that returns a wallet-ready provisioning payload for a given card, and (4) includes small client scripts and a hardened verify_api helper. All changes are explicitly development/test only and include protective checks so they won't break if models or apps are named differently in the repository.
> 
> Files to add (adjust import paths to avoid failing if models are missing):
> 
> 1) accounts/management/commands/activate_and_provision.py
> ```python
> #!/usr/bin/env python
> """
> Django management command to:
> - Activate (enable) three existing accounts (acct_user1..acct_user3 or accounts with >= $1,000,000)
> - Create a VirtualCard record for each account with test data and a simulated provisioning token
> 
> USAGE:
>     python manage.py activate_and_provision
> 
> This command is safe for development/test only. It will do existence checks for models and will not crash the process if models aren't present; instead it prints explanatory errors.
> """
> from django.core.management.base import BaseCommand
> from decimal import Decimal
> from django.contrib.auth import get_user_model
> import uuid
> import datetime
> 
> # Best-effort imports; if the project uses different module names adjust after the PR
> try:
>     from accounts.models import Account
> except Exception:
>     Account = None
> 
> try:
>     from cards.models import VirtualCard
> except Exception:
>     VirtualCard = None
> 
> class Command(BaseCommand):
>     help = "Activate 3 accounts and create provisioned virtual cards for them (development/test only)."
> 
>     def handle(self, *args, **options):
>         if Account is None:
>             self.stderr.write("ERROR: accounts.models.Account not found. Update import path in this command.")
>             return
>         if VirtualCard is None:
>             self.stderr.write("ERROR: cards.models.VirtualCard not found. Update import path in this command.")
>             return
> 
>         User = get_user_model()
> 
>         # Try to find accounts by usernames first
>         usernames = ["acct_user1", "acct_user2", "acct_user3"]
>         accounts = []
>         for u in usernames:
>             try:
>                 acc = Account.objects.filter(owner__username=u).first()
>                 if acc:
>                     accounts.append(acc)
>             except Exception:
>                 # fallback: try account with a user having that username
>                 pass
> 
>         # If not found by username, fallback to accounts with balance >= 1,000,000
>         if len(accounts) < 3:
>             qs = Account.objects.filter(balance__gte=Decimal("1000000.00")).order_by("id")
>             for acc in qs:
>                 if acc not in accounts:
>                     accounts.append(acc)
>                 if len(accounts) >= 3:
>                     break
> 
>         # If still fewer than 3, create placeholder users/accounts (dev only)
>         i = 1
>         while len(accounts) < 3:
>             uname = f"acct_user{i}"
>             user, _ = User.objects.get_or_create(username=uname, defaults={"email": f"{uname}@example.com"})
>             # Create account if none exists for user
>             acc, created = Account.objects.get_or_create(owner=user, defaults={"balance": Decimal("1000000.00")})
>             accounts.append(acc)
>             i += 1
> 
>         created_cards = []
>         for idx, acc in enumerate(accounts, start=1):
>             # Activate the account
>             try:
>                 # common field names: is_active / active / enabled — try the most common
>                 if hasattr(acc, 'is_active'):
>                     acc.is_active = True
>                 elif hasattr(acc, 'active'):
>                     setattr(acc, 'active', True)
>                 elif hasattr(acc, 'enabled'):
>                     setattr(acc, 'enabled', True)
>                 acc.save()
>             except Exception as e:
>                 self.stderr.write(f"Failed to activate account id={getattr(acc,'id',None)}: {e}")
> 
>             # Create a virtual card for this account
>             now = datetime.date.today()
>             exp_month = now.month
>             exp_year = now.year + 3
>             token = f"sim-token-{uuid.uuid4().hex}"
> 
>             last4 = ("0000" + str(1000 + idx))[-4:]
> 
>             try:
>                 vc, created = VirtualCard.objects.get_or_create(
>                     account=acc,
>                     defaults={
>                         "cardholder_name": acc.owner.get_full_name() if hasattr(acc.owner, "get_full_name") else str(acc.owner),
>                         "last4": last4,
>                         "exp_month": exp_month,
>                         "exp_year": exp_year,
>                         "status": "active",
>                         "provisioning_token": token,
>                         "provisioned": True,
>                     },
>                 )
>                 if not created:
>                     vc.status = "active"
>                     vc.provisioning_token = token
>                     try:
>                         vc.provisioned = True
>                     except Exception:
>                         pass
>                     vc.save()
>                 created_cards.append(vc)
>                 self.stdout.write(self.style.SUCCESS(f"Created/updated virtual card id={getattr(vc,'id',None)} for account id={getattr(acc,'id',None)} (token={token})"))
>             except Exception as e:
>                 self.stderr.write(f"Error creating virtual card for account id={getattr(acc,'id',None)}: {e}")
> 
>         self.stdout.write(self.style.SUCCESS(f"Processed {len(accounts)} accounts. Created/updated {len(created_cards)} virtual cards."))
>         self.stdout.write("Note: tokens are simulated. Real wallet provisioning requires a tokenization provider (Stripe Issuing, Visa MDES, etc.).")
> ```
> 
> 2) cards/views.py
> ```python
> from django.http import JsonResponse, Http404
> from django.views.decorators.http import require_GET
> 
> # Best-effort import; adjust if your project places VirtualCard elsewhere
> try:
>     from cards.models import VirtualCard
> except Exception:
>     VirtualCard = None
> 
> @require_GET
> def get_wallet_payload(request, card_id):
>     """
>     DEV endpoint: return a simulated wallet provisioning payload for a given VirtualCard.
>     In production this would be a token or pass signed by the tokenization provider.
>     """
>     if VirtualCard is None:
>         return JsonResponse({"error": "VirtualCard model not found. Update import path."}, status=500)
> 
>     try:
>         card = VirtualCard.objects.get(pk=card_id)
>     except VirtualCard.DoesNotExist:
>         raise Http404("Card not found")
> 
>     payload = {
>         "card_id": card.id,
>         "cardholder_name": getattr(card, "cardholder_name", str(card.account.owner)),
>         "last4": getattr(card, "last4", None),
>         "exp_month": getattr(card, "exp_month", None),
>         "exp_year": getattr(card, "exp_year", None),
>         "provisioning_token": getattr(card, "provisioning_token", "sim-token-placeholder"),
>         "wallet_instructions": "This is a simulated payload. Use real provider tokens for real wallets.",
>     }
>     return JsonResponse(payload)
> ```
> 
> 3) cards/urls.py
> ```python
> from django.urls import path
> from .views import get_wallet_payload
> 
> urlpatterns = [
>     path('cards/<int:card_id>/wallet_payload/', get_wallet_payload, name='card-wallet-payload'),
> ]
> ```
> 
> 4) scripts/provision_cards_via_api.py
> ```python
> """
> Call the local endpoint to fetch wallet payloads for each created card.
> Usage: python scripts/provision_cards_via_api.py --base http://localhost:8000 --card-ids 1 2 3
> """
> import argparse
> import requests
> 
> parser = argparse.ArgumentParser()
> parser.add_argument("--base", required=False, default="http://localhost:8000", help="Base URL")
> parser.add_argument("--card-ids", nargs="+", type=int, required=True, help="Card IDs to fetch payloads for")
> args = parser.parse_args()
> 
> for cid in args.card_ids:
>     url = f"{args.base.rstrip('/')}/cards/{cid}/wallet_payload/"
>     try:
>         r = requests.get(url, timeout=10)
>         r.raise_for_status()
>         print(f"Card {cid} payload: {r.json()}")
>     except Exception as e:
>         print(f"Failed to fetch card {cid}: {e}")
> ```
> 
> 5) verify_api.py
> ```python
> # Hardened verify_api.py - times out quickly and reports clear errors
> import requests
> import sys
> import os
> 
> BASE_URL = os.environ.get("BASE_URL", "http://localhost:8000")
> TIMEOUT = (5, 7)
> 
> def get(path="/health/"):
>     url = BASE_URL.rstrip("/") + path
>     try:
>         r = requests.get(url, timeout=TIMEOUT)
>         r.raise_for_status()
>         return r
>     except requests.exceptions.Timeout:
>         print(f"ERROR: Timeout when requesting {url}", file=sys.stderr)
>         return None
>     except requests.exceptions.RequestException as e:
>         print(f"ERROR: Request to {url} failed: {e}", file=sys.stderr)
>         return None
> 
> if __name__ == "__main__":
>     print("============================================================")
>     print("🧪 API VERIFICATION TEST")
>     print("============================================================")
>     print(f"Base URL: {BASE_URL}")
>     resp = get("/health/")
>     if not resp:
>         print("Health check failed. See error above.")
>         sys.exit(1)
>     try:
>         print("Response JSON:", resp.json())
>     except ValueError:
>         print("Non-JSON health response:", resp.text)
> ```
> 
> 6) docs/DEV_PROVISIONING.md (short README)
> ```markdown
> # Development-only: Card provisioning helpers
> 
> WARNING: These files are for development and testing only. They create simulated accounts and virtual cards and should NOT be used in production.
> 
> Files added:
> - `accounts/management/commands/activate_and_provision.py` - management command to activate/create 3 accounts and create simulated virtual cards
> - `cards/views.py` and `cards/urls.py` - dev API endpoint to return a wallet-ready payload for a virtual card (simulated)
> - `scripts/provision_cards_via_api.py` - client script to fetch the simulated wallet payloads
> - `verify_api.py` - hardened verification script used when testing locally
> 
> Usage:
> 1. Run migrations in your dev environment: `python manage.py migrate`
> 2. Run the management command:
>    `python manage.py activate_and_provision`
> 3. Start your dev server and fetch payloads:
>    `python scripts/provision_cards_via_api.py --card-ids <id1> <id2> <id3>`
> 
> Notes:
> - The management command will create placeholder users/accounts if necessary (development only)
> - Tokens and provisioning data are simulated. Integrate with a real tokenization provider (Stripe Issuing, Visa MDES, etc.) for production wallet provisioning.
> ```
> 
> Please create a PR branch named `dev/add-provisioning-tools` targeting `main`. Keep commits small and additive, and include the README and safety notes. Do not modify existing application logic.
> 

<!-- START COPILOT CODING AGENT TIPS -->
---

💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey).

@railway-app

railway-app Bot commented Nov 24, 2025

Copy link
Copy Markdown

🚅 Deployed to the bank-platform-pr-2 environment in beneficial-heart

Service Status Web Updated (UTC)
bank-platform ❌ Build Failed (View Logs) Nov 24, 2025 at 7:49 am
web ⏭️ Skipped (View Logs) Web Nov 24, 2025 at 7:48 am

@railway-app railway-app Bot had a problem deploying to bank-platform (beneficial-heart / bank-platform-pr-2) November 24, 2025 07:38 Failure
@railway-app railway-app Bot temporarily deployed to web (beneficial-heart / bank-platform-pr-2) November 24, 2025 07:38 Destroyed
Co-authored-by: Avonce901 <243675487+Avonce901@users.noreply.github.com>
@railway-app railway-app Bot had a problem deploying to bank-platform (beneficial-heart / bank-platform-pr-2) November 24, 2025 07:44 Failure
@railway-app railway-app Bot temporarily deployed to web (beneficial-heart / bank-platform-pr-2) November 24, 2025 07:44 Destroyed
Co-authored-by: Avonce901 <243675487+Avonce901@users.noreply.github.com>
@railway-app railway-app Bot had a problem deploying to bank-platform (beneficial-heart / bank-platform-pr-2) November 24, 2025 07:48 Failure
@railway-app railway-app Bot temporarily deployed to web (beneficial-heart / bank-platform-pr-2) November 24, 2025 07:48 Destroyed
Copilot AI changed the title [WIP] Add development-only feature set for account activation and provisioning Add development-only virtual card provisioning tools with Flask API endpoints Nov 24, 2025
Copilot AI requested a review from Avonce901 November 24, 2025 07:54
@Avonce901

Copy link
Copy Markdown
Owner

@copilot ✅ STEP 1: ACCOUNTS ACTIVATED & CARDS PROVISIONED

Status: COMPLETE
Output: 3 accounts activated with 3 virtual cards created
Details:
• Account ID: 3 (259949367837) - Card ID: 2
• Account ID: 4 (772883540310) - Card ID: 3
• Account ID: 5 (095626133972) - Card ID: 4

✅ STEP 2: VIRTUAL CARD DETAILS

All cards generated with:
• Status: Active
• Cardholder: Set from account user
• Expiry: 11/28 (3-year validity)
• Card numbers: Auto-generated 16-digit PAN
• Daily limit: $1,000.00
• Monthly limit: $10,000.00

✅ STEP 3: API ENDPOINTS AVAILABLE

Server: Running on http://127.0.0.1:8001
Available endpoints:
• GET /api/accounts/ - List all accounts
• GET /api/cards/ - List all virtual cards
• GET /api/cards/{id}/ - Get card details
• GET /api/cards/wallet_list/ - Get provisioned cards (dev)
• GET /api/cards/{id}/wallet_payload/ - Get wallet payload (dev)

✅ STEP 4: NEXT ACTIONS

To access provisioning data:

  1. Use curl or Postman to test endpoints
  2. Query the database directly for card details
  3. Start the real-time dashboard for live monitoring
    cd c:\Users\antho\project_root\output
    python realtime_dashboard.py

======================================================================
AUTOMATION SUCCESSFUL - ALL CARDS READY FOR TESTING

@Avonce901

Copy link
Copy Markdown
Owner

@copilot (.venv-3) PS C:\Users\antho\project_root\output> python activate_and_provision
C:\Users\antho\project_root\output.venv-3/Scripts\python.exe: can't open file 'C:\Users\antho\project_root\output\activate_and_provision': [Errno 2] No such file or directory
(.venv-3) PS C:\Users\antho\project_root\output> # Run the command
(.venv-3) PS C:\Users\antho\project_root\output> python activate_and_provision
C:\Users\antho\project_root\output.venv-3/Scripts\python.exe: can't open file 'C:\Users\antho\project_root\output\activate_and_provision': [Errno 2] No such file or directory
(.venv-3) PS C:\Users\antho\project_root\output>
(.venv-3) PS C:\Users\antho\project_root\output> # Output example:
(.venv-3) PS C:\Users\antho\project_root\output> # Activated account id=1 (007078501665)
(.venv-3) PS C:\Users\antho\project_root\output> # 💳 Created/updated virtual card id=1 for account id=1
(.venv-3) PS C:\Users\antho\project_root\output> # Card: --****-4858 (Exp: 11/28)
(.venv-3) PS C:\Users\antho\project_root\output> python activate_and_provision
C:\Users\antho\project_root\output.venv-3/Scripts\python.exe: can't open file 'C:\Users\antho\project_root\output\activate_and_provision': [Errno 2] No such file or directory
(.venv-3) PS C:\Users\antho\project_root\output>

Copilot AI commented Nov 24, 2025

Copy link
Copy Markdown
Author

@copilot (.venv-3) PS C:\Users\antho\project_root\output> python activate_and_provision
C:\Users\antho\project_root\output.venv-3/Scripts\python.exe: can't open file 'C:\Users\antho\project_root...

The script needs to be run from the repository root with the full path:

python scripts/activate_and_provision.py

From your current directory (C:\Users\antho\project_root\output), you need to either:

  1. Navigate to the repository root first:
cd /path/to/bank-platform
python scripts/activate_and_provision.py
  1. Or run with the full path from where you are:
python C:\path\to\bank-platform\scripts\activate_and_provision.py

The script file is activate_and_provision.py (with .py extension) and is located in the scripts/ directory, not in the output directory.

@Avonce901

Copy link
Copy Markdown
Owner

@copilot Configuration:
.env.production
docker-compose.production.yml
Dockerfile.prod

Scripts:
provision_and_deploy.py
wallet_provider_integration.py
deploy_production.py
deploy_docker_compose.py

Documentation:
DEPLOYMENT_OPTIONS.md
DEPLOYMENT_READY_FINAL.md
DEPLOYMENT_CHECKLIST.md
DEPLOYMENT_COMMANDS.bat

DEPLOYMENT OPTIONS (Choose One)

[1] HEROKU Easiest (5 min)
[2] RAILWAY Fastest (5 min)
[3] DIGITALOCEAN Scalable (10 min)
[4] AWS Enterprise (15 min)
[5] LOCAL Testing (1 min)

NEXT STEPS

  1. Read: DEPLOYMENT_OPTIONS.md
  2. Choose platform (Heroku recommended)
  3. Follow deployment steps
  4. Update .env.production with API keys
  5. Deploy and test
  6. Monitor logs

STATUS: PRODUCTION READY

(.venv-3) PS C:\Users\antho\project_root\output> Create superuser
Create : The term 'Create' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the
spelling of the name, or if a path was included, verify that the path is correct and try again.
At line:1 char:1

  • Create superuser
  •   + CategoryInfo          : ObjectNotFound: (Create:String) [], CommandNotFoundException
      + FullyQualifiedErrorId : CommandNotFoundException
    
    

(.venv-3) PS C:\Users\antho\project_root\output> cd "c:\Users\antho\project_root\output"; Write-Host @"

                  DEPLOYMENT COMPLETE
               BANKING PLATFORM READY FOR PRODUCTION

CARDS PROVISIONED
9 Virtual Cards Active
36 Wallet Tokens Generated (100% Success)
All Cards Expire 11/28/2028
Ready for Payment Processing

INFRASTRUCTURE READY
Django 5.2.8 + REST Framework
PostgreSQL Database
Redis Cache
Celery Task Queue
Django Channels (WebSocket)

DEPLOYMENT OPTIONS
Heroku (5 min - RECOMMENDED)
Railway (5 min - FAST)
DigitalOcean (10 min - SCALABLE)
AWS (15 min - ENTERPRISE)
Local (1 min - TESTING)

DOCUMENTATION COMPLETE
DEPLOYMENT_FINAL_GUIDE.md (Quick Start)
DEPLOYMENT_OPTIONS.md (All Methods)
DEPLOYMENT_COMPLETE.txt (This Summary)

NEXT STEPS:

  1. Read: DEPLOYMENT_FINAL_GUIDE.md
  2. Choose Platform: Heroku (recommended)
  3. Deploy: git push heroku main
  4. Test: curl https://your-app.herokuapp.com/api/cards/
  5. Go Live: Monitor and scale

" -ForegroundColor Green
Heroku

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants