Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
8756984
fix: correctly show credits remaining from new API
seanmcgary Apr 22, 2026
7ba8d1a
feat(sdk): add PaymentMethod and CreditPurchaseResponse types
seanmcgary Apr 23, 2026
59d4812
feat(sdk): add getPaymentMethods and purchaseCredits to BillingApiClient
seanmcgary Apr 23, 2026
89fc8b3
feat(sdk): expose getPaymentMethods and purchaseCredits on BillingModule
seanmcgary Apr 23, 2026
72fb9e3
feat(cli): add credit card payment flow to billing top-up
seanmcgary Apr 23, 2026
4407666
test(cli): add credit card flow tests for billing top-up
seanmcgary Apr 23, 2026
49f6f2b
execution plan
seanmcgary Apr 29, 2026
562f22a
feat: add list-cards subcommand
seanmcgary May 5, 2026
8667796
feat: add Base Sepolia chain config for USDC credit purchases
seanmcgary May 6, 2026
72ee53d
feat: add BillingChain type and hasBaseSupport to billing module inte…
seanmcgary May 6, 2026
af72ae9
feat: implement chain-aware getTopUpInfo and topUp for Base support
seanmcgary May 6, 2026
ed1d78d
feat: export BillingChain type and BASE_SEPOLIA_CHAIN_ID from SDK
seanmcgary May 6, 2026
68fcd6b
feat: add chain selection prompt for USDC top-up (Ethereum/Base)
seanmcgary May 6, 2026
f4e61a0
test: add chain selection tests for Base USDC top-up
seanmcgary May 6, 2026
1f55c5b
doc: execution plan
seanmcgary May 6, 2026
34e33a9
feat(sdk): add admin and coupon API methods to BillingApiClient
seanmcgary May 18, 2026
c2cebc9
feat(sdk): add AdminModule and redeemCoupon to BillingModule
seanmcgary May 18, 2026
2ca4971
feat(cli): add billing redeem-coupon command
seanmcgary May 18, 2026
b937c51
feat(cli): add admin coupons commands (create, list, get, deactivate,…
seanmcgary May 18, 2026
d44558f
feat(cli): add admin admins commands (add, remove, list)
seanmcgary May 18, 2026
3c10895
feat(cli): register admin topics in oclif config
seanmcgary May 18, 2026
bec561a
chore: execution plan
seanmcgary May 18, 2026
7451666
feat: admin subcommands, coupon redemption (#156)
seanmcgary May 20, 2026
6467903
fix(cli): spread commonFlags on billing cancel (RND-567)
mpjunior92 May 20, 2026
a05785b
fix(cli): bound upgrade watch with timeout and progress (RND-568)
mpjunior92 May 20, 2026
1213d5c
fix(cli): bound deploy watch with timeout and heartbeat (RND-569)
mpjunior92 May 20, 2026
1159ede
fix(sdk): sync AppController ABI to v1.5.x Release (containerPolicy)
mpjunior92 Jun 3, 2026
b462d3a
fix(sdk): select AppController Release ABI per environment (v1.4 vs v…
mpjunior92 Jun 3, 2026
d7e9a4f
fix(sdk): per-environment AppController Release ABI (v1.4 + v1.5) — u…
mpjunior92 Jun 3, 2026
d0a814a
merge: fold RND-568 (bound upgrade watch) into deploy-watch branch
mpjunior92 Jun 3, 2026
2b3a566
merge: fold RND-567 (billing cancel commonFlags) into deploy-watch br…
mpjunior92 Jun 3, 2026
3835361
fix: dedupe shared watch-timeout helpers after folding RND-568
mpjunior92 Jun 3, 2026
d19d74a
fix(cli): default optional prompts in non-interactive deploy/upgrade …
mpjunior92 Jun 3, 2026
2552ba2
feat(cli): non-interactive detection + all-at-once missing-input help…
mpjunior92 Jun 3, 2026
02f6260
feat(cli): --non-interactive flag, instance-type default, mainnet-alp…
mpjunior92 Jun 3, 2026
80f1c51
feat(cli): wire non-interactive deploy precheck + instance-type defau…
mpjunior92 Jun 3, 2026
e518d04
feat(cli): wire non-interactive upgrade precheck + pinned-type reuse …
mpjunior92 Jun 3, 2026
39e20d1
fix(cli): version-check hook no-ops in non-interactive mode (RND-589)
mpjunior92 Jun 3, 2026
787a6fc
test(cli): version-check hook simulates TTY; cover non-interactive no…
mpjunior92 Jun 3, 2026
ca9a16d
style(cli): prettier formatting for RND-589 changes
mpjunior92 Jun 3, 2026
0e2fae6
test(cli): lock in mainnet-alpha prod / sepolia-dev dev env default (…
mpjunior92 Jun 3, 2026
f9d187a
merge: fold PR #162 (RND-567/568/569 watch timeouts + billing flags) …
mpjunior92 Jun 3, 2026
7a56475
feat(sdk): retry 502/503/504 in requestWithRetry, not just 429 (RND-592)
mpjunior92 Jun 3, 2026
41bcc65
feat(cli): add 'compute app status [--wait] [--json]' (RND-592)
mpjunior92 Jun 3, 2026
cbb0504
docs(cli): agent skill uses 'app status --wait' instead of polling 'a…
mpjunior92 Jun 3, 2026
b22ef9a
test(cli): align promptUseVerifiableBuild tests with merged non-TTY c…
mpjunior92 Jun 3, 2026
2ca2677
fix(cli): confirmWithDefault returns the default in non-TTY instead o…
mpjunior92 Jun 3, 2026
9df2a11
feat(cli): distinct exit codes for deploy/upgrade failure stages (RND…
mpjunior92 Jun 3, 2026
55cd43c
docs(cli): document deploy/upgrade exit codes 2/3/4 in agent skill (R…
mpjunior92 Jun 3, 2026
aa937d3
fix(cli,sdk): harden amd64 enforcement — close assume-amd64 hole + pr…
mpjunior92 Jun 4, 2026
0e48c7e
feat(cli,sdk): block deploy/upgrade when wallet ETH < estimated gas (…
mpjunior92 Jun 4, 2026
09286b1
chore: remove Linear ticket IDs from source code
mpjunior92 Jun 4, 2026
a8ccb24
refactor(cli): add explicit type hints to deploy/upgrade let declarat…
mpjunior92 Jun 4, 2026
b20f026
refactor(cli): inject non-interactive decision into prompt helpers
mpjunior92 Jun 4, 2026
fdcd0f7
fix(cli): surface wallet-ETH read failure in billing status
mpjunior92 Jun 4, 2026
4f95fa4
refactor(cli): drop "Interactive" suffix from dual-mode prompt helpers
mpjunior92 Jun 4, 2026
7323563
refactor(cli): extract stageFailure() to map deploy/upgrade stage -> …
mpjunior92 Jun 4, 2026
4e27c37
fix(cli): correct exit-code classification for gas + dockerfile cases…
mpjunior92 Jun 4, 2026
dde9727
fix(sdk): unify amd64 remediation text with the CLI prebuilt-image path
mpjunior92 Jun 5, 2026
056f996
fix(cli): app status --wait returns immediately on settled statuses; …
mpjunior92 Jun 9, 2026
2365d57
fix(cli): agent-friendly deploy/upgrade — non-interactive defaults, a…
mpjunior92 Jun 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
770 changes: 770 additions & 0 deletions docs/superpowers/plans/2026-04-23-top-up-credit-card.md

Large diffs are not rendered by default.

670 changes: 670 additions & 0 deletions docs/superpowers/plans/2026-05-06-base-chain-usdc-topup.md

Large diffs are not rendered by default.

934 changes: 934 additions & 0 deletions docs/superpowers/plans/2026-05-17-admin-and-coupon-commands.md

Large diffs are not rendered by default.

211 changes: 211 additions & 0 deletions docs/superpowers/specs/2026-04-23-top-up-credit-card-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
# Top-Up Credit Card Support

Add credit card purchasing to `ecloud billing top-up` alongside the existing USDC on-chain flow.

## Motivation

We're moving to a credit-based burndown system. Users need to purchase credits, and not everyone wants to use USDC on-chain. Adding credit card support via Stripe lets users top up with a familiar payment method.

## API Routes

Both routes live on the billing API server (`ECLOUD_BILLING_API_URL`), use the same EIP-712 signature auth as existing routes (`Authorization: Bearer <sig>`, `X-Account`, `X-Expiry`).

### GET /v1/payment-methods

Returns saved payment methods for the authenticated wallet.

Request: no body, auth required.

Response:
```json
{
"paymentMethods": [
{
"id": "029641fc-3e5c-11f1-986c-5601121cbf6d",
"stripePaymentMethodId": "pm_1ABC123...",
"createdAt": "2026-04-20T15:00:00Z"
}
]
}
```

### POST /v1/credits/purchase

Two modes depending on whether `paymentMethodId` is provided.

**Direct charge (card on file):**

Request:
```json
{
"amountCents": 5000,
"paymentMethodId": "029641fc-3e5c-11f1-986c-5601121cbf6d"
}
```

Response:
```json
{
"purchaseId": "a1b2c3d4-5e6f-11f1-986c-5601121cbf6d",
"amountCents": "5000"
}
```

**Checkout session (no card on file):**

Request:
```json
{
"amountCents": 5000
}
```

Response:
```json
{
"checkoutSessionId": "cs_test_abc123...",
"checkoutUrl": "https://checkout.stripe.com/c/pay/cs_test_abc123...",
"amountCents": "5000"
}
```

Minimum `amountCents`: 500 ($5.00).

## Design

### SDK: New methods on `BillingApiClient`

File: `packages/sdk/src/client/common/utils/billingapi.ts`

Add two methods to the existing `BillingApiClient` class:

```typescript
async getPaymentMethods(): Promise<PaymentMethodsResponse>
```
- `GET ${billingApiServerURL}/v1/payment-methods`
- Uses `makeAuthenticatedRequest` with a dummy productId (e.g. `"compute"`) for signature generation since the auth scheme requires a product field.

```typescript
async purchaseCredits(amountCents: number, paymentMethodId?: string): Promise<CreditPurchaseResponse>
```
- `POST ${billingApiServerURL}/v1/credits/purchase`
- Body: `{ amountCents }` or `{ amountCents, paymentMethodId }` depending on whether a payment method is provided.
- Uses `makeAuthenticatedRequest`.

### SDK: New types

File: `packages/sdk/src/client/common/types/index.ts`

```typescript
export interface PaymentMethod {
id: string;
stripePaymentMethodId: string;
createdAt: string;
}

export interface PaymentMethodsResponse {
paymentMethods: PaymentMethod[];
}

export interface CreditPurchaseResponse {
purchaseId?: string;
checkoutSessionId?: string;
checkoutUrl?: string;
amountCents: string;
}
```

`CreditPurchaseResponse` is a union-style interface: a direct charge returns `purchaseId` without checkout fields; a checkout session returns `checkoutSessionId` + `checkoutUrl` without `purchaseId`.

### SDK: Export new methods from billing module

File: `packages/sdk/src/client/modules/billing/index.ts`

Expose the two new `BillingApiClient` methods through the `BillingModule` interface:

```typescript
export interface BillingModule {
// ... existing methods ...
getPaymentMethods: () => Promise<PaymentMethodsResponse>;
purchaseCredits: (amountCents: number, paymentMethodId?: string) => Promise<CreditPurchaseResponse>;
}
```

Wire them to `billingApi.getPaymentMethods()` and `billingApi.purchaseCredits()` in `createBillingModule`.

### CLI: Modified `top-up.ts` command

File: `packages/cli/src/commands/billing/top-up.ts`

#### New flag

```
--method usdc | card (optional, prompts if omitted)
```

#### Updated flow

1. Show wallet address and current credit balance (unchanged).
2. **Payment method selection:**
- If `--method usdc` -> go to USDC path.
- If `--method card` -> go to credit card path.
- If no flag -> prompt user to choose between "USDC (on-chain)" and "Credit card".
3. **USDC path:** Unchanged from current implementation (steps 2-5 in existing code).
4. **Credit card path:**
a. Prompt for dollar amount (whole dollars, minimum $5). Skipped if `--amount` flag is provided.
b. Convert to cents: `amountCents = dollars * 100`.
c. Call `billing.getPaymentMethods()`.
d. If payment methods exist:
- Show: "Use card on file (pm_...1ABC)?" with yes/no prompt.
- If yes: call `billing.purchaseCredits(amountCents, paymentMethod.id)`. This returns `{ purchaseId, amountCents }`. Proceed to poll for credits.
- If no: call `billing.purchaseCredits(amountCents)` without payment method ID. This returns a checkout URL. Open in browser with `open`. Proceed to poll for credits.
e. If no payment methods: call `billing.purchaseCredits(amountCents)` (no payment method ID). Open checkout URL in browser. Proceed to poll for credits.
5. **Credit polling:** Same polling loop as today — poll `billing.getStatus()` until `remainingCredits` increases or timeout (3 minutes).

#### Amount validation (credit card path)

- Must be a whole dollar amount (integer).
- Minimum: $5 (500 cents).
- No maximum (Stripe handles limits).

#### Non-interactive support

For CI/scripting, all prompts can be skipped via flags:
- `--method card --amount 50` skips the method and amount prompts.
- Without a card on file, the checkout URL is printed to stdout (the `open` call will be attempted but the URL is always logged).
- With a card on file and no flag to choose it, the command will still prompt. Full non-interactive card selection is out of scope for this change.

### CLI: Update command description and examples

Update `static description` and `static examples` to reflect the new credit card option.

### Tests

File: `packages/cli/src/commands/billing/__tests__/top-up.test.ts`

Add test cases:
- **Credit card, card on file, user accepts:** mock `getPaymentMethods` returning one card, mock `purchaseCredits` returning `{ purchaseId, amountCents }`, verify no browser open, verify credit polling.
- **Credit card, card on file, user declines (wants new card):** mock `purchaseCredits` returning `{ checkoutUrl, ... }`, verify `open` is called with checkout URL.
- **Credit card, no card on file:** mock `getPaymentMethods` returning empty array, mock `purchaseCredits` returning checkout URL, verify `open` is called.
- **`--method card --amount 50` skips prompts:** verify `select` and `input` are not called.
- **Amount below $5 minimum:** verify validation error.
- **Existing USDC tests remain unchanged.**

Mock `billing.getPaymentMethods` and `billing.purchaseCredits` on the same `mockBilling` object used by existing tests. Mock `open` as already done in `subscribe.test.ts`.

## Files changed

| File | Change |
|------|--------|
| `packages/sdk/src/client/common/types/index.ts` | Add `PaymentMethod`, `PaymentMethodsResponse`, `CreditPurchaseResponse` |
| `packages/sdk/src/client/common/utils/billingapi.ts` | Add `getPaymentMethods()`, `purchaseCredits()` |
| `packages/sdk/src/client/modules/billing/index.ts` | Expose new methods on `BillingModule` |
| `packages/cli/src/commands/billing/top-up.ts` | Add `--method` flag, credit card flow, method selection prompt |
| `packages/cli/src/commands/billing/__tests__/top-up.test.ts` | Add credit card test cases |

## Out of scope

- Listing/managing saved payment methods (separate command later).
- Deleting payment methods.
- Full non-interactive card selection (auto-picking a saved card without prompting).
- Changing the subscribe command flow.
9 changes: 9 additions & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@
"compute:env": {
"hidden": true,
"description": "Manage deployment environment [alias: env]"
},
"admin": {
"description": "Admin operations (requires admin privileges)"
},
"admin:coupons": {
"description": "Manage coupons"
},
"admin:admins": {
"description": "Manage admin users"
}
}
},
Expand Down
Loading