@blockmoblabs/cryptopay 0.1.1 → 0.2.0
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/README.md +38 -14
- package/dist/index.d.ts +13 -1
- package/dist/index.js +18 -0
- package/dist/types.d.ts +39 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ npm install @blockmoblabs/cryptopay
|
|
|
16
16
|
import { CryptoPay } from '@blockmoblabs/cryptopay';
|
|
17
17
|
|
|
18
18
|
const cryptopay = new CryptoPay({
|
|
19
|
-
apiKey: process.env.CRYPTOPAY_API_KEY!, //
|
|
19
|
+
apiKey: process.env.CRYPTOPAY_API_KEY!, // cp_live_… from Dashboard -> API keys
|
|
20
20
|
baseUrl: process.env.CRYPTOPAY_API_URL, // only when self-hosting
|
|
21
21
|
});
|
|
22
22
|
|
|
@@ -32,8 +32,7 @@ const intent = await cryptopay.createIntent({
|
|
|
32
32
|
});
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
The key
|
|
36
|
-
reports which one you are on.
|
|
35
|
+
The key identifies the merchant; there is no mode flag to get wrong.
|
|
37
36
|
|
|
38
37
|
| Method | What |
|
|
39
38
|
| --- | --- |
|
|
@@ -41,10 +40,39 @@ reports which one you are on.
|
|
|
41
40
|
| `getIntent(id)` | Read one payment, including its deposit sessions. |
|
|
42
41
|
| `listIntents(query)` | Page through payments; `search` matches any of your references. |
|
|
43
42
|
| `cancelIntent(id)` | Cancel an unpaid payment. Refused once completed. |
|
|
43
|
+
| `createSubscription(input)` | Bill a customer every week, month or year. See below. |
|
|
44
|
+
| `getSubscription(id)` / `listSubscriptions()` | Read subscriptions and their next due date. |
|
|
45
|
+
| `cancelSubscription(id)` | Stop future payments. |
|
|
44
46
|
| `verifyWebhook(opts)` | Verify and parse a webhook. Throws on anything suspect. |
|
|
45
47
|
|
|
46
48
|
Non-2xx responses throw `CryptoPayError` with `status` and `body`.
|
|
47
49
|
|
|
50
|
+
## Subscriptions
|
|
51
|
+
|
|
52
|
+
Crypto cannot be pulled from a wallet, so a subscription is a recurring invoice. Three days before
|
|
53
|
+
each due date the customer is emailed a payment link, then reminded once a day. If it is still
|
|
54
|
+
unpaid three days after the due date, the subscription lapses.
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
const sub = await cryptopay.createSubscription({
|
|
58
|
+
amountUsd: 20,
|
|
59
|
+
customerEmail: 'user@example.com',
|
|
60
|
+
interval: 'month', // 'week' | 'month' | 'year'
|
|
61
|
+
firstDueAt: '2026-11-02', // later payments stay on the 2nd
|
|
62
|
+
reference: 'sub_221', // comes back as customer.subscriptionRef
|
|
63
|
+
description: 'Pro plan', // shown to the customer in the emails
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
In your webhook handler:
|
|
68
|
+
|
|
69
|
+
| Event | What to do |
|
|
70
|
+
| --- | --- |
|
|
71
|
+
| `payment.completed` with `customer.subscriptionRef` | A period was paid. Extend access. |
|
|
72
|
+
| `subscription.renewed` | Same moment; `event.subscription.dueAt` is the next due date. |
|
|
73
|
+
| `subscription.payment_due` | An invoice opened. `event.subscription.checkoutUrl` is the link, if you want to send your own message. |
|
|
74
|
+
| `subscription.lapsed` | Unpaid after the grace period. Remove access. |
|
|
75
|
+
|
|
48
76
|
## Browser
|
|
49
77
|
|
|
50
78
|
```ts
|
|
@@ -67,7 +95,7 @@ checkout's origin can drive the modal.
|
|
|
67
95
|
|
|
68
96
|
## Webhooks
|
|
69
97
|
|
|
70
|
-
Pass the **raw** body
|
|
98
|
+
Pass the **raw** body: a re-serialized object will not match the signature.
|
|
71
99
|
|
|
72
100
|
```ts
|
|
73
101
|
app.post('/webhooks/cryptopay', express.raw({ type: 'application/json' }), async (req, res) => {
|
|
@@ -100,18 +128,14 @@ old event does not get through. Delivery is at-least-once: dedupe on `event.id`.
|
|
|
100
128
|
Events: `payment.created`, `payment.detected`, `payment.confirming`, `payment.underpaid`,
|
|
101
129
|
`payment.completed`, `payment.expired`, `payment.failed`.
|
|
102
130
|
|
|
103
|
-
##
|
|
104
|
-
|
|
105
|
-
A test key runs the whole lifecycle against a simulator — real intents, statuses and webhooks, no
|
|
106
|
-
chain and no money. The deposit address is `cptest_…`, deliberately invalid on every network, and the
|
|
107
|
-
payment confirms on a timer: detected within ~15s and confirmed within ~30s, since the watcher that
|
|
108
|
-
advances it ticks every 15 seconds. In an automated test, call `POST /sessions/:id/recheck` (what *I
|
|
109
|
-
have paid* does in the checkout) to confirm immediately instead of sleeping.
|
|
131
|
+
## Trying it out
|
|
110
132
|
|
|
111
|
-
|
|
133
|
+
Accounts are live from sign-up. Make a small real payment to see the whole lifecycle, or create a
|
|
134
|
+
payment link from the dashboard and open its checkout to see what your customers see. Events can be
|
|
135
|
+
re-sent from Dashboard -> Webhooks while you build your handler.
|
|
112
136
|
|
|
113
137
|
## Fees
|
|
114
138
|
|
|
115
|
-
|
|
139
|
+
1% per successful payment with a $0.50 minimum, charged once on completion. It comes off your side: the payer is
|
|
116
140
|
asked for exactly the invoice amount. On a $40 payment, `fee` is
|
|
117
|
-
`{ grossUsd: 40, feeUsd:
|
|
141
|
+
`{ grossUsd: 40, feeUsd: 0.5, netUsd: 39.5 }`.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type CreateIntentInput, type PaymentIntent, type WebhookEvent } from './types.js';
|
|
1
|
+
import { type CreateIntentInput, type CreateSubscriptionInput, type PaymentIntent, type Subscription, type WebhookEvent } from './types.js';
|
|
2
2
|
export * from './types.js';
|
|
3
3
|
export type CryptoPayOptions = {
|
|
4
4
|
/** `cp_test_…` or `cp_live_…`. The key decides the mode; there is no mode flag to get wrong. */
|
|
@@ -41,6 +41,18 @@ export declare class CryptoPay {
|
|
|
41
41
|
intents: PaymentIntent[];
|
|
42
42
|
nextCursor: string | null;
|
|
43
43
|
}>;
|
|
44
|
+
/**
|
|
45
|
+
* Bill a customer on a schedule. Three days before each due date they are emailed a payment link,
|
|
46
|
+
* then reminded daily; unpaid three days after it, the subscription lapses. Each payment arrives
|
|
47
|
+
* as an ordinary `payment.completed` webhook carrying your `reference` as `subscriptionRef`.
|
|
48
|
+
*/
|
|
49
|
+
createSubscription(input: CreateSubscriptionInput): Promise<Subscription>;
|
|
50
|
+
getSubscription(id: string): Promise<Subscription>;
|
|
51
|
+
listSubscriptions(): Promise<{
|
|
52
|
+
subscriptions: Subscription[];
|
|
53
|
+
}>;
|
|
54
|
+
/** Stops future payments. Safe to call twice. */
|
|
55
|
+
cancelSubscription(id: string): Promise<Subscription>;
|
|
44
56
|
/**
|
|
45
57
|
* Verify a webhook and return its parsed body. Pass the RAW request body — a re-serialized object
|
|
46
58
|
* will not match the signature. Throws if the signature, the headers, or the timestamp is wrong,
|
package/dist/index.js
CHANGED
|
@@ -53,6 +53,24 @@ export class CryptoPay {
|
|
|
53
53
|
const suffix = params.toString();
|
|
54
54
|
return this.request('GET', `/payment-intents${suffix ? `?${suffix}` : ''}`);
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Bill a customer on a schedule. Three days before each due date they are emailed a payment link,
|
|
58
|
+
* then reminded daily; unpaid three days after it, the subscription lapses. Each payment arrives
|
|
59
|
+
* as an ordinary `payment.completed` webhook carrying your `reference` as `subscriptionRef`.
|
|
60
|
+
*/
|
|
61
|
+
createSubscription(input) {
|
|
62
|
+
return this.request('POST', '/subscriptions', { body: input });
|
|
63
|
+
}
|
|
64
|
+
getSubscription(id) {
|
|
65
|
+
return this.request('GET', `/subscriptions/${encodeURIComponent(id)}`);
|
|
66
|
+
}
|
|
67
|
+
listSubscriptions() {
|
|
68
|
+
return this.request('GET', '/subscriptions');
|
|
69
|
+
}
|
|
70
|
+
/** Stops future payments. Safe to call twice. */
|
|
71
|
+
cancelSubscription(id) {
|
|
72
|
+
return this.request('POST', `/subscriptions/${encodeURIComponent(id)}/cancel`);
|
|
73
|
+
}
|
|
56
74
|
/**
|
|
57
75
|
* Verify a webhook and return its parsed body. Pass the RAW request body — a re-serialized object
|
|
58
76
|
* will not match the signature. Throws if the signature, the headers, or the timestamp is wrong,
|
package/dist/types.d.ts
CHANGED
|
@@ -76,7 +76,41 @@ export type CreateIntentInput = {
|
|
|
76
76
|
/** Repeating a request with the same key returns the original intent instead of charging twice. */
|
|
77
77
|
idempotencyKey?: string;
|
|
78
78
|
};
|
|
79
|
-
export type WebhookEventType = 'payment.created' | 'payment.detected' | 'payment.confirming' | 'payment.underpaid' | 'payment.completed' | 'payment.expired' | 'payment.failed' | 'sweep.failed';
|
|
79
|
+
export type WebhookEventType = 'payment.created' | 'payment.detected' | 'payment.confirming' | 'payment.underpaid' | 'payment.completed' | 'payment.expired' | 'payment.failed' | 'sweep.failed' | 'subscription.payment_due' | 'subscription.renewed' | 'subscription.lapsed';
|
|
80
|
+
export type CreateSubscriptionInput = {
|
|
81
|
+
/** Charged every interval. 0.50 to 1,000,000, at most 2 decimals. */
|
|
82
|
+
amountUsd: number;
|
|
83
|
+
/** Who is billed. Payment links and reminders are emailed here. */
|
|
84
|
+
customerEmail: string;
|
|
85
|
+
interval: 'week' | 'month' | 'year';
|
|
86
|
+
/** When the first payment is due (ISO date). Left out: now. Later ones keep the same day. */
|
|
87
|
+
firstDueAt?: string;
|
|
88
|
+
customerRef?: string;
|
|
89
|
+
/** Your own id for this subscription. Comes back as `customer.subscriptionRef` on every payment. */
|
|
90
|
+
reference?: string;
|
|
91
|
+
/** What is being paid for, as the customer reads it in the emails. */
|
|
92
|
+
description?: string;
|
|
93
|
+
metadata?: Record<string, unknown>;
|
|
94
|
+
};
|
|
95
|
+
export type Subscription = {
|
|
96
|
+
id: string;
|
|
97
|
+
mode: ApiMode;
|
|
98
|
+
status: 'active' | 'lapsed' | 'cancelled';
|
|
99
|
+
customerEmail: string;
|
|
100
|
+
customerRef: string | null;
|
|
101
|
+
reference: string | null;
|
|
102
|
+
description: string | null;
|
|
103
|
+
amountUsd: number;
|
|
104
|
+
interval: 'week' | 'month' | 'year';
|
|
105
|
+
/** When the period now being collected is due. Null once the subscription has ended. */
|
|
106
|
+
dueAt: string | null;
|
|
107
|
+
periodsPaid: number;
|
|
108
|
+
/** The open invoice (a payment intent), once the customer has been asked to pay it. */
|
|
109
|
+
intentId: string | null;
|
|
110
|
+
metadata: unknown;
|
|
111
|
+
endedAt: string | null;
|
|
112
|
+
createdAt: string;
|
|
113
|
+
};
|
|
80
114
|
export type WebhookEvent = {
|
|
81
115
|
id: string;
|
|
82
116
|
type: WebhookEventType;
|
|
@@ -99,6 +133,10 @@ export type WebhookEvent = {
|
|
|
99
133
|
feeUsd: number;
|
|
100
134
|
netUsd: number;
|
|
101
135
|
} | null;
|
|
136
|
+
/** On `subscription.*` events. `checkoutUrl` is set on `subscription.payment_due`. */
|
|
137
|
+
subscription?: Subscription & {
|
|
138
|
+
checkoutUrl?: string | null;
|
|
139
|
+
};
|
|
102
140
|
payment: {
|
|
103
141
|
sessionId: string;
|
|
104
142
|
chain: Chain;
|
package/package.json
CHANGED