From f9757c4cd337c2c70a666685571f0e20a6e92b66 Mon Sep 17 00:00:00 2001 From: hikaru Date: Sat, 9 May 2026 20:51:07 +0900 Subject: [PATCH] feat: add coinpay CLI wrapper adapter --- packages/targets/payment-coinpay/package.json | 32 ++++++ .../targets/payment-coinpay/src/index.test.ts | 7 ++ packages/targets/payment-coinpay/src/index.ts | 104 ++++++++++++++++++ .../targets/payment-coinpay/tsconfig.json | 5 + 4 files changed, 148 insertions(+) create mode 100644 packages/targets/payment-coinpay/package.json create mode 100644 packages/targets/payment-coinpay/src/index.test.ts create mode 100644 packages/targets/payment-coinpay/src/index.ts create mode 100644 packages/targets/payment-coinpay/tsconfig.json diff --git a/packages/targets/payment-coinpay/package.json b/packages/targets/payment-coinpay/package.json new file mode 100644 index 0000000..7f66aae --- /dev/null +++ b/packages/targets/payment-coinpay/package.json @@ -0,0 +1,32 @@ +{ + "name": "@profullstack/sh1pt-target-payment-coinpay", + "version": "0.1.11", + "type": "module", + "main": "./src/index.ts", + "scripts": { + "build": "tsc -p tsconfig.json", + "typecheck": "tsc -p tsconfig.json --noEmit", + "prepublishOnly": "pnpm build" + }, + "dependencies": { + "@profullstack/sh1pt-core": "workspace:*" + }, + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/profullstack/sh1pt.git", + "directory": "packages/targets/payment-coinpay" + }, + "files": ["dist"], + "publishConfig": { + "access": "public", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + } + } +} diff --git a/packages/targets/payment-coinpay/src/index.test.ts b/packages/targets/payment-coinpay/src/index.test.ts new file mode 100644 index 0000000..1dd0cbb --- /dev/null +++ b/packages/targets/payment-coinpay/src/index.test.ts @@ -0,0 +1,7 @@ +import { contractTestTarget } from '@profullstack/sh1pt-core/testing'; +import target from './index.js'; + +contractTestTarget(target, { + sampleConfig: { command: 'create', args: { amount: 100, blockchain: 'BTC' }, businessId: 'biz_test' }, + requiredSecrets: ['COINPAY_API_KEY'], +}); diff --git a/packages/targets/payment-coinpay/src/index.ts b/packages/targets/payment-coinpay/src/index.ts new file mode 100644 index 0000000..ec178a3 --- /dev/null +++ b/packages/targets/payment-coinpay/src/index.ts @@ -0,0 +1,104 @@ +import { defineTarget, setupGuide, exec } from '@profullstack/sh1pt-core'; + +interface Config { + command?: 'create' | 'get' | 'list' | 'rates'; + args?: Record; + businessId?: string; + description?: string; +} + +export default defineTarget({ + id: 'payment-coinpay', + kind: 'payment', + label: 'CoinPay (CLI wrapper)', + + async build(ctx, config) { + ctx.log('coinpay: verifying CLI availability'); + + // 1. Auto-install CLI if missing + try { + await exec('coinpay', ['--version'], { log: ctx.log, throwOnNonZero: false }); + } catch { + ctx.log('CLI not found — installing globally'); + await exec('npm', ['install', '-g', '@profullstack/coinpay'], { + log: ctx.log, throwOnNonZero: true, + }); + } + + // 2. Delegate API key setup to the wrapped CLI + try { + const { stdout } = await exec('coinpay', ['config', 'get-key'], { + log: ctx.log, throwOnNonZero: false, + }); + if (!stdout.trim()) { + const key = ctx.secret('COINPAY_API_KEY'); + if (key) { + await exec('coinpay', ['config', 'set-key', key], { + log: ctx.log, throwOnNonZero: true, + }); + } else { + throw new Error('COINPAY_API_KEY not set. Run: sh1pt secret set COINPAY_API_KEY '); + } + } + } catch (e) { + throw new Error(`Config check failed: ${e instanceof Error ? e.message : String(e)}`); + } + + return { artifact: 'ready' }; + }, + + async ship(ctx, config) { + const cmd = config.command ?? 'create'; + if (ctx.dryRun) return { id: 'dry-run', meta: { command: cmd } }; + + switch (cmd) { + case 'create': { + const args = ['payment', 'create']; + const bizId = config.businessId ?? (config.args?.businessId as string); + if (bizId) args.push('--business-id', bizId); + if (config.args?.amount) args.push('--amount', String(config.args.amount)); + if (config.args?.blockchain) args.push('--blockchain', String(config.args.blockchain)); + if (config.description) args.push('--description', config.description); + + const { stdout } = await exec('coinpay', args, { log: ctx.log, throwOnNonZero: true }); + return { id: `cp_${Date.now()}`, meta: { raw: stdout.trim() } }; + } + + case 'get': { + const paymentId = config.args?.paymentId as string; + if (!paymentId) throw new Error('paymentId required for get command'); + const { stdout } = await exec('coinpay', ['payment', 'get', paymentId], { log: ctx.log }); + return { id: paymentId, meta: { raw: stdout.trim() } }; + } + + case 'list': { + const args = ['payment', 'list']; + const bizId = config.businessId ?? (config.args?.businessId as string); + if (bizId) args.push('--business-id', bizId); + const { stdout } = await exec('coinpay', args, { log: ctx.log }); + return { id: `list-${Date.now()}`, meta: { raw: stdout.trim() } }; + } + + case 'rates': { + const coin = (config.args?.coin as string) ?? 'BTC'; + const fiat = (config.args?.fiat as string) ?? 'USD'; + const { stdout } = await exec('coinpay', ['rates', 'get', coin, '--fiat', fiat], { log: ctx.log }); + return { id: `${coin}-${fiat}`, meta: { raw: stdout.trim() } }; + } + + default: + throw new Error(`Unknown command: ${cmd}`); + } + }, + + setup: setupGuide({ + label: 'CoinPay CLI', + vendorDocUrl: 'https://coinpayportal.com/docs/sdk#cli', + steps: [ + 'Install the CLI: npm install -g @profullstack/coinpay', + 'Get API key from dashboard → API Keys', + 'Run: coinpay config set-key ', + 'Or: sh1pt secret set COINPAY_API_KEY ', + ], + }), +}); diff --git a/packages/targets/payment-coinpay/tsconfig.json b/packages/targets/payment-coinpay/tsconfig.json new file mode 100644 index 0000000..fb3a84d --- /dev/null +++ b/packages/targets/payment-coinpay/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { "outDir": "./dist", "rootDir": "./src" }, + "include": ["src"] +}