> ## Documentation Index
> Fetch the complete documentation index at: https://docs.browserbase.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Agentic credit card automation

> Effortlessly create virtual cards with Stripe and automate purchases using Browserbase.

Here are the steps:

1. Set up your environment
2. Create a Stripe Cardholder
3. Create a Virtual Card with spending controls
4. Retrieve Virtual Card Details
5. Make a Purchase

## Build an agentic credit card automation

### 1. Set up your environment

Install the required dependencies and set up your API keys.

<Tabs>
  <Tab title="Node.js">
    <CodeGroup>
      ```bash npm theme={null}
      npm install stripe @browserbasehq/sdk playwright-core dotenv
      ```

      ```bash yarn theme={null}
      yarn add stripe @browserbasehq/sdk playwright-core dotenv
      ```

      ```bash pnpm theme={null}
      pnpm add stripe @browserbasehq/sdk playwright-core dotenv
      ```

      ```bash bun theme={null}
      bun add stripe @browserbasehq/sdk playwright-core dotenv
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Python">
    <CodeGroup>
      ```bash pip theme={null}
      pip install stripe browserbase playwright python-dotenv
      ```

      ```bash poetry theme={null}
      poetry add stripe browserbase playwright python-dotenv
      ```

      ```bash uv theme={null}
      uv pip install stripe browserbase playwright python-dotenv
      ```

      ```bash conda theme={null}
      conda install -c conda-forge stripe browserbase playwright python-dotenv
      ```
    </CodeGroup>
  </Tab>
</Tabs>

Create a .env file with your API keys (securely store keys for Stripe, Browserbase, and optional AI models for Stagehand):

* Stripe [API key](https://dashboard.stripe.com/apikeys)
* Browserbase [API key](https://browserbase.com/settings/)
* (Optional) LLM API key for [Stagehand](https://www.stagehand.dev/)

```env .env theme={null}
STRIPE_API_KEY=your_stripe_api_key

BROWSERBASE_API_KEY=your_browserbase_api_key

