Skip to main content
ClaudeWave
Skill3.7k repo starsupdated 1mo ago

pinme-uniwebpay

Use when generating, modifying, or reviewing PinMe Worker (Cloudflare Worker TypeScript) code that accepts payments through UniwebPay — payment links, products/prices, checkout sessions, payment status reads, refunds, subscriptions, or handling UniwebPay webhooks with @uniwebpay/sdk in a PinMe project.

Install in Claude Code
Copy
git clone --depth 1 https://github.com/glitternetwork/pinme /tmp/pinme-uniwebpay && cp -r /tmp/pinme-uniwebpay/skills/pinme-uniwebpay ~/.claude/skills/pinme-uniwebpay
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

# PinMe UniwebPay Payment Integration

Guides writing payment services in a PinMe Worker (Cloudflare Worker TypeScript) that call UniwebPay directly through `@uniwebpay/sdk`.

Core model: PinMe provisions the UniwebPay wallet and keys per **PinMe user** (not per project) and injects `UNIWEB_*` environment bindings at Worker deploy time; Worker code calls UniwebPay **directly with the SDK** — it does not go through PinMe payment proxy routes, and it must not call the legacy VibeCash APIs.

## Environment Binding Contract

```typescript
export interface Env {
  UNIWEB_SECRET: string;           // PinMe-provisioned sk_server_ key (server-side only)
  UNIWEB_WEBHOOK_SECRET?: string;  // wallet-level whsec_, used to verify webhook signatures
  UNIWEB_API_URL?: string;         // UniwebPay API endpoint override (default https://apiskill.uniwebpay.com)
  UNIWEB_PAY_URL?: string;         // UniwebPay checkout host override (default https://skill.uniwebpay.com)
  UNIWEB_WALLET_ID?: string;       // user-level wallet id (wal_), diagnostics/reconciliation only
  WORKER_URL?: string;             // this project's public URL: https://{projectName}.{platform api domain}
  PROJECT_NAME?: string;           // PinMe project name
  DB?: D1Database;                 // project D1 (if enabled)
}
```

Injection rules (metadata is rebuilt server-side by PinMe at deploy time; client-supplied bindings are ignored):

- The `UNIWEB_*` bindings are injected only after the user's UniwebPay credentials have been provisioned. Newly created projects are provisioned automatically and get them immediately; **existing projects must be redeployed after enabling UniwebPay or rotating keys** to pick up new bindings.
- `WORKER_URL`, `PROJECT_NAME`, `API_KEY`, `DB` and other base bindings are injected on every deploy, independent of UniwebPay.
- All projects owned by the same PinMe user share one wallet, one `sk_server_`, and one `whsec_`.
- PinMe never gives the full wallet secret (`sk_live_`) to a Worker. Do not ask the user for it, and do not put it in code, `wrangler.toml`, `.dev.vars`, responses, logs, D1, or frontend bundles.
- If `UNIWEB_SECRET` is missing at runtime, the user has not enabled UniwebPay or has not redeployed — tell the user to enable it and redeploy; never fabricate a value.

## SDK Client

Always instantiate on the server side (the Worker); the SDK throws when run in a browser:

```typescript
import Uniweb from "@uniwebpay/sdk";

function uniwebClient(env: Env): Uniweb {
  return new Uniweb(env.UNIWEB_SECRET, {
    baseUrl: env.UNIWEB_API_URL,
    payUrl: env.UNIWEB_PAY_URL,
  });
}
```

- The constructor's first positional argument is the key (must have an `sk_server_` or `sk_live_` prefix); the second is optional options: `{ baseUrl?, payUrl?, timeout? (default 30s), maxRetries? (default 2) }`.
- The SDK auto-retries only GET/DELETE on 429/5xx; POST/PATCH are never retried (avoids duplicate charges).
- Install `@uniwebpay/sdk` only when Worker code imports it; pick the package manager from the project's existing lockfile.

## Choosing an Integration Path

