# Stripe Client

Process payments, manage customers, and interact with Stripe's payment platform.

## Methods

| Method                                   | Description                                        |
| ---------------------------------------- | -------------------------------------------------- |
| `apiRequest(options, schema, metadata?)` | Make any Stripe API request with schema validation |

## Usage

### Create a Customer

```typescript
import { api, z, stripe } from "@superblocksteam/sdk-api";

// Integration ID from the integrations panel
const PROD_STRIPE = "a1b2c3d4-5678-90ab-cdef-stripe000001";

const CustomerSchema = z.object({
  id: z.string(),
  object: z.literal("customer"),
  email: z.string().nullable(),
  name: z.string().nullable(),
  created: z.number(),
  metadata: z.record(z.string()),
  default_source: z.string().nullable(),
});

export default api({
  name: "StripeExample",
  integrations: {
    stripe: stripe(PROD_STRIPE),
  },
  input: z.object({
    email: z.string().email(),
    name: z.string(),
  }),
  output: z.object({
    customerId: z.string(),
  }),
  async run(ctx, { email, name }) {
    const result = await ctx.integrations.stripe.apiRequest(
      {
        method: "POST",
        path: "/v1/customers",
        body: {
          email: email,
          name: name,
          metadata: {
            source: "api",
          },
        },
      },
      { response: CustomerSchema },
    );

    return { customerId: result.id };
  },
});
```

### Get a Customer

```typescript
const customer = await ctx.integrations.stripe.apiRequest(
  {
    method: "GET",
    path: `/v1/customers/${customerId}`,
  },
  { response: CustomerSchema },
);

console.log(`Customer: ${customer.name} (${customer.email})`);
```

### Create a Payment Intent

```typescript
const PaymentIntentSchema = z.object({
  id: z.string(),
  object: z.literal("payment_intent"),
  amount: z.number(),
  currency: z.string(),
  status: z.string(),
  client_secret: z.string(),
  customer: z.string().nullable(),
  metadata: z.record(z.string()),
});

const paymentIntent = await ctx.integrations.stripe.apiRequest(
  {
    method: "POST",
    path: "/v1/payment_intents",
    body: {
      amount: 2000, // Amount in cents ($20.00)
      currency: "usd",
      customer: customerId,
      automatic_payment_methods: {
        enabled: true,
      },
      metadata: {
        order_id: "order_123",
      },
    },
  },
  { response: PaymentIntentSchema },
);

// Return client_secret to frontend for Stripe.js
console.log(`Client secret: ${paymentIntent.client_secret}`);
```

### List Charges

```typescript
const ChargeSchema = z.object({
  id: z.string(),
  object: z.literal("charge"),
  amount: z.number(),
  currency: z.string(),
  status: z.string(), // succeeded, pending, failed
  customer: z.string().nullable(),
  created: z.number(),
  paid: z.boolean(),
  refunded: z.boolean(),
});

const ListChargesSchema = z.object({
  object: z.literal("list"),
  data: z.array(ChargeSchema),
  has_more: z.boolean(),
  url: z.string(),
});

const charges = await ctx.integrations.stripe.apiRequest(
  {
    method: "GET",
    path: "/v1/charges",
    params: {
      customer: customerId,
      limit: 10,
    },
  },
  { response: ListChargesSchema },
);

charges.data.forEach((charge) => {
  console.log(`${charge.id}: $${charge.amount / 100} - ${charge.status}`);
});
```

### Create a Subscription

```typescript
const SubscriptionSchema = z.object({
  id: z.string(),
  object: z.literal("subscription"),
  customer: z.string(),
  status: z.string(), // active, past_due, canceled, etc.
  current_period_start: z.number(),
  current_period_end: z.number(),
  items: z.object({
    data: z.array(
      z.object({
        id: z.string(),
        price: z.object({ id: z.string() }),
      }),
    ),
  }),
});

const subscription = await ctx.integrations.stripe.apiRequest(
  {
    method: "POST",
    path: "/v1/subscriptions",
    body: {
      customer: customerId,
      items: [{ price: "price_abc123" }],
      payment_behavior: "default_incomplete",
      expand: ["latest_invoice.payment_intent"],
    },
  },
  { response: SubscriptionSchema },
);
```

### Create a Refund

```typescript
const RefundSchema = z.object({
  id: z.string(),
  object: z.literal("refund"),
  amount: z.number(),
  charge: z.string(),
  status: z.string(), // succeeded, pending, failed, canceled
  created: z.number(),
});

const refund = await ctx.integrations.stripe.apiRequest(
  {
    method: "POST",
    path: "/v1/refunds",
    body: {
      charge: chargeId,
      amount: 1000, // Partial refund of $10.00
      reason: "requested_by_customer",
    },
  },
  { response: RefundSchema },
);
```

### List Products