ANTHROPIC_API_KEY=OPTIONAL_your_anthropic_api_key
OPENAI_API_KEY=OPTIONAL_your_openai_api_key
```

### 2. Create a Stripe cardholder

You must create a cardholder before issuing virtual cards. The cardholder will have a verified billing address and be eligible to receive virtual cards.

<Tabs>
  <Tab title="Node.js">
    ```ts create-cardholder.ts theme={null}
    import Stripe from 'stripe';
    import dotenv from 'dotenv';
    dotenv.config();

    const stripe = new Stripe(process.env.STRIPE_API_KEY!);

    async function createCardholder() {
        const cardholder = await stripe.issuing.cardholders.create({
            name: "Browserbase User",
            email: "hello@browserbase.com",
            phone_number: "+15555555555",
            status: 'active',
            type: 'individual',
            billing: {
                address: {
                line1: '123 Main Street',
                city: 'San Francisco',
                state: 'CA',
                country: 'US',
                postal_code: '94111',
                }
            },
        });
        console.log("Cardholder created:", cardholder.id);
        return cardholder;
    }

    const cardholder = createCardholder();
    ```
  </Tab>

  <Tab title="Python">
    ```python create-cardholder.py theme={null}
    import stripe
    import os
    import dotenv
    dotenv.load_dotenv()

    stripe.api_key = os.getenv("STRIPE_API_KEY")

    def create_cardholder():
        cardholder = stripe.issuing.Cardholder.create(
            name="Browserbase User",
            email="hello@browserbase.com",
            phone_number="+15555555555",
            status='active',
            type='individual',
            billing={
                "address": {
                    "line1": "123 Main Street",
                    "city": "San Francisco",
                    "state": "CA",
                    "country": "US",
                    "postal_code": "94111",
                }
            },
        )
        print("Cardholder created:", cardholder.id)
        return cardholder

    card_holder = create_cardholder()
    ```
  </Tab>
</Tabs>

<Note>
  Save the cardholder ID from the console output for the next step.
</Note>

### 3. Create a virtual card

Once you have a cardholder, you can create a virtual card under their name. This step generates a virtual card with a predefined spending limit. Stripe lets you customize the card's spending controls, including daily, monthly, or per-transaction limits. Find more information on spending controls in the [Stripe docs](https://docs.stripe.com/issuing/controls/spending-controls).

<Tabs>
  <Tab title="Node.js">
    ```ts create-card.ts theme={null}
    async function createCard(cardholderId: string) {
        const card = await stripe.issuing.cards.create({
            cardholder: cardholderId,
            currency: 'usd',
            type: 'virtual',
            spending_controls: {
                allowed_categories: ['charitable_and_social_service_organizations_fundraising'],
                // Choose to block certain categories instead of allowing them
                // blocked_categories: ['automated_cash_disburse'],
                spending_limits: [{
                    amount: 7500, // $75.00 measured in cents
                    interval: 'daily', // all_time, daily, weekly, monthly, yearly, per_authorization
                }],
            },
        });

        console.log('Card created:', card.id);
        return card;
    }

    const cardholderId = "ic_INPUT_CARDHOLDER_ID_HERE" // replace with your cardholder id from the previous step
    const virtual_card = createCard(cardholderId);
    ```
  </Tab>

  <Tab title="Python">
    ```python create-card.py theme={null}
    def create_card(cardholder_id):
        card = stripe.issuing.Card.create(
            cardholder=cardholder_id,
            currency='usd',
            type='virtual',
            spending_controls={
                "allowed_categories": ['charitable_and_social_service_organizations_fundraising'],
                # Choose to block certain categories instead of allowing them
                # "blocked_categories": ['automated_cash_disburse'],
                "spending_limits": [
                    {
                        "amount": 7500,  # measured in cents
                        "interval": "daily",  # all_time, daily, weekly, monthly, yearly, per_authorization
                    }
                ]
            },
        )
        print("Card created:", card.id)
        return card

    cardholder_id = "ic_INPUT_CARDHOLDER_ID_HERE" # replace with your card id from the previous step
    virtual_card = create_card(cardholder_id)
    ```
  </Tab>
</Tabs>

<Note>
  This function returns all the details needed to complete an online purchase.
</Note>

### 4. Retrieve virtual card details

After creating a virtual card, you'll need to retrieve its details (card number, expiration date, and CVC) to use it for transactions. You can use the returned data to automatically enter the card details when needed.

<Tabs>
  <Tab title="Node.js">
    ```typescript get-card.ts theme={null}
    export async function getCard(cardId: string) {
        const card = await stripe.issuing.cards.retrieve(
            cardId, {expand: ['number', 'cvc']});

        const cardInfo = {
            cardholder_firstName: card.cardholder.name.split(' ')[0],
            cardholder_lastName: card.cardholder.name.split(' ')[1],
            cardholder_email: card.cardholder.email,
            cardholder_phone: card.cardholder.phone_number,
            cardholder_address: card.cardholder.billing.address,
            card_number: card.number,
            expiration_month: card.exp_month,
            expiration_year: card.exp_year.toString().slice(-2), // 2028 -> 28
            cvc: card.cvc,
            brand: card.brand,
            currency: card.currency,
        };
        console.log('Card info:', cardInfo);
        return cardInfo;
    }

    const cardId = "ic_INPUT_CARD_ID_HERE"; // replace with your card id from the previous step
    getCard(cardId);
    ```
  </Tab>

  <Tab title="Python">
    ```python get-card.py theme={null}
    def getCard(card_id):
        card = stripe.issuing.Card.retrieve(card_id, expand=['number', 'cvc'])

        card_info = {
            'cardholder_firstName': card.cardholder.name.split(' ')[0],
            'cardholder_lastName': card.cardholder.name.split(' ')[1],
            'cardholder_email': card.cardholder.email,
            'cardholder_phone': card.cardholder.phone_number,
            'cardholder_address': card.cardholder.billing.address,
            'card_number': card.number,
            'expiration_month': card.exp_month,
            'expiration_year': str(card.exp_year)[-2:], # 2028 -> 28
            'cvc': card.cvc,
            'brand': card.brand,
            'currency': card.currency,
        }
        print('Card info:', card_info)
        return card_info

    card_id = "ic_REPLACE_WITH_YOUR_CARD_ID_HERE" # replace with your card id from the previous step
    card_info = getCard(card_id)
    ```
  </Tab>
</Tabs>

### 5. Make a purchase

In this step, you'll automate filling in the credit card payment form. This example walks you through navigating to the Red Cross donation page, selecting a donation amount, and completing the payment process using the virtual card details retrieved earlier.

<Tabs>
  <Tab title="Node.js">
    <CodeGroup>
      ```ts Stagehand theme={null}
      import { Stagehand } from "@browserbasehq/stagehand";
      import dotenv from "dotenv";
      import { getCard } from "./get-card.js";
      dotenv.config();

      const stagehand = new Stagehand({
          env: "BROWSERBASE",
      });

      const cardId = "ic_INPUT_CARD_ID_HERE"; // replace with your card id from the previous step

      async function main() {
          await stagehand.init();
          const page = stagehand.context.pages()[0];

          console.log(`Watching session: https://www.browserbase.com/sessions/${stagehand.browserbaseSessionID}`);

          const paymentInfo = await getCard(cardId);

          // Navigate to Red Cross donation page
          await page.goto('https://www.redcross.org/donate/donation.html/')

          const donationAmount = await stagehand.observe(
              "Find the donation amounts, and click $75"
          );
          // Click the first donation amount
          await stagehand.act(donationAmount[0])

          // Find the continue button and click it
          const continueButton = await stagehand.observe(
              "Find the continue button and click it"
          );
          await stagehand.act(continueButton[0])

          // Find the credit card button and click it
          const creditCardButton = await stagehand.observe(
              "Find the credit card button and click it"
          );
          await stagehand.act(creditCardButton[0])

          await stagehand.act("click the continue button")

          const formValues = await stagehand.observe(
              `Fill in the form with the following values: ${JSON.stringify(paymentInfo)}`
          );
          console.log("formValues", formValues);

          // Fill in the form with the values
          for (const value of formValues) {
              await stagehand.act(value);
          }
          await page.waitForTimeout(10000);

          // Click the submit button
          await stagehand.act("click the donate button")

          await stagehand.close();
      }
      main().catch(console.error);
      ```

      ```ts Playwright theme={null}
      import dotenv from 'dotenv';
      dotenv.config();
      import { chromium } from "playwright-core";
      import Browserbase from "@browserbasehq/sdk";
      import { getCard } from './get-card.ts';

      const BROWSERBASE_API_KEY = process.env.BROWSERBASE_API_KEY!;

      const cardId = "ic_INPUT_CARD_ID_HERE"; // replace with your card id from the previous step

      (async () => {
          const bb = new Browserbase({apiKey: BROWSERBASE_API_KEY});
          const session = await bb.sessions.create();
          const browser = await chromium.connectOverCDP(session.connectUrl);
          const defaultContext = browser.contexts()[0];
          const page = defaultContext.pages()[0];

          const paymentInfo = await getCard(cardId);

          // log session ID url
          console.log(`Session URL: https://www.browserbase.com/sessions/${session.id}`);

          // go to the donation page
          await page?.goto("https://www.redcross.org/donate/donation.html");

          // click the first donation amount
          await page?.click("#modf-handle-0-radio");

          // click the continue button
          await page?.click("text=Continue");

          // click the credit card button
          await page?.click("text=credit card");

          // Fill billing information
          await page?.fill("input[name='bill_to_forename']", paymentInfo.cardholder_firstName!);
          await page?.fill("input[name='bill_to_surname']", paymentInfo.cardholder_lastName!);
          await page?.fill("input[name='bill_to_email']", paymentInfo.cardholder_email!);
          await page?.fill("input[name='bill_to_phone']", paymentInfo.cardholder_phone!);

          // Fill in the address information
          await page?.fill("input[name='bill_to_address_line1']", paymentInfo.cardholder_address.line1!);
          await page?.fill("input[name='bill_to_address_city']", paymentInfo.cardholder_address.city!);
          await page?.fill("input[name='bill_to_address_postal_code']", paymentInfo.cardholder_address.postal_code!);
          await page?.selectOption("select#bill_to_address_state", paymentInfo.cardholder_address.state!);

          // Fill in the card information
          await page?.fill("input#cardnumber", paymentInfo.card_number!);
          await page?.fill("input#MM", paymentInfo.expiration_month!.toString());
          await page?.fill("input#YY", paymentInfo.expiration_year!.toString());
          await page?.fill("input#CVC", paymentInfo.cvc!.toString());

          // click donate button
          await page?.click("text=Donate");

          await page.close();
          await browser.close();
      })().catch((error) => console.error(error.message));
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Python">
    ```python Playwright theme={null}
    from playwright.sync_api import Playwright, sync_playwright
    from browserbase import Browserbase
    from get_card import getCard
    import os
    import dotenv
    dotenv.load_dotenv()

    bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])

    cardId = "ic_INPUT_CARD_ID_HERE" # replace with your card id from the previous step

    def run(playwright: Playwright) -> None:
        session = bb.sessions.create()

        browser = playwright.chromium.connect_over_cdp(session.connect_url)
        context = browser.contexts[0]
        page = context.pages[0]

        payment_info = getCard(cardId)

        # Watch the session
        print(f"Session URL: https://browserbase.com/sessions/{session.id}")

        # Navigate to the donation page
        page.goto("https://www.redcross.org/donate/donation.html")

        # Perform actions on the donation page
        page.click("#modf-handle-0-radio")
        page.click("text=Continue")
        page.click("text=credit card")

        # Fill billing information
        page.fill("input[name='bill_to_forename']", payment_info["cardholder_firstName"])
        page.fill("input[name='bill_to_surname']", payment_info["cardholder_lastName"])
        page.fill("input[name='bill_to_email']", payment_info["cardholder_email"])
        page.fill("input[name='bill_to_phone']", payment_info["cardholder_phone"])

        # Fill in the address information
        page.fill("input[name='bill_to_address_line1']", payment_info["cardholder_address"]["line1"])
        page.fill("input[name='bill_to_address_city']", payment_info["cardholder_address"]["city"])
        page.fill("input[name='bill_to_address_postal_code']", payment_info["cardholder_address"]["postal_code"])
        page.select_option("select#bill_to_address_state", payment_info["cardholder_address"]["state"])

        # Fill in the card information
        page.fill("input#cardnumber", payment_info["card_number"])
        page.fill("input#MM", str(payment_info["expiration_month"]))
        page.fill("input#YY", str(payment_info["expiration_year"]))
        page.fill("input#CVC", str(payment_info["cvc"]))

        # Click donate button
        page.click("text=Donate")

        page.close()
        browser.close()

    if __name__ == "__main__":
        with sync_playwright() as playwright:
            run(playwright)
    ```
  </Tab>
</Tabs>

🎉 You made an online purchase with **Stripe** and **Browserbase**!

## Next steps

With this foundation, you can build more advanced payment automation:

* Create multiple virtual cards for different departments or spending categories
* Integrate with expense management systems
* Set up automated subscription payments
* Build checkout automation for testing payment flows

## Best practices

* **Track your transactions**: Monitor card usage through the [Stripe Dashboard](https://dashboard.stripe.com/cards)
* **Handle errors gracefully**: Implement robust error handling for form fields and payment rejections
* **Add verification steps**: Verify successful transactions by checking for confirmation elements
* **Secure your credentials**: Never expose API keys in client-side code
