# Setup Script Template

Once you have all inputs from Step 3, **generate a single Python script** called `setup_payments.py` that executes all the following steps automatically without human intervention. Write the script, then execute it.

The script must:

1. Store payment provider credentials in AgentCore Identity
2. Create the IAM execution role with trust policy and permissions
3. Wait for IAM propagation (15 seconds)
4. Create the Payment Manager and wait for READY status
5. Create the Payment Connector
6. Create the Payment Instrument (wallet)
7. Print a summary of all created resources and next steps

## Template

Substitute the developer's inputs into the configuration section:

```python
"""
AgentCore Payments Setup Script
Generated by the payments skill. Executes all non-interactive setup steps.

NAMING RULES:
- Resource names (credential provider, manager, connector): lowercase alphanumeric + hyphens only.
  NO underscores, NO dots, NO uppercase. Pattern: [a-z0-9]([a-z0-9-]*[a-z0-9])?
- The paymentManagerId (returned by create) is used for CP get/list operations.
- The paymentManagerArn (returned by create) is used for DP operations (instrument, session, process).
- create_payment_session requires userId parameter.
"""
import boto3
import json
import uuid
import time
import os

# === CONFIGURATION (from developer inputs) ===
REGION = "<REGION>"                          # e.g., "ap-southeast-2"
ACCOUNT_ID = "<ACCOUNT_ID>"                  # e.g., "123456789012"
PROVIDER = "<PROVIDER>"                      # "CoinbaseCDP" or "StripePrivy"
END_USER_EMAIL = "<END_USER_EMAIL>"          # e.g., "developer@example.com"
RESOURCE_PREFIX = "paymentspoc"              # prefix for all resource names

# Read credentials from environment variables (NOT from file directly).
# Run `source .env.payments` in your terminal before executing this script.
# Do NOT pass credentials through the agent — they must stay local.

# For Coinbase:
COINBASE_API_KEY_ID = os.environ.get("COINBASE_API_KEY_ID", "")
COINBASE_API_KEY_SECRET = os.environ.get("COINBASE_API_KEY_SECRET", "")
COINBASE_WALLET_SECRET = os.environ.get("COINBASE_WALLET_SECRET", "")
# For Stripe:
AUTH_PRIVATE_KEY = os.environ.get("AUTH_PRIVATE_KEY", "")
AUTH_ID = os.environ.get("AUTH_ID", "")
PRIVY_APP_ID = os.environ.get("PRIVY_APP_ID", "")
PRIVY_APP_SECRET = os.environ.get("PRIVY_APP_SECRET", "")

# === CLIENTS ===
iam = boto3.client("iam")
cp_client = boto3.client("bedrock-agentcore-control", region_name=REGION)
dp_client = boto3.client("bedrock-agentcore", region_name=REGION)

print("=" * 60)
print("AgentCore Payments Setup")
print("=" * 60)

# === STEP 1: Store credentials ===
print("\n[1/6] Storing payment provider credentials...")
cred_name = f"{RESOURCE_PREFIX}-creds"

def create_credential_provider_with_retry(name, vendor, config, max_retries=5):
    """Create credential provider, appending a numeric suffix if name already exists."""
    for attempt in range(max_retries):
        unique_name = name if attempt == 0 else f"{name}-{attempt}"
        try:
            if vendor == "CoinbaseCDP":
                resp = cp_client.create_payment_credential_provider(
                    name=unique_name,
                    credentialProviderVendor=vendor,
                    providerConfigurationInput={"coinbaseCdpConfiguration": config}
                )
            elif vendor == "StripePrivy":
                resp = cp_client.create_payment_credential_provider(
                    name=unique_name,
                    credentialProviderVendor=vendor,
                    providerConfigurationInput={"stripePrivyConfiguration": config}
                )
            print(f"  (Using name: {unique_name})")
            return resp
        except Exception as e:
            if "already exists" in str(e).lower() or "conflict" in str(e).lower():
                print(f"  Name '{unique_name}' already exists, trying with suffix...")
                continue
            raise
    raise Exception(f"Failed to create credential provider after {max_retries} attempts")

if PROVIDER == "CoinbaseCDP":
    cred_config = {
        "apiKeyId": COINBASE_API_KEY_ID,
        "apiKeySecret": COINBASE_API_KEY_SECRET,
        "walletSecret": COINBASE_WALLET_SECRET
    }
elif PROVIDER == "StripePrivy":
    cred_config = {
        "appId": PRIVY_APP_ID,
        "appSecret": PRIVY_APP_SECRET,
        "authorizationPrivateKey": AUTH_PRIVATE_KEY,
        "authorizationId": AUTH_ID
    }

cred_resp = create_credential_provider_with_retry(cred_name, PROVIDER, cred_config)
credential_provider_arn = cred_resp["credentialProviderArn"]
print(f"  OK Credential Provider ARN: {credential_provider_arn}")

# === STEP 2: Create IAM role ===
print("\n[2/6] Creating IAM service role...")
base_role_name = f"AgentCorePayments-{RESOURCE_PREFIX}"

def create_role_with_retry(base_name, max_retries=5):
    """Create IAM role, appending a numeric suffix if name already exists."""
    for attempt in range(max_retries):
        unique_name = base_name if attempt == 0 else f"{base_name}-{attempt}"
        try:
            iam.create_role(
                RoleName=unique_name,
                AssumeRolePolicyDocument=json.dumps({
                    "Version": "2012-10-17",
                    "Statement": [{
                        "Effect": "Allow",
                        "Principal": {"Service": "bedrock-agentcore.amazonaws.com"},
                        "Action": "sts:AssumeRole",
                        "Condition": {
                            "StringEquals": {"aws:SourceAccount": ACCOUNT_ID},
                            "ArnLike": {"aws:SourceArn": f"arn:aws:bedrock-agentcore:{REGION}:{ACCOUNT_ID}:payment-manager/{RESOURCE_PREFIX}-*"}
                        }
                    }]
                }),
                Description="Service role for AgentCore Payments"
            )
            print(f"  (Using role name: {unique_name})")
            return unique_name
        except iam.exceptions.EntityAlreadyExistsException:
            print(f"  Role '{unique_name}' already exists, trying with suffix...")
            continue
    raise Exception(f"Failed to create role after {max_retries} attempts")

role_name = create_role_with_retry(base_role_name)

iam.put_role_policy(
    RoleName=role_name,
    PolicyName="PaymentsResourceRetrievalPolicy",
    PolicyDocument=json.dumps({
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "WorkloadIdentity",
                "Effect": "Allow",
                "Action": [
                    "bedrock-agentcore:CreateWorkloadIdentity",
                    "bedrock-agentcore:GetWorkloadAccessToken",
                    "bedrock-agentcore:GetResourcePaymentToken"
                ],
                "Resource": [
                    f"arn:aws:bedrock-agentcore:{REGION}:{ACCOUNT_ID}:token-vault/default",
                    f"arn:aws:bedrock-agentcore:{REGION}:{ACCOUNT_ID}:token-vault/default/paymentcredentialprovider/*",
                    f"arn:aws:bedrock-agentcore:{REGION}:{ACCOUNT_ID}:workload-identity-directory/default",
                    f"arn:aws:bedrock-agentcore:{REGION}:{ACCOUNT_ID}:workload-identity-directory/default/workload-identity/*"
                ]
            },
            {
                "Sid": "SecretsAccess",
                "Effect": "Allow",
                "Action": "secretsmanager:GetSecretValue",
                "Resource": f"arn:aws:secretsmanager:{REGION}:{ACCOUNT_ID}:secret:bedrock-agentcore-identity*"
            }
        ]
    })
)
role_arn = f"arn:aws:iam::{ACCOUNT_ID}:role/{role_name}"
print(f"  OK Role ARN: {role_arn}")
print("  Waiting 15s for IAM propagation...")
time.sleep(15)

# === STEP 3: Create Payment Manager ===
print("\n[3/6] Creating Payment Manager...")
mgr_resp = cp_client.create_payment_manager(
    name=RESOURCE_PREFIX,
    description="Payment manager created by AgentCore Payments skill",
    authorizerType="AWS_IAM",
    roleArn=role_arn,
    clientToken=str(uuid.uuid4())
)
payment_manager_arn = mgr_resp["paymentManagerArn"]
manager_id = mgr_resp["paymentManagerId"]
print(f"  OK Payment Manager ARN: {payment_manager_arn}")

# Wait for READY
for i in range(12):
    status_resp = cp_client.get_payment_manager(paymentManagerId=manager_id)
    if status_resp["status"] == "READY":
        break
    time.sleep(5)
if status_resp["status"] != "READY":
    raise Exception(
        f"Payment Manager did not reach READY status after 60s "
        f"(current: {status_resp['status']}). Check CloudTrail for errors."
    )
print(f"  OK Status: {status_resp['status']}")

# === STEP 4: Create Payment Connector ===
print("\n[4/6] Creating Payment Connector...")
connector_config_key = "coinbaseCDP" if PROVIDER == "CoinbaseCDP" else "stripePrivy"
conn_resp = cp_client.create_payment_connector(
    paymentManagerId=manager_id,
    name=f"{RESOURCE_PREFIX}connector",
    description=f"{PROVIDER} connector",
    type=PROVIDER,
    credentialProviderConfigurations=[{
        connector_config_key: {"credentialProviderArn": credential_provider_arn}
    }],
    clientToken=str(uuid.uuid4())
)
connector_id = conn_resp["paymentConnectorId"]
print(f"  OK Connector ID: {connector_id}")

# === STEP 5: Create Payment Instrument ===
print("\n[5/6] Creating Payment Instrument (wallet)...")
user_id = f"{RESOURCE_PREFIX}-user"
instr_resp = dp_client.create_payment_instrument(
    paymentManagerArn=payment_manager_arn,
    paymentConnectorId=connector_id,
    userId=user_id,
    paymentInstrumentType="EMBEDDED_CRYPTO_WALLET",
    paymentInstrumentDetails={
        "embeddedCryptoWallet": {
            "network": "ETHEREUM",
            "linkedAccounts": [
                {"email": {"emailAddress": END_USER_EMAIL}}
            ]
        }
    },
    clientToken=str(uuid.uuid4())
)
instrument_data = instr_resp.get("paymentInstrument", instr_resp)
payment_instrument_id = instrument_data["paymentInstrumentId"]
wallet_details = instrument_data.get("paymentInstrumentDetails", {}).get("embeddedCryptoWallet", {})
wallet_address = wallet_details.get("walletAddress", "pending")
redirect_url = wallet_details.get("redirectUrl", None)
print(f"  OK Instrument ID: {payment_instrument_id}")
print(f"  OK Wallet Address: {wallet_address}")

# === STEP 6: Create Payment Session ===
print("\n[6/6] Creating Payment Session...")
session_resp = dp_client.create_payment_session(
    paymentManagerArn=payment_manager_arn,
    userId=user_id,
    expiryTimeInMinutes=60
)
payment_session_id = session_resp["paymentSession"]["paymentSessionId"]
print(f"  OK Session ID: {payment_session_id}")

# === SUMMARY ===
print("\n" + "=" * 60)
print("SETUP COMPLETE")
print("=" * 60)
print(f"""
Resources created:
  Payment Manager ARN:  {payment_manager_arn}
  Connector ID:         {connector_id}
  Instrument ID:        {payment_instrument_id}
  Wallet Address:       {wallet_address}
  Session ID:           {payment_session_id}
  User ID:              {user_id}
  Region:               {REGION}

Environment variables for your agent:
  export PAYMENT_MANAGER_ARN="{payment_manager_arn}"
  export PAYMENT_INSTRUMENT_ID="{payment_instrument_id}"
  export PAYMENT_SESSION_ID="{payment_session_id}"
  export PAYMENT_USER_ID="{user_id}"
  export AWS_REGION="{REGION}"
""")

print("\nMANUAL STEPS REQUIRED:\n")

# Step 1: Delegation — provider-specific
if PROVIDER == "CoinbaseCDP":
    print(f"""1. DELEGATION — Grant the agent permission to spend from the wallet:
   Visit: {redirect_url}
   Log in with: {END_USER_EMAIL}
   Grant permissions to the wallet address: {wallet_address}
""")
elif PROVIDER == "StripePrivy":
    print(f"""1. DELEGATION — Enable delegation on the embedded wallet:
   a. Set up a frontend using the Privy frontend SDK:
      https://github.com/privy-io/aws-agentcore-sdk
   b. Log in with the end user email: {END_USER_EMAIL}
   c. Approve delegation for the wallet address: {wallet_address}
""")

# Step 2: Funding — same for both providers
print(f"""2. FUNDING — Send testnet USDC to the wallet:
   Go to: https://faucet.circle.com/
   Select: Base Sepolia
   Paste wallet address: {wallet_address}
""")
```

## After executing the script

- Tell the developer to run `source .env.payments` before executing the script
- Print the summary to the developer
- Tell them to complete the **two manual steps** (delegation + funding) for the provider they chose
- Do NOT reference the other provider's flow — only show steps for the provider in use
- Wait for them to confirm before proceeding to Step 5 (wiring)