| Scenario | Approach | Returns |
|------|------|------|
| Fixed-amount one-time collection | `uniweb.links.create(...)` | Permanent, reusable `/p/` link (one-time payments only) |
| Stable product catalog | `products.create` + `prices.create` once, store the `priceId` | Price carries a permanent `paymentUrl` (`/buy/` link) |
| Dynamic cart/order | Reuse or create a price, then `uniweb.checkout.create(...)` | `session.url` — **one-time, expires in 24 hours** |
| Subscriptions | Recurring price + `checkout.create({ mode: "subscription" })` or `subscriptions.create` | Same as above |
| Server-side payment status checks | `payments.get / list` | Server routes only |

Amounts are always **integer minor units** (cents). Default currency convention is `SGD` unless the app has a stronger existing convention. Do not create a new product/price on every page view — create stable catalog items once and persist the `priceId`.

## Payment Methods and Currency Rules

| Method | Supported currencies |
|------|---------|
| `card` | SGD, USD, EUR, GBP, JPY, CNY, HKD, AUD, MYR, THB (minimum 10 minor units) |
| `wechat` | SGD only |
| `alipay` | SGD only |
| `paynow` | SGD only |

- The QR methods (wechat/alipay/paynow) **all support SGD only** — never generate "CNY via WeChat/Alipay" code.
- Subscriptions (recurring / `mode: "subscription"`) use `card` only.
- When `paymentMethodTypes` is omitted, the server picks sensible defaults for the currency; when passed explicitly, validate user input against the table above first.

## SDK Surface Quick Reference

The surface below is verified against source. All parameter fields are camelCase (`priceId`, `webhookUrl`, `startingAfter`, …); the SDK handles wire-level conversion itself. `list()` returns `{ data: T[], hasMore: boolean }`; `listAll()` is an async generator available on products, prices, payments, customers, subscriptions, and links (not on checkout or refunds).

Products (`webhookUrl` is the per-product callback override):

```typescript
await uniweb.products.create({ name, description?, webhookUrl?, metadata? });
await uniweb.products.list({ limit?, startingAfter? });
await uniweb.products.get(productId);
await uniweb.products.update(productId, { name?, description?, webhookUrl?, active?, metadata? });
await uniweb.products.del(productId);
for await (const product of uniweb.products.listAll()) {}
```

Prices (the returned price carries a permanent `paymentUrl`; `deactivate` takes it off sale):

```typescript
await uniweb.prices.create({
  productId,
  amount,        // integer minor units
  currency,      // e.g. "SGD"
  type,          // "one_time" | "recurring"
  interval?,     // "day" | "week" | "month" | "year"; recurring only
  intervalCount?,
  trialPeriodDays?,
  metadata?,
});
await uniweb.prices.list({ productId?, limit?, startingAfter? });
await uniweb.prices.get(priceId);
await uniweb.prices.update(p
pinme-authSkill

Use when a PinMe project (Worker TypeScript) needs to integrate user authentication — creating email/password users, verifying id_tokens, querying user info, or listing users via Identity Platform auth proxy APIs.

pinme-emailSkill

Use this skill when a PinMe project (Worker TypeScript) needs to integrate email sending (send_email). Guides AI to generate correct Worker TS code.

pinme-llmSkill

Use this skill when a PinMe project (Worker TypeScript) needs to call OpenRouter-backed LLM APIs, including models, chat/completions, streaming, or OpenRouter web search. Guides AI to generate correct Worker TS code.

pinme-shareSkill

Use this skill when the user wants to share, publish, or upload a static result through PinMe, especially by generating a static HTML share page for a PinMe project link, deployed full-stack app, Codex conversation summary, report, file, demo, or any 分享/发布/上传分享页 request that should end with `pinme upload`.

pinmeSkill

Use this skill when the user mentions "pinme", or needs to upload files, store to IPFS, create/publish/deploy websites or full-stack services (including frontend pages, backend APIs, database storage, email sending, etc.), or any feature requiring backend database/server support.

pinme-r2Skill

Use when a PinMe Cloudflare Worker needs R2 object storage, including secure file or image upload, streaming download, metadata lookup, deletion, listing, Range requests, or R2+D1 coordination. Guides AI to use PinMe's automatically injected env.R2 binding without R2 credentials or manual Wrangler configuration.