```typescript
const ProductSchema = z.object({
  id: z.string(),
  object: z.literal("product"),
  name: z.string(),
  description: z.string().nullable(),
  active: z.boolean(),
  metadata: z.record(z.string()),
});

const ListProductsSchema = z.object({
  object: z.literal("list"),
  data: z.array(ProductSchema),
  has_more: z.boolean(),
});

const products = await ctx.integrations.stripe.apiRequest(
  {
    method: "GET",
    path: "/v1/products",
    params: {
      active: true,
      limit: 25,
    },
  },
  { response: ListProductsSchema },
);
```

### Create Invoice

```typescript
const InvoiceSchema = z.object({
  id: z.string(),
  object: z.literal("invoice"),
  customer: z.string(),
  status: z.string(), // draft, open, paid, uncollectible, void
  total: z.number(),
  amount_due: z.number(),
  hosted_invoice_url: z.string().nullable(),
});

const invoice = await ctx.integrations.stripe.apiRequest(
  {
    method: "POST",
    path: "/v1/invoices",
    body: {
      customer: customerId,
      collection_method: "send_invoice",
      days_until_due: 30,
    },
  },
  { response: InvoiceSchema },
);

// Add line items
await ctx.integrations.stripe.apiRequest(
  {
    method: "POST",
    path: "/v1/invoiceitems",
    body: {
      customer: customerId,
      invoice: invoice.id,
      amount: 5000,
      currency: "usd",
      description: "Consulting services",
    },
  },
  { response: z.object({ id: z.string() }) },
);

// Finalize and send
await ctx.integrations.stripe.apiRequest(
  {
    method: "POST",
    path: `/v1/invoices/${invoice.id}/finalize`,
  },
  { response: InvoiceSchema },
);
```

## Trace Metadata

All methods accept an optional `metadata` parameter as the last argument for diagnostics labeling. See the [root SDK README](../../../README.md#trace-metadata) for details.

## Common Pitfalls

### No Specialized Methods

```typescript
// WRONG - These methods do not exist
await stripe.createCustomer({ ... });
await stripe.createPaymentIntent({ ... });

// CORRECT - Use apiRequest
await ctx.integrations.stripe.apiRequest(
  { method: "POST", path: "/v1/customers", body: { ... } },
  { response: CustomerSchema }
);
```

### API Versioning

Stripe has API versions. Set the header for consistent behavior:

```typescript
const result = await ctx.integrations.stripe.apiRequest(
  {
    method: "POST",
    path: "/v1/customers",
    body: { ... },
    headers: {
      "Stripe-Version": "2023-10-16", // Pin to specific version
    },
  },
  { response: CustomerSchema }
);
```

### Amounts in Cents

All amounts are in the smallest currency unit (cents for USD):

```typescript
// WRONG - Dollar amount
const body = { amount: 20.0 }; // $20.00

// CORRECT - Cents
const body = { amount: 2000 }; // $20.00 = 2000 cents
```

### Expandable Objects

Many Stripe responses include expandable objects:

```typescript
// Default - Returns only ID
const response = { customer: "cus_123" };

// With expand - Returns full object
const body = {
  expand: ["customer", "latest_invoice.payment_intent"],
};
// Response: { customer: { id: "cus_123", email: "...", ... } }
```

### Idempotency Keys

For safe retries, use idempotency keys:

```typescript
const result = await ctx.integrations.stripe.apiRequest(
  {
    method: "POST",
    path: "/v1/payment_intents",
    body: { ... },
    headers: {
      "Idempotency-Key": "unique-key-for-this-request",
    },
  },
  { response: PaymentIntentSchema }
);
```

### Pagination

Stripe uses cursor-based pagination:

```typescript
async function getAllCustomers(stripe: StripeClient) {
  const allCustomers: Customer[] = [];
  let startingAfter: string | undefined;

  while (true) {
    const result = await ctx.integrations.stripe.apiRequest(
      {
        method: "GET",
        path: "/v1/customers",
        params: {
          limit: 100,
          ...(startingAfter && { starting_after: startingAfter }),
        },
      },
      { response: ListCustomersSchema },
    );

    allCustomers.push(...result.data);
    if (!result.has_more) break;
    startingAfter = result.data[result.data.length - 1].id;
  }

  return allCustomers;
}
```

### Metadata

Metadata values must be strings:

```typescript
// WRONG - Non-string values
const metadata = {
  count: 5,
  active: true,
};

// CORRECT - String values only
const metadata = {
  count: "5",
  active: "true",
};
```

## Error Handling

```typescript
import { RestApiValidationError } from "@superblocksteam/sdk-api";

try {
  const result = await ctx.integrations.stripe.apiRequest(
    { method: "POST", path: "/v1/customers", body: { ... } },
    { response: CustomerSchema }
  );
} catch (error) {
  if (error instanceof RestApiValidationError) {
    console.error("Validation failed:", error.details.zodError);
  }
}
```

## API Reference

- [Stripe API Documentation](https://stripe.com/docs/api)
- [Customers](https://stripe.com/docs/api/customers)
- [Payment Intents](https://stripe.com/docs/api/payment_intents)
- [Subscriptions](https://stripe.com/docs/api/subscriptions)
- [Error Handling](https://stripe.com/docs/api/errors)
