@profullstack/coinpay 0.6.2 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/coinpay.js CHANGED
@@ -227,6 +227,10 @@ ${colors.cyan}Commands:${colors.reset}
227
227
  badge [did] Get embeddable reputation badge URL
228
228
  verify <id> Verify a credential
229
229
  revocations List revoked credentials
230
+ issuer register Register platform issuer (--name, --domain)
231
+ issuer list List your platform issuers
232
+ issuer rotate Rotate issuer API key (--id)
233
+ issuer deactivate Deactivate an issuer (--id)
230
234
 
231
235
  ${colors.bright}webhook${colors.reset}
232
236
  logs <business-id> Get webhook logs
@@ -1863,6 +1867,98 @@ async function handleReputation(subcommand, args, flags) {
1863
1867
  break;
1864
1868
  }
1865
1869
 
1870
+ case 'issuer': {
1871
+ const issuerSubcommand = args[0];
1872
+ const client = createClient();
1873
+
1874
+ switch (issuerSubcommand) {
1875
+ case 'register': {
1876
+ const name = flags.name;
1877
+ const domain = flags.domain;
1878
+ if (!name || !domain) {
1879
+ print.error('Required: --name <name> --domain <domain>');
1880
+ return;
1881
+ }
1882
+ const { registerPlatformIssuer } = await import('../src/reputation.js');
1883
+ const result = await registerPlatformIssuer(client, { name, domain, did: flags.did });
1884
+
1885
+ if (result.success) {
1886
+ print.success(`Issuer registered: ${result.issuer.name}`);
1887
+ print.info(` ID: ${result.issuer.id}`);
1888
+ print.info(` DID: ${result.issuer.did}`);
1889
+ print.info(` Domain: ${result.issuer.domain}`);
1890
+ console.log(`\n ${colors.bright}${colors.yellow}⚠️ API Key (shown only once):${colors.reset}`);
1891
+ console.log(` ${colors.yellow}${result.api_key}${colors.reset}\n`);
1892
+ } else {
1893
+ print.error(result.error || 'Registration failed');
1894
+ }
1895
+ if (flags.json) print.json(result);
1896
+ break;
1897
+ }
1898
+
1899
+ case 'list': {
1900
+ const { listPlatformIssuers } = await import('../src/reputation.js');
1901
+ const result = await listPlatformIssuers(client);
1902
+
1903
+ if (result.success) {
1904
+ print.info(`Platform issuers (${result.issuers.length}):`);
1905
+ for (const iss of result.issuers) {
1906
+ const status = iss.active ? `${colors.green}active${colors.reset}` : `${colors.red}inactive${colors.reset}`;
1907
+ console.log(` ${iss.id} | ${iss.name} | ${iss.domain} | ${status} | key: ${iss.api_key || 'none'}`);
1908
+ }
1909
+ } else {
1910
+ print.error(result.error || 'Failed to list issuers');
1911
+ }
1912
+ if (flags.json) print.json(result);
1913
+ break;
1914
+ }
1915
+
1916
+ case 'rotate': {
1917
+ const id = flags.id || args[1];
1918
+ if (!id) {
1919
+ print.error('Required: --id <issuer-id>');
1920
+ return;
1921
+ }
1922
+ const { rotatePlatformApiKey } = await import('../src/reputation.js');
1923
+ const result = await rotatePlatformApiKey(client, id);
1924
+
1925
+ if (result.success) {
1926
+ print.success(`API key rotated for: ${result.issuer.name}`);
1927
+ console.log(`\n ${colors.bright}${colors.yellow}⚠️ New API Key (shown only once):${colors.reset}`);
1928
+ console.log(` ${colors.yellow}${result.api_key}${colors.reset}\n`);
1929
+ } else {
1930
+ print.error(result.error || 'Rotation failed');
1931
+ }
1932
+ if (flags.json) print.json(result);
1933
+ break;
1934
+ }
1935
+
1936
+ case 'deactivate': {
1937
+ const id = flags.id || args[1];
1938
+ if (!id) {
1939
+ print.error('Required: --id <issuer-id>');
1940
+ return;
1941
+ }
1942
+ const { deactivatePlatformIssuer } = await import('../src/reputation.js');
1943
+ const result = await deactivatePlatformIssuer(client, id);
1944
+
1945
+ if (result.success) {
1946
+ print.success(`Issuer deactivated: ${result.issuer.name}`);
1947
+ } else {
1948
+ print.error(result.error || 'Deactivation failed');
1949
+ }
1950
+ if (flags.json) print.json(result);
1951
+ break;
1952
+ }
1953
+
1954
+ default:
1955
+ print.error(`Unknown issuer command: ${issuerSubcommand}`);
1956
+ print.info('Available: register, list, rotate, deactivate');
1957
+ process.exit(1);
1958
+ }
1959
+ break;
1960
+ }
1961
+
1866
1962
  case 'badge': {
1867
1963
  const did = args[0];
1868
1964
  if (!did) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@profullstack/coinpay",
3
- "version": "0.6.2",
3
+ "version": "0.6.4",
4
4
  "description": "CoinPay SDK & CLI — Accept cryptocurrency payments (BTC, ETH, SOL, POL, BCH, USDC) with wallet and swap support",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -17,6 +17,10 @@
17
17
  "import": "./src/payments.js",
18
18
  "types": "./src/payments.d.ts"
19
19
  },
