shieldpay-api-sdkjs 2.0.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 ADDED
@@ -0,0 +1,63 @@
1
+ # shieldpay-sdk
2
+
3
+ Official JavaScript/TypeScript SDK for the [ShieldPay API](https://hnow.info).
4
+
5
+ Anti-spam, VPN blocking, email authentication, and PayPal payments — all in one package.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install shieldpay-sdk
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```js
16
+ import { ShieldPay } from 'shieldpay-sdk'
17
+
18
+ const sp = new ShieldPay({ apiKey: 'sk_live_YOUR_KEY' })
19
+ // All calls go to: https://hnow.info/api/worker=sk_live_YOUR_KEY/...
20
+
21
+ // Create a PayPal payment
22
+ const order = await sp.payment.create({
23
+ amount: 49.99,
24
+ currency: 'CHF',
25
+ return_url: 'https://yoursite.com/success',
26
+ cancel_url: 'https://yoursite.com/cancel',
27
+ })
28
+ window.location.href = order.approve_url // redirect to PayPal
29
+
30
+ // After PayPal redirects back, capture
31
+ const captured = await sp.payment.capture(orderId)
32
+
33
+ // Check IP reputation (Pro: real IPQS check)
34
+ const ip = await sp.security.checkIp('1.2.3.4')
35
+
36
+ // Passwordless email auth for your users
37
+ const { token } = await sp.auth.createToken('user@email.com')
38
+ const { verified } = await sp.auth.verifyToken(token)
39
+ ```
40
+
41
+ ## API Endpoint
42
+
43
+ Your API key is embedded in the URL — no headers needed:
44
+
45
+ ```
46
+ https://hnow.info/api/worker=sk_live_YOUR_KEY/payment/create
47
+ https://hnow.info/api/worker=sk_live_YOUR_KEY/security/check-ip
48
+ https://hnow.info/api/worker=sk_live_YOUR_KEY/me
49
+ ```
50
+
51
+ ## Plans
52
+
53
+ | Feature | Free | Pro (1 CHF/mo) |
54
+ |---|---|---|
55
+ | PayPal payments | ✓ | ✓ |
56
+ | Transaction fee | 2% | **0%** |
57
+ | VPN/Tor blocking | Basic | Full IPQS |
58
+ | Auto-fallback | ✗ | ✓ |
59
+ | Email alerts | ✗ | ✓ |
60
+
61
+ ## Docs
62
+
63
+ Full documentation: [https://hnow.info/SDK/docs/](https://hnow.info/SDK/docs/)
@@ -0,0 +1,412 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
5
+ <title>ShieldPay SDK v2 — Docs</title>
6
+ <link href="https://fonts.googleapis.com/css2?family=Syne:wght@700;800&family=DM+Mono:wght@400;500&family=DM+Sans:wght@300;400;500&display=swap" rel="stylesheet">
7
+ <style>
8
+ :root{--bg:#0a0a0f;--surface:#111118;--border:#1e1e2e;--accent:#7c6aff;--accent2:#00e5c0;--danger:#ff4e6a;--warn:#ffb347;--text:#e8e8f0;--muted:#6b6b80;--card:#13131f}
9
+ *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
10
+ body{background:var(--bg);color:var(--text);font-family:'DM Sans',sans-serif;display:flex;min-height:100vh;line-height:1.6}
11
+ .sidebar{width:265px;border-right:1px solid var(--border);padding:0;flex-shrink:0;position:sticky;top:0;height:100vh;overflow-y:auto;background:var(--surface)}
12
+ .sidebar-top{padding:1.5rem 1.5rem 1rem;border-bottom:1px solid var(--border)}
13
+ .logo{font-family:'Syne',sans-serif;font-weight:800;font-size:1.15rem;color:var(--text);text-decoration:none;display:block;margin-bottom:.4rem}.logo span{color:var(--accent)}
14
+ .version-chip{font-family:'DM Mono',monospace;font-size:.68rem;background:rgba(124,106,255,.12);border:1px solid rgba(124,106,255,.25);color:var(--accent);padding:.15rem .55rem;border-radius:100px}
15
+ .nav-section{padding:.5rem 1.5rem .2rem;font-family:'DM Mono',monospace;font-size:.65rem;color:var(--muted);text-transform:uppercase;letter-spacing:.12em;margin-top:.5rem}
16
+ .sidebar nav a{display:block;padding:.45rem 1.5rem;color:var(--muted);text-decoration:none;font-size:.84rem;border-left:2px solid transparent;transition:all .15s}
17
+ .sidebar nav a:hover{color:var(--text);background:rgba(124,106,255,.04);text-decoration:none}
18
+ .sidebar nav a.active{color:var(--text);border-left-color:var(--accent);background:rgba(124,106,255,.07)}
19
+ .main{flex:1;padding:3.5rem 4rem;max-width:860px}
20
+ h1{font-family:'Syne',sans-serif;font-size:2.3rem;font-weight:800;letter-spacing:-.04em;margin-bottom:.5rem}
21
+ h2{font-family:'Syne',sans-serif;font-size:1.45rem;font-weight:800;letter-spacing:-.03em;margin:3rem 0 .9rem;padding-top:3rem;border-top:1px solid var(--border)}
22
+ h2:first-of-type{border:none;padding:0;margin-top:1rem}
23
+ h3{font-family:'Syne',sans-serif;font-size:1rem;font-weight:700;margin:1.6rem 0 .5rem;color:var(--accent2)}
24
+ h4{font-family:'DM Mono',monospace;font-size:.85rem;font-weight:500;color:var(--text);margin:.8rem 0 .3rem}
25
+ p{color:var(--muted);line-height:1.75;margin-bottom:.9rem;font-size:.93rem}
26
+ a{color:var(--accent);text-decoration:none}a:hover{text-decoration:underline}
27
+ pre{background:var(--card);border:1px solid var(--border);border-radius:10px;padding:1.25rem 1.5rem;overflow-x:auto;margin:1rem 0;font-family:'DM Mono',monospace;font-size:.81rem;line-height:1.75;position:relative}
28
+ code{font-family:'DM Mono',monospace;font-size:.83rem;background:rgba(124,106,255,.1);padding:.12rem .38rem;border-radius:4px;color:var(--accent)}
29
+ pre code{background:none;padding:0;color:inherit;font-size:inherit}
30
+ .kw{color:var(--accent2)}.st{color:#ffcb6b}.nm{color:#f78c6c}.cm{color:#4a5568;font-style:italic}.prop{color:#82aaff}.type{color:#c792ea}
31
+ .badge{display:inline-flex;align-items:center;padding:.18rem .55rem;border-radius:100px;font-family:'DM Mono',monospace;font-size:.7rem;font-weight:700;margin-left:.4rem;vertical-align:middle}
32
+ .b-get{background:rgba(0,229,192,.08);color:var(--accent2);border:1px solid rgba(0,229,192,.25)}
33
+ .b-post{background:rgba(124,106,255,.1);color:var(--accent);border:1px solid rgba(124,106,255,.25)}
34
+ .b-pro{background:rgba(255,179,71,.08);color:var(--warn);border:1px solid rgba(255,179,71,.25)}
35
+ .b-free{background:rgba(107,107,128,.1);color:var(--muted);border:1px solid var(--border)}
36
+ table{width:100%;border-collapse:collapse;margin:1rem 0;font-size:.86rem}
37
+ th{text-align:left;padding:.55rem .75rem;color:var(--muted);font-size:.72rem;font-family:'DM Mono',monospace;text-transform:uppercase;letter-spacing:.06em;border-bottom:1px solid var(--border)}
38
+ td{padding:.6rem .75rem;border-bottom:1px solid rgba(30,30,46,.5);color:var(--text);vertical-align:top}
39
+ tr:last-child td{border:none}
40
+ .param-name{font-family:'DM Mono',monospace;font-size:.82rem;color:var(--accent2)}
41
+ .param-type{font-family:'DM Mono',monospace;font-size:.78rem;color:var(--type, #c792ea)}
42
+ .param-req{color:var(--danger);font-size:.75rem}
43
+ .alert{padding:.8rem 1rem;border-radius:8px;font-size:.86rem;margin:1rem 0;line-height:1.6}
44
+ .alert-info{background:rgba(124,106,255,.07);border:1px solid rgba(124,106,255,.2);color:var(--accent)}
45
+ .alert-warn{background:rgba(255,179,71,.07);border:1px solid rgba(255,179,71,.2);color:var(--warn)}
46
+ .alert-success{background:rgba(0,229,192,.07);border:1px solid rgba(0,229,192,.2);color:var(--accent2)}
47
+ .endpoint-line{display:flex;align-items:center;gap:.6rem;margin:.4rem 0;font-family:'DM Mono',monospace;font-size:.8rem}
48
+ .plan-grid{display:grid;grid-template-columns:1fr 1fr;gap:1rem;margin:1rem 0}
49
+ .plan-box{background:var(--card);border:1px solid var(--border);border-radius:11px;padding:1.2rem}
50
+ .plan-box.pro{border-color:rgba(124,106,255,.4)}
51
+ .plan-box h4{font-family:'Syne',sans-serif;font-weight:700;font-size:.95rem;margin-bottom:.7rem}
52
+ .plan-box ul{list-style:none;font-size:.85rem;color:var(--muted)}
53
+ .plan-box li{padding:.25rem 0;display:flex;gap:.5rem}.ck{color:var(--accent2)}.cx{color:var(--danger)}
54
+ hr{border:none;border-top:1px solid var(--border);margin:2rem 0}
55
+ @media(max-width:768px){.sidebar{display:none}.main{padding:2rem 1.5rem}}
56
+ </style>
57
+ </head>
58
+ <body>
59
+
60
+ <aside class="sidebar">
61
+ <div class="sidebar-top">
62
+ <a href="/" class="logo">Shield<span>Pay</span></a>
63
+ <span class="version-chip">v2.0.0</span>
64
+ </div>
65
+ <nav>
66
+ <div class="nav-section">Getting Started</div>
67
+ <a href="#install">Installation</a>
68
+ <a href="#quickstart">Quick Start</a>
69
+ <a href="#endpoint">API Endpoint</a>
70
+ <a href="#register">Registration</a>
71
+ <div class="nav-section">SDK Modules</div>
72
+ <a href="#payment">payment</a>
73
+ <a href="#auth">auth</a>
74
+ <a href="#security">security</a>
75
+ <a href="#account">account</a>
76
+ <div class="nav-section">REST API</div>
77
+ <a href="#endpoints">All Endpoints</a>
78
+ <a href="#errors">Errors & Codes</a>
79
+ <a href="#ratelimits">Rate Limits</a>
80
+ <div class="nav-section">Plans</div>
81
+ <a href="#plans">Free vs Pro</a>
82
+ <a href="#fees">Fee Calculation</a>
83
+ <div class="nav-section">Integration</div>
84
+ <a href="#webhooks">Webhooks</a>
85
+ <a href="#fallback">Auto-Fallback</a>
86
+ <div class="nav-section">Links</div>
87
+ <a href="/panel.php">Dashboard ↗</a>
88
+ <a href="/admin.php">Admin ↗</a>
89
+ <a href="/">Home ↗</a>
90
+ </nav>
91
+ </aside>
92
+
93
+ <main class="main">
94
+
95
+ <h1>ShieldPay SDK</h1>
96
+ <p>The official JavaScript/TypeScript SDK for the ShieldPay API — payments, anti-spam, VPN blocking, and email authentication in one package.</p>
97
+
98
+ <div class="alert alert-info">
99
+ <strong>Base URL:</strong> <code>https://hnow.info/api/worker={YOUR_API_KEY}/{endpoint}</code><br>
100
+ Your API key is embedded directly in the URL path — no separate Authorization header needed.
101
+ </div>
102
+
103
+ <!-- INSTALL -->
104
+ <h2 id="install">Installation</h2>
105
+ <pre><span class="cm"># npm</span>
106
+ npm install shieldpay-sdk
107
+
108
+ <span class="cm"># yarn</span>
109
+ yarn add shieldpay-sdk
110
+
111
+ <span class="cm"># pnpm</span>
112
+ pnpm add shieldpay-sdk</pre>
113
+
114
+ <p>Works in Node.js ≥18, Deno, Bun, Cloudflare Workers, and all modern browsers. Uses the native <code>fetch</code> API — no dependencies.</p>
115
+
116
+ <!-- QUICKSTART -->
117
+ <h2 id="quickstart">Quick Start</h2>
118
+ <pre><span class="kw">import</span> { ShieldPay } <span class="kw">from</span> <span class="st">'shieldpay-sdk'</span>
119
+
120
+ <span class="cm">// 1. Initialize with your API key from the dashboard</span>
121
+ <span class="kw">const</span> sp = <span class="kw">new</span> ShieldPay({ apiKey: <span class="st">'sk_live_YOUR_KEY'</span> })
122
+ <span class="cm">// → All calls go to: https://hnow.info/api/worker=sk_live_YOUR_KEY/...</span>
123
+
124
+ <span class="cm">// 2. Create a PayPal order</span>
125
+ <span class="kw">const</span> order = <span class="kw">await</span> sp.payment.create({
126
+ amount: <span class="nm">49.99</span>,
127
+ currency: <span class="st">'CHF'</span>,
128
+ description: <span class="st">'Premium subscription'</span>,
129
+ return_url: <span class="st">'https://yoursite.com/payment/success'</span>,
130
+ cancel_url: <span class="st">'https://yoursite.com/payment/cancel'</span>,
131
+ })
132
+
133
+ <span class="cm">// 3. Redirect user to PayPal for approval</span>
134
+ window.location.href = order.approve_url
135
+
136
+ <span class="cm">// 4. After PayPal redirects back, capture the payment</span>
137
+ <span class="cm">// (in your return_url handler)</span>
138
+ <span class="kw">const</span> captured = <span class="kw">await</span> sp.payment.capture(orderId)
139
+ console.log(captured.status) <span class="cm">// "COMPLETED"</span></pre>
140
+
141
+ <!-- ENDPOINT -->
142
+ <h2 id="endpoint">API Endpoint Format</h2>
143
+ <p>ShieldPay uses a custom URL pattern that embeds your API key directly in the path. This makes it easy to call from any HTTP client without custom headers:</p>
144
+
145
+ <pre><span class="cm"># Pattern</span>
146
+ https://hnow.info/api/worker=<span class="nm">{API_KEY}</span>/<span class="nm">{endpoint}</span>
147
+
148
+ <span class="cm"># Examples</span>
149
+ GET https://hnow.info/api/worker=sk_live_abc/me
150
+ POST https://hnow.info/api/worker=sk_live_abc/payment/create
151
+ GET https://hnow.info/api/worker=sk_live_abc/payment/status?order_id=XYZ
152
+ POST https://hnow.info/api/worker=sk_live_abc/security/check-ip</pre>
153
+
154
+ <p>The <code>X-API-Key</code> header is also accepted for clients that prefer header-based auth. The SDK handles both automatically.</p>
155
+
156
+ <pre><span class="cm">// Get your full endpoint URL</span>
157
+ <span class="kw">const</span> sp = <span class="kw">new</span> ShieldPay({ apiKey: <span class="st">'sk_live_abc'</span> })
158
+ <span class="kw">const</span> url = sp.endpoint(<span class="st">'/payment/create'</span>)
159
+ <span class="cm">// → "https://hnow.info/api/worker=sk_live_abc/payment/create"</span></pre>
160
+
161
+ <!-- REGISTER -->
162
+ <h2 id="register">Registration & Email Verification</h2>
163
+ <p>Register a company and verify their email to receive an API key. These are <strong>static methods</strong> — no API key instance needed.</p>
164
+
165
+ <pre><span class="kw">import</span> { ShieldPay } <span class="kw">from</span> <span class="st">'shieldpay-sdk'</span>
166
+
167
+ <span class="cm">// 1. Register (anti-spam + VPN check runs automatically)</span>
168
+ <span class="kw">const</span> reg = <span class="kw">await</span> ShieldPay.register({
169
+ company: <span class="st">'Acme Corp'</span>,
170
+ email: <span class="st">'hello@acme.com'</span>,
171
+ password: <span class="st">'securepassword123'</span>,
172
+ })
173
+ <span class="cm">// → { ok: true, message: "Account created. Check your email to verify." }</span>
174
+
175
+ <span class="cm">// 2. User clicks the link in their email, which contains the token.
176
+ // Your app then calls:</span>
177
+ <span class="kw">const</span> verified = <span class="kw">await</span> ShieldPay.verifyEmail(<span class="st">'TOKEN_FROM_EMAIL_LINK'</span>)
178
+ <span class="cm">// → {
179
+ // ok: true,
180
+ // api_key: "sk_live_abc123...",
181
+ // api_endpoint: "https://hnow.info/api/worker=sk_live_abc123",
182
+ // message: "Email verified! Use your API key to authenticate."
183
+ // }</span>
184
+
185
+ <span class="cm">// 3. Store api_key securely and initialize</span>
186
+ <span class="kw">const</span> sp = <span class="kw">new</span> ShieldPay({ apiKey: verified.api_key })</pre>
187
+
188
+ <!-- PAYMENT -->
189
+ <h2 id="payment">payment module</h2>
190
+ <p>All PayPal order operations. Free plan: 2% fee deducted per transaction. Pro plan: 0% fee.</p>
191
+
192
+ <h3>payment.create(params) <span class="badge b-post">POST</span></h3>
193
+ <table>
194
+ <tr><th>Param</th><th>Type</th><th>Required</th><th>Default</th><th>Description</th></tr>
195
+ <tr><td class="param-name">amount</td><td class="param-type">number</td><td class="param-req">✓</td><td>—</td><td>Amount to charge (e.g. <code>49.99</code>)</td></tr>
196
+ <tr><td class="param-name">currency</td><td class="param-type">string</td><td></td><td><code>'CHF'</code></td><td>ISO 4217 currency code</td></tr>
197
+ <tr><td class="param-name">description</td><td class="param-type">string</td><td></td><td>—</td><td>Order description shown on PayPal</td></tr>
198
+ <tr><td class="param-name">return_url</td><td class="param-type">string</td><td></td><td>Dashboard</td><td>Redirect URL after successful payment</td></tr>
199
+ <tr><td class="param-name">cancel_url</td><td class="param-type">string</td><td></td><td>Dashboard</td><td>Redirect URL if user cancels</td></tr>
200
+ </table>
201
+ <pre><span class="kw">const</span> order = <span class="kw">await</span> sp.payment.create({
202
+ amount: <span class="nm">25.00</span>,
203
+ currency: <span class="st">'CHF'</span>,
204
+ })
205
+
206
+ <span class="cm">// Response (Free plan):</span>
207
+ <span class="cm">// {</span>
208
+ <span class="cm">// ok: true,</span>
209
+ <span class="cm">// order_id: "9XW12345AB",</span>
210
+ <span class="cm">// approve_url: "https://www.paypal.com/checkoutnow?token=...",</span>
211
+ <span class="cm">// amount: 25.00,</span>
212
+ <span class="cm">// fee: 0.50, // 2% — 0 on Pro</span>
213
+ <span class="cm">// net_amount: 24.50,</span>
214
+ <span class="cm">// currency: "CHF",</span>
215
+ <span class="cm">// plan: "free",</span>
216
+ <span class="cm">// fee_note: "2% ShieldPay fee: CHF 0.50. Upgrade to Pro for 0% fees."</span>
217
+ <span class="cm">// }</span></pre>
218
+
219
+ <h3>payment.capture(orderId) <span class="badge b-post">POST</span></h3>
220
+ <p>Finalizes a payment after the user approves it on PayPal. Call this in your <code>return_url</code> handler. On Pro plan, if this fails, auto-fallback is triggered automatically.</p>
221
+ <pre><span class="kw">const</span> result = <span class="kw">await</span> sp.payment.capture(<span class="st">'9XW12345AB'</span>)
222
+ <span class="cm">// → { ok: true, captured: true, order_id: "...", status: "COMPLETED", payer: {...} }</span></pre>
223
+
224
+ <h3>payment.status(orderId) <span class="badge b-get">GET</span></h3>
225
+ <pre><span class="kw">const</span> s = <span class="kw">await</span> sp.payment.status(<span class="st">'9XW12345AB'</span>)
226
+ <span class="cm">// → { ok: true, order_id: "...", status: "CREATED|APPROVED|COMPLETED", amount: {...} }</span></pre>
227
+
228
+ <h3>payment.refund(captureId, amount?, note?) <span class="badge b-post">POST</span></h3>
229
+ <pre><span class="cm">// Full refund</span>
230
+ <span class="kw">await</span> sp.payment.refund(<span class="st">'CAPTURE_ID'</span>)
231
+
232
+ <span class="cm">// Partial refund</span>
233
+ <span class="kw">await</span> sp.payment.refund(<span class="st">'CAPTURE_ID'</span>, <span class="nm">10.00</span>, <span class="st">'Partial refund for returned item'</span>)</pre>
234
+
235
+ <h3>payment.list(options?) <span class="badge b-get">GET</span></h3>
236
+ <pre><span class="kw">const</span> { transactions } = <span class="kw">await</span> sp.payment.list({ limit: <span class="nm">50</span>, status: <span class="st">'completed'</span> })</pre>
237
+
238
+ <!-- AUTH MODULE -->
239
+ <h2 id="auth">auth module</h2>
240
+ <p>Passwordless email token authentication for your end users. Issue a one-time token, send it by email, verify it on return.</p>
241
+
242
+ <h3>auth.createToken(email) <span class="badge b-post">POST</span></h3>
243
+ <pre><span class="kw">const</span> { token, expires_at } = <span class="kw">await</span> sp.auth.createToken(<span class="st">'user@email.com'</span>)
244
+ <span class="cm">// Token expires in 15 minutes. Single-use.</span>
245
+ <span class="cm">// Send this token to the user's email in a magic link.</span>
246
+
247
+ <span class="cm">// Example magic link: https://yourapp.com/auth?token=TOKEN</span></pre>
248
+
249
+ <h3>auth.verifyToken(token) <span class="badge b-post">POST</span></h3>
250
+ <pre><span class="kw">const</span> result = <span class="kw">await</span> sp.auth.verifyToken(req.query.token)
251
+ <span class="cm">// → { ok: true, verified: true, email: "user@email.com" }</span>
252
+
253
+ <span class="cm">// If expired or already used:</span>
254
+ <span class="cm">// throws ShieldPayError: "Invalid or expired token" (status 410)</span></pre>
255
+
256
+ <!-- SECURITY MODULE -->
257
+ <h2 id="security">security module</h2>
258
+
259
+ <h3>security.checkIp(ip) <span class="badge b-post">POST</span> <span class="badge b-pro">Pro: IPQS</span></h3>
260
+ <p>Free plan: checks manual blocklist and Cloudflare threat score. Pro plan: real-time IPQS reputation check (VPN, Tor, fraud score).</p>
261
+ <pre><span class="kw">const</span> check = <span class="kw">await</span> sp.security.checkIp(<span class="st">'1.2.3.4'</span>)
262
+ <span class="cm">// Clean IP:</span>
263
+ <span class="cm">// → { ok: true, blocked: false, reason: null, country: "CH" }</span>
264
+
265
+ <span class="cm">// VPN detected (Pro):</span>
266
+ <span class="cm">// → { ok: true, blocked: true, reason: "Active VPN connection detected.", score: 92 }</span>
267
+
268
+ <span class="cm">// Tor exit node:</span>
269
+ <span class="cm">// → { ok: true, blocked: true, reason: "Tor exit node detected." }</span></pre>
270
+
271
+ <h3>security.checkEmail(email) <span class="badge b-post">POST</span></h3>
272
+ <pre><span class="kw">const</span> r = <span class="kw">await</span> sp.security.checkEmail(<span class="st">'test@mailinator.com'</span>)
273
+ <span class="cm">// → { ok: true, blocked: true, reason: 'Disposable email domain "mailinator.com" is not allowed.' }</span></pre>
274
+
275
+ <!-- ACCOUNT MODULE -->
276
+ <h2 id="account">account module</h2>
277
+
278
+ <h3>account.me() <span class="badge b-get">GET</span></h3>
279
+ <pre><span class="kw">const</span> { user, plan, api_endpoint } = <span class="kw">await</span> sp.account.me()
280
+ <span class="cm">// → { ok: true, user: { id, company, email, plan, ... }, plan: "pro",</span>
281
+ <span class="cm">// api_endpoint: "https://hnow.info/api/worker=sk_live_xxx" }</span></pre>
282
+
283
+ <h3>account.health() <span class="badge b-get">GET</span></h3>
284
+ <pre><span class="kw">const</span> status = <span class="kw">await</span> sp.account.health()
285
+ <span class="cm">// → { ok: true, ts: 1735000000000, region: "ZRH" }</span></pre>
286
+
287
+ <!-- ALL ENDPOINTS -->
288
+ <h2 id="endpoints">All Endpoints</h2>
289
+ <p>Base: <code>https://hnow.info/api/worker={KEY}</code></p>
290
+ <table>
291
+ <tr><th>Method</th><th>Path</th><th>Auth</th><th>Plan</th><th>Description</th></tr>
292
+ <tr><td><span class="badge b-get">GET</span></td><td><code>/health</code></td><td>—</td><td>Any</td><td>Health check + region</td></tr>
293
+ <tr><td><span class="badge b-post">POST</span></td><td><code>/auth/register</code></td><td>—</td><td>Any</td><td>Register company (anti-spam check)</td></tr>
294
+ <tr><td><span class="badge b-post">POST</span></td><td><code>/auth/verify</code></td><td>—</td><td>Any</td><td>Verify email token → get API key</td></tr>
295
+ <tr><td><span class="badge b-get">GET</span></td><td><code>/me</code></td><td>✓</td><td>Any</td><td>Account info + plan</td></tr>
296
+ <tr><td><span class="badge b-get">GET</span></td><td><code>/transactions</code></td><td>✓</td><td>Any</td><td>Transaction list (paginated)</td></tr>
297
+ <tr><td><span class="badge b-post">POST</span></td><td><code>/payment/create</code></td><td>✓</td><td>Any</td><td>Create PayPal order</td></tr>
298
+ <tr><td><span class="badge b-post">POST</span></td><td><code>/payment/capture</code></td><td>✓</td><td>Any</td><td>Capture approved order</td></tr>
299
+ <tr><td><span class="badge b-get">GET</span></td><td><code>/payment/status</code></td><td>✓</td><td>Any</td><td>Order status from PayPal</td></tr>
300
+ <tr><td><span class="badge b-post">POST</span></td><td><code>/payment/refund</code></td><td>✓</td><td>Any</td><td>Refund a capture</td></tr>
301
+ <tr><td><span class="badge b-get">GET</span></td><td><code>/payment/list</code></td><td>✓</td><td>Any</td><td>List transactions</td></tr>
302
+ <tr><td><span class="badge b-post">POST</span></td><td><code>/auth/token</code></td><td>✓</td><td>Any</td><td>Create email auth token for user</td></tr>
303
+ <tr><td><span class="badge b-post">POST</span></td><td><code>/auth/token/verify</code></td><td>✓</td><td>Any</td><td>Verify email auth token</td></tr>
304
+ <tr><td><span class="badge b-post">POST</span></td><td><code>/security/check-ip</code></td><td>✓</td><td>Pro+</td><td>IP reputation (IPQS on Pro)</td></tr>
305
+ <tr><td><span class="badge b-post">POST</span></td><td><code>/security/check-email</code></td><td>✓</td><td>Any</td><td>Disposable email check</td></tr>
306
+ </table>
307
+
308
+ <!-- ERRORS -->
309
+ <h2 id="errors">Errors & Codes</h2>
310
+ <p>All errors throw a <code>ShieldPayError</code> with <code>.message</code>, <code>.code</code>, and <code>.status</code>.</p>
311
+ <pre><span class="kw">import</span> { ShieldPay, ShieldPayError } <span class="kw">from</span> <span class="st">'shieldpay-sdk'</span>
312
+
313
+ <span class="kw">try</span> {
314
+ <span class="kw">const</span> order = <span class="kw">await</span> sp.payment.create({ amount: <span class="nm">-1</span> })
315
+ } <span class="kw">catch</span> (e) {
316
+ <span class="kw">if</span> (e <span class="kw">instanceof</span> ShieldPayError) {
317
+ console.error(e.message) <span class="cm">// "Invalid amount"</span>
318
+ console.error(e.code) <span class="cm">// "INVALID_AMOUNT"</span>
319
+ console.error(e.status) <span class="cm">// 400</span>
320
+ }
321
+ }</pre>
322
+ <table>
323
+ <tr><th>HTTP</th><th>Code</th><th>Meaning</th></tr>
324
+ <tr><td><code>400</code></td><td><code>INVALID_*</code></td><td>Bad request / missing or invalid parameters</td></tr>
325
+ <tr><td><code>401</code></td><td><code>INVALID_KEY</code></td><td>API key missing, invalid, or revoked</td></tr>
326
+ <tr><td><code>403</code></td><td><code>BLOCKED</code></td><td>Spam/VPN/rate limit blocked</td></tr>
327
+ <tr><td><code>404</code></td><td><code>NOT_FOUND</code></td><td>Endpoint or resource not found</td></tr>
328
+ <tr><td><code>409</code></td><td><code>CONFLICT</code></td><td>Email already registered</td></tr>
329
+ <tr><td><code>410</code></td><td><code>TOKEN_EXPIRED</code></td><td>Token expired or already used</td></tr>
330
+ <tr><td><code>429</code></td><td><code>RATE_LIMITED</code></td><td>Rate limit exceeded — check <code>Retry-After</code> header</td></tr>
331
+ <tr><td><code>502</code></td><td><code>UPSTREAM_ERROR</code></td><td>PayPal API error</td></tr>
332
+ <tr><td><code>TIMEOUT</code></td><td><code>TIMEOUT</code></td><td>Request timed out (default: 15s)</td></tr>
333
+ <tr><td><code>NETWORK</code></td><td><code>NETWORK_ERROR</code></td><td>Network unreachable</td></tr>
334
+ </table>
335
+
336
+ <!-- RATE LIMITS -->
337
+ <h2 id="ratelimits">Rate Limits</h2>
338
+ <table>
339
+ <tr><th>Scope</th><th>Limit</th><th>Window</th></tr>
340
+ <tr><td>API calls per key</td><td>1,000</td><td>1 hour (sliding)</td></tr>
341
+ <tr><td>Registrations per IP</td><td>5</td><td>1 hour</td></tr>
342
+ <tr><td>Login attempts per IP</td><td>10</td><td>15 minutes</td></tr>
343
+ <tr><td>Admin login per IP</td><td>5</td><td>15 minutes</td></tr>
344
+ </table>
345
+ <p>Rate limit headers are included on every response:</p>
346
+ <pre>X-RateLimit-Limit: 1000
347
+ X-RateLimit-Remaining: 994
348
+ Retry-After: 3142 <span class="cm"># seconds until reset (only on 429)</span></pre>
349
+
350
+ <!-- PLANS -->
351
+ <h2 id="plans">Free vs Pro</h2>
352
+ <div class="plan-grid">
353
+ <div class="plan-box">
354
+ <h4>Free — 0 CHF/mo</h4>
355
+ <ul>
356
+ <li><span class="ck">✓</span> Full API access</li>
357
+ <li><span class="ck">✓</span> PayPal payments</li>
358
+ <li><span class="ck">✓</span> Basic anti-spam</li>
359
+ <li><span class="ck">✓</span> Email token auth</li>
360
+ <li><span class="ck">✓</span> 1,000 req/hour</li>
361
+ <li style="color:var(--danger)"><span class="cx">✗</span> <strong>2% fee per transaction</strong></li>
362
+ <li style="color:var(--danger)"><span class="cx">✗</span> VPN/IPQS blocking</li>
363
+ <li style="color:var(--danger)"><span class="cx">✗</span> Auto-fallback</li>
364
+ </ul>
365
+ </div>
366
+ <div class="plan-box pro">
367
+ <h4>Pro — 1 CHF/mo</h4>
368
+ <ul>
369
+ <li><span class="ck">✓</span> Everything in Free</li>
370
+ <li><span class="ck">✓</span> <strong>0% transaction fee</strong></li>
371
+ <li><span class="ck">✓</span> Real-time IPQS VPN/Tor detection</li>
372
+ <li><span class="ck">✓</span> ASN datacenter blocking</li>
373
+ <li><span class="ck">✓</span> Automatic payment fallback</li>
374
+ <li><span class="ck">✓</span> Retry queue (5min → 15min → 1h)</li>
375
+ <li><span class="ck">✓</span> Email alert on fallback</li>
376
+ </ul>
377
+ </div>
378
+ </div>
379
+
380
+ <!-- FEE CALCULATION -->
381
+ <h2 id="fees">Fee Calculation</h2>
382
+ <table>
383
+ <tr><th>Plan</th><th>Amount</th><th>Fee</th><th>Net to you</th></tr>
384
+ <tr><td>Free</td><td>100.00 CHF</td><td style="color:var(--warn)">2.00 CHF</td><td>98.00 CHF</td></tr>
385
+ <tr><td>Free</td><td>49.99 CHF</td><td style="color:var(--warn)">1.00 CHF</td><td>48.99 CHF</td></tr>
386
+ <tr><td>Pro</td><td>100.00 CHF</td><td style="color:var(--accent2)">0.00 CHF</td><td>100.00 CHF</td></tr>
387
+ <tr><td>Pro</td><td>1,000.00 CHF</td><td style="color:var(--accent2)">0.00 CHF</td><td>1,000.00 CHF</td></tr>
388
+ </table>
389
+ <p>The fee is calculated server-side and visible in the <code>payment.create()</code> response as <code>fee</code> and <code>net_amount</code>.</p>
390
+
391
+ <!-- WEBHOOKS -->
392
+ <h2 id="webhooks">PayPal Webhooks</h2>
393
+ <p>Register this URL in your PayPal Developer Dashboard → your app → Webhooks:</p>
394
+ <pre>https://hnow.info/api/webhook/paypal.php</pre>
395
+ <p>Required events: <code>PAYMENT.CAPTURE.COMPLETED</code>, <code>PAYMENT.CAPTURE.DENIED</code>, <code>PAYMENT.SALE.REFUNDED</code></p>
396
+ <p>ShieldPay verifies the PayPal webhook signature automatically on live mode.</p>
397
+
398
+ <!-- FALLBACK -->
399
+ <h2 id="fallback">Auto-Fallback (Pro)</h2>
400
+ <p>When a payment capture fails on a Pro account, ShieldPay automatically:</p>
401
+ <ol style="color:var(--muted);font-size:.9rem;padding-left:1.5rem;line-height:2.2">
402
+ <li>Logs a fallback event with <code>status: pending</code></li>
403
+ <li>Sends an email alert to the account's registered email</li>
404
+ <li>Enqueues a retry via Cloudflare Queues (after 5 minutes)</li>
405
+ <li>Retries up to 3 times (5min → 15min → 1 hour)</li>
406
+ <li>Admin can view and resolve fallbacks at <code>/admin.php?page=fallbacks</code></li>
407
+ </ol>
408
+ <p>Fallback is <strong>not available on the Free plan</strong>. Failed payments on Free simply return an error.</p>
409
+
410
+ </main>
411
+ </body>
412
+ </html>
package/index.js ADDED
@@ -0,0 +1,322 @@
1
+ /**
2
+ * shieldpay-sdk v2.0.0
3
+ * Official SDK for the ShieldPay API
4
+ *
5
+ * Install: npm install shieldpay-sdk
6
+ * Docs: https://hnow.info/SDK/docs/
7
+ *
8
+ * Usage:
9
+ * import { ShieldPay } from 'shieldpay-sdk'
10
+ * const sp = new ShieldPay({ apiKey: 'sk_live_xxx' })
11
+ * // SDK calls: https://hnow.info/api/worker=sk_live_xxx/{endpoint}
12
+ */
13
+
14
+ const SDK_VERSION = '2.0.0'
15
+ const DEFAULT_BASE = 'https://hnow.info'
16
+
17
+ // ─── Main class ───────────────────────────────────────────────────────────────
18
+
19
+ export class ShieldPay {
20
+ #apiKey
21
+ #baseUrl
22
+ #timeout
23
+
24
+ /**
25
+ * @param {{ apiKey: string, baseUrl?: string, timeout?: number }} options
26
+ */
27
+ constructor({ apiKey, baseUrl = DEFAULT_BASE, timeout = 15000 } = {}) {
28
+ if (!apiKey) throw new ShieldPayError('apiKey is required', 'MISSING_KEY')
29
+ if (!apiKey.startsWith('sk_live_') && !apiKey.startsWith('sk_test_'))
30
+ throw new ShieldPayError('Invalid API key format. Expected sk_live_... or sk_test_...', 'INVALID_KEY')
31
+
32
+ this.#apiKey = apiKey
33
+ this.#baseUrl = baseUrl.replace(/\/$/, '')
34
+ this.#timeout = timeout
35
+
36
+ // Bound modules
37
+ this.payment = new PaymentModule(this)
38
+ this.auth = new AuthModule(this)
39
+ this.security = new SecurityModule(this)
40
+ this.account = new AccountModule(this)
41
+ }
42
+
43
+ /**
44
+ * Get the full API endpoint URL for a path.
45
+ * Format: https://hnow.info/api/worker={KEY}/{path}
46
+ */
47
+ endpoint(path = '') {
48
+ return `${this.#baseUrl}/api/worker=${encodeURIComponent(this.#apiKey)}${path}`
49
+ }
50
+
51
+ /**
52
+ * Internal HTTP client.
53
+ * @param {string} path - API path (e.g. '/payment/create')
54
+ * @param {RequestInit} options
55
+ * @returns {Promise<any>}
56
+ */
57
+ async _request(path, options = {}) {
58
+ const url = this.endpoint(path)
59
+ const controller = new AbortController()
60
+ const timer = setTimeout(() => controller.abort(), this.#timeout)
61
+
62
+ let response
63
+ try {
64
+ response = await fetch(url, {
65
+ ...options,
66
+ headers: {
67
+ 'Content-Type': 'application/json',
68
+ 'X-SDK-Version': `shieldpay-sdk/${SDK_VERSION}`,
69
+ ...(options.headers || {}),
70
+ },
71
+ signal: controller.signal,
72
+ })
73
+ } catch (e) {
74
+ if (e.name === 'AbortError') throw new ShieldPayError('Request timed out', 'TIMEOUT')
75
+ throw new ShieldPayError(`Network error: ${e.message}`, 'NETWORK_ERROR')
76
+ } finally {
77
+ clearTimeout(timer)
78
+ }
79
+
80
+ const data = await response.json().catch(() => ({ ok: false, error: 'Invalid JSON response' }))
81
+
82
+ if (!response.ok || !data.ok) {
83
+ throw new ShieldPayError(
84
+ data.error || `HTTP ${response.status}`,
85
+ data.code || `HTTP_${response.status}`,
86
+ response.status,
87
+ data
88
+ )
89
+ }
90
+
91
+ return data
92
+ }
93
+ }
94
+
95
+ // ─── Payment Module ───────────────────────────────────────────────────────────
96
+
97
+ class PaymentModule {
98
+ constructor(sp) { this._sp = sp }
99
+
100
+ /**
101
+ * Create a PayPal payment order.
102
+ * On Free plan: 2% fee is applied. On Pro: 0% fee.
103
+ *
104
+ * @param {object} params
105
+ * @param {number} params.amount - Amount to charge
106
+ * @param {string} [params.currency] - ISO currency code (default: CHF)
107
+ * @param {string} [params.description]- Order description
108
+ * @param {string} [params.return_url] - URL after successful PayPal payment
109
+ * @param {string} [params.cancel_url] - URL if user cancels PayPal
110
+ *
111
+ * @returns {Promise<{order_id, approve_url, amount, fee, net_amount, currency, plan, fee_note}>}
112
+ *
113
+ * @example
114
+ * const order = await sp.payment.create({ amount: 49.99, currency: 'CHF' })
115
+ * window.location.href = order.approve_url // redirect to PayPal
116
+ */
117
+ async create({ amount, currency = 'CHF', description, return_url, cancel_url } = {}) {
118
+ if (!amount || isNaN(+amount) || +amount <= 0) throw new ShieldPayError('amount must be a positive number', 'INVALID_AMOUNT')
119
+ return this._sp._request('/payment/create', {
120
+ method: 'POST',
121
+ body: JSON.stringify({ amount: +amount, currency, description, return_url, cancel_url }),
122
+ })
123
+ }
124
+
125
+ /**
126
+ * Capture an approved PayPal order.
127
+ * Call this after PayPal redirects back to your return_url.
128
+ *
129
+ * @param {string} orderId - PayPal order ID from create()
130
+ * @returns {Promise<{captured, order_id, status, amount, payer}>}
131
+ */
132
+ async capture(orderId) {
133
+ if (!orderId) throw new ShieldPayError('orderId is required', 'MISSING_PARAM')
134
+ return this._sp._request('/payment/capture', {
135
+ method: 'POST',
136
+ body: JSON.stringify({ order_id: orderId }),
137
+ })
138
+ }
139
+
140
+ /**
141
+ * Get the current status of a PayPal order.
142
+ * @param {string} orderId
143
+ * @returns {Promise<{order_id, status, amount, created, updated}>}
144
+ */
145
+ async status(orderId) {
146
+ if (!orderId) throw new ShieldPayError('orderId is required', 'MISSING_PARAM')
147
+ return this._sp._request(`/payment/status?order_id=${encodeURIComponent(orderId)}`)
148
+ }
149
+
150
+ /**
151
+ * Refund a captured payment.
152
+ * @param {string} captureId - PayPal capture ID
153
+ * @param {number} [amount] - Partial refund amount (omit for full refund)
154
+ * @param {string} [note] - Note to customer
155
+ */
156
+ async refund(captureId, amount, note) {
157
+ if (!captureId) throw new ShieldPayError('captureId is required', 'MISSING_PARAM')
158
+ return this._sp._request('/payment/refund', {
159
+ method: 'POST',
160
+ body: JSON.stringify({ capture_id: captureId, amount, note }),
161
+ })
162
+ }
163
+
164
+ /**
165
+ * List recent transactions.
166
+ * @param {{ limit?: number, status?: string }} options
167
+ */
168
+ async list({ limit = 20, status } = {}) {
169
+ const params = new URLSearchParams({ limit: String(limit) })
170
+ if (status) params.set('status', status)
171
+ return this._sp._request(`/payment/list?${params}`)
172
+ }
173
+ }
174
+
175
+ // ─── Auth Module ──────────────────────────────────────────────────────────────
176
+
177
+ class AuthModule {
178
+ constructor(sp) { this._sp = sp }
179
+
180
+ /**
181
+ * Create a passwordless email login token for one of your end users.
182
+ * Token expires in 15 minutes.
183
+ *
184
+ * @param {string} email - The user's email address
185
+ * @returns {Promise<{token, expires_at}>}
186
+ */
187
+ async createToken(email) {
188
+ if (!email) throw new ShieldPayError('email is required', 'MISSING_PARAM')
189
+ return this._sp._request('/auth/token', {
190
+ method: 'POST',
191
+ body: JSON.stringify({ email }),
192
+ })
193
+ }
194
+
195
+ /**
196
+ * Verify a token your user received by email.
197
+ * Tokens are single-use and expire after 15 minutes.
198
+ *
199
+ * @param {string} token
200
+ * @returns {Promise<{verified, email}>}
201
+ */
202
+ async verifyToken(token) {
203
+ if (!token) throw new ShieldPayError('token is required', 'MISSING_PARAM')
204
+ return this._sp._request('/auth/token/verify', {
205
+ method: 'POST',
206
+ body: JSON.stringify({ token }),
207
+ })
208
+ }
209
+ }
210
+
211
+ // ─── Security Module ──────────────────────────────────────────────────────────
212
+
213
+ class SecurityModule {
214
+ constructor(sp) { this._sp = sp }
215
+
216
+ /**
217
+ * Check if an IP is blocked (VPN, Tor, datacenter, fraud score).
218
+ * Pro plan uses real-time IPQS reputation check.
219
+ *
220
+ * @param {string} ip - IPv4 or IPv6 address
221
+ * @returns {Promise<{blocked, reason, country, score?}>}
222
+ */
223
+ async checkIp(ip) {
224
+ if (!ip) throw new ShieldPayError('ip is required', 'MISSING_PARAM')
225
+ return this._sp._request('/security/check-ip', {
226
+ method: 'POST',
227
+ body: JSON.stringify({ ip }),
228
+ })
229
+ }
230
+
231
+ /**
232
+ * Check if an email is disposable or blocked.
233
+ *
234
+ * @param {string} email
235
+ * @returns {Promise<{blocked, reason}>}
236
+ */
237
+ async checkEmail(email) {
238
+ if (!email) throw new ShieldPayError('email is required', 'MISSING_PARAM')
239
+ return this._sp._request('/security/check-email', {
240
+ method: 'POST',
241
+ body: JSON.stringify({ email }),
242
+ })
243
+ }
244
+ }
245
+
246
+ // ─── Account Module ───────────────────────────────────────────────────────────
247
+
248
+ class AccountModule {
249
+ constructor(sp) { this._sp = sp }
250
+
251
+ /** Get your account info and plan. */
252
+ async me() {
253
+ return this._sp._request('/me')
254
+ }
255
+
256
+ /** List all transactions. */
257
+ async transactions(options = {}) {
258
+ return this._sp._request('/transactions', { method: 'GET' })
259
+ }
260
+
261
+ /** Health check — verify the API is reachable. */
262
+ async health() {
263
+ const res = await fetch(`${this._sp.endpoint('')}health`)
264
+ return res.json()
265
+ }
266
+ }
267
+
268
+ // ─── Static registration (no instance needed) ─────────────────────────────────
269
+
270
+ ShieldPay.register = async function(params, baseUrl = DEFAULT_BASE) {
271
+ const { company, email, password } = params
272
+ if (!company || !email || !password) throw new ShieldPayError('company, email and password are required', 'MISSING_PARAMS')
273
+
274
+ const url = `${baseUrl}/api/auth/register`
275
+ const res = await fetch(url, {
276
+ method: 'POST',
277
+ headers: { 'Content-Type': 'application/json', 'X-SDK-Version': `shieldpay-sdk/${SDK_VERSION}` },
278
+ body: JSON.stringify({ company, email, password }),
279
+ })
280
+ const data = await res.json()
281
+ if (!data.ok) throw new ShieldPayError(data.error, data.code, res.status)
282
+ return data
283
+ }
284
+
285
+ ShieldPay.verifyEmail = async function(token, baseUrl = DEFAULT_BASE) {
286
+ const url = `${baseUrl}/api/auth/verify`
287
+ const res = await fetch(url, {
288
+ method: 'POST',
289
+ headers: { 'Content-Type': 'application/json', 'X-SDK-Version': `shieldpay-sdk/${SDK_VERSION}` },
290
+ body: JSON.stringify({ token }),
291
+ })
292
+ const data = await res.json()
293
+ if (!data.ok) throw new ShieldPayError(data.error, data.code, res.status)
294
+ return data // { api_key, api_endpoint, message }
295
+ }
296
+
297
+ // ─── Error Class ──────────────────────────────────────────────────────────────
298
+
299
+ export class ShieldPayError extends Error {
300
+ /**
301
+ * @param {string} message
302
+ * @param {string} [code] - Machine-readable error code
303
+ * @param {number} [status] - HTTP status code
304
+ * @param {object} [raw] - Full raw API response
305
+ */
306
+ constructor(message, code = 'UNKNOWN', status, raw) {
307
+ super(message)
308
+ this.name = 'ShieldPayError'
309
+ this.code = code
310
+ this.status = status
311
+ this.raw = raw
312
+ }
313
+ }
314
+
315
+ // ─── CommonJS compatibility ───────────────────────────────────────────────────
316
+
317
+ if (typeof module !== 'undefined' && module.exports) {
318
+ module.exports = { ShieldPay, ShieldPayError }
319
+ module.exports.default = ShieldPay
320
+ }
321
+
322
+ export default ShieldPay
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "shieldpay-api-sdkjs",
3
+ "version": "2.0.0",
4
+ "description": "FIXLE SDK for the ShieldPay API — payments, anti-spam, VPN blocking and email auth",
5
+ "main": "index.js",
6
+ "module": "index.js",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./index.js",
10
+ "require": "./index.js"
11
+ }
12
+ },
13
+ "type": "module",
14
+ "scripts": {
15
+ "test": "node test.js"
16
+ },
17
+ "keywords": ["paypal", "payments", "anti-spam", "vpn-detection", "cloudflare-workers", "shieldpay"],
18
+ "author": "Fixle",
19
+ "license": "MIT",
20
+ "homepage": "https://fixle.ch/shieldpay/SDK/docs/",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/FixleCH/shieldpay-sdk"
24
+ },
25
+ "engines": {
26
+ "node": ">=18.0.0"
27
+ }
28
+ }