20
+ "./card-payments": {
21
+ "import": "./src/card-payments.js",
22
+ "types": "./src/card-payments.d.ts"
23
+ },
20
24
  "./webhooks": {
21
25
  "import": "./src/webhooks.js",
22
26
  "types": "./src/webhooks.d.ts"
@@ -0,0 +1,298 @@
1
+ /**
2
+ * CoinPay Card Payments - Convenience functions for Stripe integration
3
+ *
4
+ * This module provides high-level convenience functions for working with
5
+ * card payments through Stripe Connect, similar to the payments.js module.
6
+ */
7
+
8
+ /**
9
+ * Quick card payment creation with sensible defaults
10
+ *
11
+ * @param {import('./client.js').CoinPayClient} client - CoinPay client instance
12
+ * @param {string} businessId - Business ID
13
+ * @param {number} amountUSD - Amount in USD (will be converted to cents)
14
+ * @param {string} description - Payment description
15
+ * @param {Object} [options] - Additional options
16
+ * @param {Object} [options.metadata] - Custom metadata
17
+ * @param {string} [options.successUrl] - Success redirect URL
18
+ * @param {string} [options.cancelUrl] - Cancel redirect URL
19
+ * @param {boolean} [options.escrowMode=false] - Enable escrow mode
20
+ * @returns {Promise<Object>} Payment session with checkout URL
21
+ *
22
+ * @example
23
+ * import { createQuickCardPayment } from '@profullstack/coinpay/card-payments';
24
+ *
25
+ * const payment = await createQuickCardPayment(client, 'business-id', 50, 'Order #123', {
26
+ * metadata: { orderId: '123' },
27
+ * escrowMode: true
28
+ * });
29
+ *
30
+ * // Redirect customer to: payment.checkout_url
31
+ */
32
+ export async function createQuickCardPayment(client, businessId, amountUSD, description, options = {}) {
33
+ const {
34
+ metadata = {},
35
+ successUrl,
36
+ cancelUrl,
37
+ escrowMode = false,
38
+ } = options;
39
+
40
+ // Convert USD to cents
41
+ const amountCents = Math.round(amountUSD * 100);
42
+
43
+ return client.createCardPayment({
44
+ businessId,
45
+ amount: amountCents,
46
+ currency: 'usd',
47
+ description,
48
+ metadata,
49
+ successUrl,
50
+ cancelUrl,
51
+ escrowMode,
52
+ });
53
+ }
54
+
55
+ /**
56
+ * Wait for merchant to complete Stripe onboarding
57
+ *
58
+ * Polls the Stripe account status until onboarding is complete.
59
+ * Useful for integration flows where you need to wait for merchant setup.
60
+ *
61
+ * @param {import('./client.js').CoinPayClient} client - CoinPay client instance
62
+ * @param {string} businessId - Business ID
63
+ * @param {Object} [options] - Polling options
64
+ * @param {number} [options.intervalMs=5000] - Polling interval in ms
65
+ * @param {number} [options.timeoutMs=300000] - Timeout in ms (default: 5 minutes)
66
+ * @param {Function} [options.onStatusChange] - Callback for status changes
67
+ * @returns {Promise<Object>} Final account status when onboarding complete
68
+ *
69
+ * @example
70
+ * const accountStatus = await waitForStripeOnboarding(client, 'business-id', {
71
+ * onStatusChange: (status) => {
72
+ * console.log(`Onboarding status: ${JSON.stringify(status)}`);
73
+ * }
74
+ * });
75
+ */
76
+ export async function waitForStripeOnboarding(client, businessId, options = {}) {
77
+ const {
78
+ intervalMs = 5000,
79
+ timeoutMs = 300000, // 5 minutes
80
+ onStatusChange,
81
+ } = options;
82
+
83
+ const startTime = Date.now();
84
+
85
+ while (Date.now() - startTime < timeoutMs) {
86
+ const status = await client.getStripeAccountStatus(businessId);
87
+
88
+ if (onStatusChange) {
89
+ onStatusChange(status);
90
+ }
91
+
92
+ if (status.onboarding_complete) {
93
+ return status;
94
+ }
95
+
96
+ // Wait before next poll
97
+ await new Promise(resolve => setTimeout(resolve, intervalMs));
98
+ }
99
+
100
+ throw new Error(`Stripe onboarding timeout after ${timeoutMs}ms`);
101
+ }
102
+
103
+ /**
104
+ * Create payment with automatic merchant onboarding check
105
+ *
106
+ * Checks if merchant has completed Stripe onboarding first, and provides
107
+ * helpful error messages if not. Prevents failed payment attempts.
108
+ *
109
+ * @param {import('./client.js').CoinPayClient} client - CoinPay client instance
110
+ * @param {Object} params - Payment parameters (same as createCardPayment)
111
+ * @returns {Promise<Object>} Payment session or onboarding info if incomplete
112
+ *
113
+ * @example
114
+ * const result = await createCardPaymentWithOnboardingCheck(client, {
115
+ * businessId: 'business-id',
116
+ * amount: 5000,
117
+ * description: 'Order #123'
118
+ * });
119
+ *
120
+ * if (result.requires_onboarding) {
121
+ * // Redirect merchant to result.onboarding_url
122
+ * } else {
123
+ * // Redirect customer to result.checkout_url
124
+ * }
125
+ */
126
+ export async function createCardPaymentWithOnboardingCheck(client, params) {
127
+ try {
128
+ // Check onboarding status first
129
+ const status = await client.getStripeAccountStatus(params.businessId);
130
+
131
+ if (!status.onboarding_complete) {
132
+ // Generate onboarding link if needed
133
+ const onboarding = await client.createStripeOnboardingLink(params.businessId);
134
+
135
+ return {
136
+ requires_onboarding: true,
137
+ onboarding_url: onboarding.onboarding_url,
138
+ status: status,
139
+ message: 'Merchant must complete Stripe onboarding before accepting card payments',
140
+ };
141
+ }
142
+
143
+ // Onboarding complete, create payment
144
+ const payment = await client.createCardPayment(params);
145
+ return {
146
+ requires_onboarding: false,
147
+ ...payment,
148
+ };
149
+
150
+ } catch (error) {
151
+ if (error.status === 404) {
152
+ // No Stripe account exists, need onboarding
153
+ const onboarding = await client.createStripeOnboardingLink(params.businessId);
154
+
155
+ return {
156
+ requires_onboarding: true,
157
+ onboarding_url: onboarding.onboarding_url,
158
+ status: null,
159
+ message: 'Merchant needs to complete Stripe onboarding',
160
+ };
161
+ }
162
+ throw error;
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Get payment method support status
168
+ *
169
+ * Returns which payment methods are available for a merchant.
170
+ * Helps with conditional UI rendering.
171
+ *
172
+ * @param {import('./client.js').CoinPayClient} client - CoinPay client instance
173
+ * @param {string} businessId - Business ID
174
+ * @returns {Promise<Object>} Available payment methods
175
+ *
176
+ * @example
177
+ * const support = await getPaymentMethodSupport(client, 'business-id');
178
+ * console.log(support);
179
+ * // {
180
+ * // crypto: true,
181
+ * // cards: true,
182
+ * // escrow: true,
183
+ * // stripe_onboarding_complete: true
184
+ * // }
185
+ */
186
+ export async function getPaymentMethodSupport(client, businessId) {
187
+ try {
188
+ // Check if business exists (crypto payments always work)
189
+ await client.getBusiness(businessId);
190
+
191
+ let cardSupport = false;
192
+ let stripeOnboardingComplete = false;
193
+
194
+ // Check Stripe status
195
+ try {
196
+ const stripeStatus = await client.getStripeAccountStatus(businessId);
197
+ cardSupport = stripeStatus.onboarding_complete;
198
+ stripeOnboardingComplete = stripeStatus.onboarding_complete;
199
+ } catch (error) {
200
+ // No Stripe account = no card support yet
201
+ cardSupport = false;
202
+ stripeOnboardingComplete = false;
203
+ }
204
+
205
+ return {
206
+ crypto: true, // Always available
207
+ cards: cardSupport,
208
+ escrow: cardSupport, // Card escrow requires Stripe
209
+ stripe_onboarding_complete: stripeOnboardingComplete,
210
+ };
211
+
212
+ } catch (error) {
213
+ if (error.status === 404) {
214
+ return {
215
+ crypto: false,
216
+ cards: false,
217
+ escrow: false,
218
+ stripe_onboarding_complete: false,
219
+ error: 'Business not found',
220
+ };
221
+ }
222
+ throw error;
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Format amount for display
228
+ *
229
+ * Converts cents to dollar amount with proper formatting.
230
+ *
231
+ * @param {number} amountCents - Amount in cents
232
+ * @param {string} [currency='USD'] - Currency code
233
+ * @returns {string} Formatted amount string
234
+ *
235
+ * @example
236
+ * formatCardAmount(5000); // "$50.00"
237
+ * formatCardAmount(5050); // "$50.50"
238
+ * formatCardAmount(500, 'EUR'); // "€5.00"
239
+ */
240
+ export function formatCardAmount(amountCents, currency = 'USD') {
241
+ const amount = amountCents / 100;
242
+
243
+ const formatters = {
244
+ USD: (amt) => `$${amt.toFixed(2)}`,
245
+ EUR: (amt) => `€${amt.toFixed(2)}`,
246
+ GBP: (amt) => `£${amt.toFixed(2)}`,
247
+ CAD: (amt) => `C$${amt.toFixed(2)}`,
248
+ };
249
+
250
+ const formatter = formatters[currency.toUpperCase()];
251
+ if (formatter) {
252
+ return formatter(amount);
253
+ }
254
+
255
+ // Fallback for unknown currencies
256
+ return `${amount.toFixed(2)} ${currency.toUpperCase()}`;
257
+ }
258
+
259
+ /**
260
+ * Calculate platform fees for card payments
261
+ *
262
+ * @param {number} amountCents - Payment amount in cents
263
+ * @param {string} tier - Merchant tier ('free' or 'pro')
264
+ * @returns {Object} Fee breakdown
265
+ *
266
+ * @example
267
+ * const fees = calculateCardPaymentFees(5000, 'free');
268
+ * console.log(fees);
269
+ * // {
270
+ * // amount: 5000,
271
+ * // platformFee: 50,
272
+ * // platformFeePercent: 1,
273
+ * // merchantReceives: 4950 // before Stripe fees
274
+ * // }
275
+ */
276
+ export function calculateCardPaymentFees(amountCents, tier = 'free') {
277
+ const platformFeePercent = tier === 'pro' ? 0.5 : 1.0; // 0.5% or 1%
278
+ const platformFeeCents = Math.round(amountCents * (platformFeePercent / 100));
279
+
280
+ return {
281
+ amount: amountCents,
282
+ platformFee: platformFeeCents,
283
+ platformFeePercent,
284
+ merchantReceives: amountCents - platformFeeCents, // Before Stripe processing fees
285
+ };
286
+ }
287
+
288
+ /**
289
+ * Default export with all convenience functions
290
+ */
291
+ export default {
292
+ createQuickCardPayment,
293
+ waitForStripeOnboarding,
294
+ createCardPaymentWithOnboardingCheck,
295
+ getPaymentMethodSupport,
296
+ formatCardAmount,
297
+ calculateCardPaymentFees,
298
+ };
package/src/client.js CHANGED
@@ -522,6 +522,169 @@ export class CoinPayClient {
522
522
  const { authenticateEscrow } = await import('./escrow.js');
523
523
  return authenticateEscrow(this, escrowId, token);
524
524
  }
525
+
526
+ // ── Stripe Card Payment Methods ────────────────────────
527
+
528
+ /**
529
+ * Create a card payment using Stripe
530
+ *
531
+ * Creates a card payment session through Stripe Connect. Returns a checkout URL
532
+ * that customers can use to pay with their credit/debit card.
533
+ *
534
+ * @param {Object} params - Payment parameters
535
+ * @param {string} params.businessId - Business ID (merchant must have completed Stripe onboarding)
536
+ * @param {number} params.amount - Amount in cents (e.g., 5000 = $50.00)
537
+ * @param {string} [params.currency='usd'] - Currency code (usd, eur, etc.)
538
+ * @param {string} [params.description] - Payment description
539
+ * @param {Object} [params.metadata] - Custom metadata
540
+ * @param {string} [params.successUrl] - URL to redirect on successful payment
541
+ * @param {string} [params.cancelUrl] - URL to redirect on payment cancellation
542
+ * @param {boolean} [params.escrowMode=false] - Whether to use escrow mode (holds funds until release)
543
+ * @returns {Promise<Object>} Payment session with checkout URL
544
+ *
545
+ * @example
546
+ * // Create a $50 card payment
547
+ * const payment = await client.createCardPayment({
548
+ * businessId: 'your-business-id',
549
+ * amount: 5000, // $50.00 in cents
550
+ * currency: 'usd',
551
+ * description: 'Order #12345',
552
+ * metadata: { orderId: '12345' },
553
+ * successUrl: 'https://yourdomain.com/success',
554
+ * cancelUrl: 'https://yourdomain.com/cancel'
555
+ * });
556
+ *
557
+ * // Redirect customer to payment.checkout_url
558
+ */
559
+ async createCardPayment({
560
+ businessId,
561
+ amount,
562
+ currency = 'usd',
563
+ description,
564
+ metadata,
565
+ successUrl,
566
+ cancelUrl,
567
+ escrowMode = false
568
+ }) {
569
+ return this.request('/stripe/payments/create', {
570
+ method: 'POST',
571
+ body: JSON.stringify({
572
+ businessId,
573
+ amount,
574
+ currency,
575
+ description,
576
+ metadata,
577
+ successUrl,
578
+ cancelUrl,
579
+ escrowMode
580
+ }),
581
+ });
582
+ }
583
+
584
+ /**
585
+ * Get Stripe account onboarding status
586
+ *
587
+ * Checks whether a merchant has completed Stripe Connect onboarding
588
+ * and is able to accept card payments.
589
+ *
590
+ * @param {string} businessId - Business ID
591
+ * @returns {Promise<Object>} Account status and capabilities
592
+ *
593
+ * @example
594
+ * const status = await client.getStripeAccountStatus('business-id');
595
+ * if (status.onboarding_complete) {
596
+ * console.log('Merchant can accept card payments');
597
+ * }
598
+ */
599
+ async getStripeAccountStatus(businessId) {
600
+ return this.request(`/stripe/connect/status/${businessId}`);
601
+ }
602
+
603
+ /**
604
+ * Create Stripe Connect onboarding link
605
+ *
606
+ * Generates a Stripe Express onboarding link for a merchant.
607
+ * The merchant must complete this onboarding before they can accept card payments.
608
+ *
609
+ * @param {string} businessId - Business ID
610
+ * @param {Object} [params] - Optional parameters
611
+ * @param {string} [params.email] - Merchant email (pre-fills onboarding form)
612
+ * @param {string} [params.country='US'] - Country code (US, CA, GB, etc.)
613
+ * @returns {Promise<Object>} Onboarding link and account ID
614
+ *
615
+ * @example
616
+ * const onboarding = await client.createStripeOnboardingLink('business-id', {
617
+ * email: 'merchant@example.com',
618
+ * country: 'US'
619
+ * });
620
+ *
621
+ * // Redirect merchant to onboarding.onboarding_url
622
+ */
623
+ async createStripeOnboardingLink(businessId, params = {}) {
624
+ return this.request('/stripe/connect/onboard', {
625
+ method: 'POST',
626
+ body: JSON.stringify({
627
+ businessId,
628
+ ...params
629
+ }),
630
+ });
631
+ }
632
+
633
+ /**
634
+ * Release card escrow funds to merchant
635
+ *
636
+ * Releases funds held in card escrow mode to the merchant's Stripe account.
637
+ * Only works for payments created with escrowMode: true.
638
+ *
639
+ * @param {string} escrowId - Escrow ID (returned from escrow payment creation)
640
+ * @param {string} [reason] - Reason for release
641
+ * @returns {Promise<Object>} Release confirmation with transfer details
642
+ *
643
+ * @example
644
+ * const release = await client.releaseCardEscrow('escrow-id', 'Work completed');
645
+ * console.log(`Released $${release.amount_transferred / 100} to merchant`);
646
+ */
647
+ async releaseCardEscrow(escrowId, reason) {
648
+ return this.request('/stripe/escrow/release', {
649
+ method: 'POST',
650
+ body: JSON.stringify({
651
+ escrowId,
652
+ reason
653
+ }),
654
+ });
655
+ }
656
+
657
+ /**
658
+ * Refund a card payment
659
+ *
660
+ * Creates a refund for a card payment. Works for both gateway and escrow mode payments.
661
+ * For escrow payments, refunds from held funds. For gateway payments, refunds from merchant account.
662
+ *
663
+ * @param {string} paymentId - Payment intent ID or escrow ID
664
+ * @param {Object} [params] - Optional parameters
665
+ * @param {number} [params.amount] - Refund amount in cents (partial refund). Omit for full refund.
666
+ * @param {string} [params.reason] - Reason for refund
667
+ * @returns {Promise<Object>} Refund confirmation
668
+ *
669
+ * @example
670
+ * // Full refund
671
+ * const refund = await client.refundCardPayment('payment-id');
672
+ *
673
+ * // Partial refund
674
+ * const partialRefund = await client.refundCardPayment('payment-id', {
675
+ * amount: 2500, // $25.00 refund
676
+ * reason: 'Customer complaint'
677
+ * });
678
+ */
679
+ async refundCardPayment(paymentId, params = {}) {
680
+ return this.request('/stripe/escrow/refund', {
681
+ method: 'POST',
682
+ body: JSON.stringify({
683
+ escrowId: paymentId, // API expects escrowId param
684
+ ...params
685
+ }),
686
+ });
687
+ }
525
688
  }
526
689
 
527
690
  export default CoinPayClient;
@@ -113,3 +113,20 @@ export interface DidInfo {
113
113
  export function getMyDid(client: CoinPayClient): Promise<DidInfo>;
114
114
  export function claimDid(client: CoinPayClient): Promise<DidInfo>;
115
115
  export function linkDid(client: CoinPayClient, params: { did: string; publicKey: string; signature: string }): Promise<DidInfo>;
116
+
117
+ // Platform Issuer Self-Service
118
+
119
+ export interface PlatformIssuer {
120
+ id: string;
121
+ did: string;
122
+ name: string;
123
+ domain: string;
124
+ active: boolean;
125
+ api_key: string | null;
126
+ created_at: string;
127
+ }
128
+
129
+ export function registerPlatformIssuer(client: CoinPayClient, params: { name: string; domain: string; did?: string }): Promise<{ success: boolean; issuer: PlatformIssuer; api_key: string }>;
130
+ export function listPlatformIssuers(client: CoinPayClient): Promise<{ success: boolean; issuers: PlatformIssuer[] }>;
131
+ export function rotatePlatformApiKey(client: CoinPayClient, issuerId: string): Promise<{ success: boolean; issuer: PlatformIssuer; api_key: string }>;
132
+ export function deactivatePlatformIssuer(client: CoinPayClient, issuerId: string): Promise<{ success: boolean; issuer: PlatformIssuer }>;
package/src/reputation.js CHANGED
@@ -180,3 +180,56 @@ export async function getTrustProfile(client, agentDid) {
180
180
  export function getBadgeUrl(baseUrl, did) {
181
181
  return `${baseUrl}/api/reputation/badge/${encodeURIComponent(did)}`;
182
182
  }
183
+
184
+ // ═══════════════════════════════════════════════════════════
185
+ // Platform Issuer Self-Service
186
+ // ═══════════════════════════════════════════════════════════
187
+
188
+ /**
189
+ * Register a new platform issuer
190
+ * @param {import('./client.js').CoinPayClient} client
191
+ * @param {Object} params
192
+ * @param {string} params.name - Issuer name (alphanumeric, dots, hyphens, underscores)
193
+ * @param {string} params.domain - Issuer domain
194
+ * @param {string} [params.did] - Optional DID (auto-generated from domain if omitted)
195
+ * @returns {Promise<Object>} { success, issuer, api_key }
196
+ */
197
+ export async function registerPlatformIssuer(client, { name, domain, did }) {
198
+ return client.request('/reputation/issuers', {
199
+ method: 'POST',
200
+ body: JSON.stringify({ name, domain, did }),
201
+ });
202
+ }
203
+
204
+ /**
205
+ * List merchant's registered platform issuers (keys masked)
206
+ * @param {import('./client.js').CoinPayClient} client
207
+ * @returns {Promise<Object>} { success, issuers }
208
+ */
209
+ export async function listPlatformIssuers(client) {
210
+ return client.request('/reputation/issuers');
211
+ }
212
+
213
+ /**
214
+ * Rotate a platform issuer's API key
215
+ * @param {import('./client.js').CoinPayClient} client
216
+ * @param {string} issuerId - The issuer ID
217
+ * @returns {Promise<Object>} { success, issuer, api_key }
218
+ */
219
+ export async function rotatePlatformApiKey(client, issuerId) {
220
+ return client.request(`/reputation/issuers/${issuerId}/rotate`, {
221
+ method: 'POST',
222
+ });
223
+ }
224
+
225
+ /**
226
+ * Deactivate a platform issuer (soft delete)
227
+ * @param {import('./client.js').CoinPayClient} client
228
+ * @param {string} issuerId - The issuer ID
229
+ * @returns {Promise<Object>} { success, issuer }
230
+ */
231
+ export async function deactivatePlatformIssuer(client, issuerId) {
232
+ return client.request(`/reputation/issuers/${issuerId}`, {
233
+ method: 'DELETE',
234
+ });
235
+ }