shieldpay-api-sdkjs 2.0.0 → 2.0.2

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 CHANGED
@@ -1,22 +1,22 @@
1
- # shieldpay-sdk
1
+ # shieldpay-api-sdkjs
2
2
 
3
- Official JavaScript/TypeScript SDK for the [ShieldPay API](https://hnow.info).
3
+ Official JavaScript/TypeScript SDK for the [ShieldPay API](https://fixle.ch/shieldpay/).
4
4
 
5
5
  Anti-spam, VPN blocking, email authentication, and PayPal payments — all in one package.
6
6
 
7
7
  ## Install
8
8
 
9
9
  ```bash
10
- npm install shieldpay-sdk
10
+ npm install shieldpay-api-sdkjs
11
11
  ```
12
12
 
13
13
  ## Quick Start
14
14
 
15
15
  ```js
16
- import { ShieldPay } from 'shieldpay-sdk'
16
+ import { ShieldPay } from 'shieldpay-api-sdkjs'
17
17
 
18
18
  const sp = new ShieldPay({ apiKey: 'sk_live_YOUR_KEY' })
19
- // All calls go to: https://hnow.info/api/worker=sk_live_YOUR_KEY/...
19
+ // All calls go to: https://fixle.ch/shieldpay/api/worker=sk_live_YOUR_KEY/...
20
20
 
21
21
  // Create a PayPal payment
22
22
  const order = await sp.payment.create({
@@ -43,9 +43,9 @@ const { verified } = await sp.auth.verifyToken(token)
43
43
  Your API key is embedded in the URL — no headers needed:
44
44
 
45
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
46
+ https://fixle.ch/shieldpay/api/worker=sk_live_YOUR_KEY/payment/create
47
+ https://fixle.ch/shieldpay/api/worker=sk_live_YOUR_KEY/security/check-ip
48
+ https://fixle.ch/shieldpay/api/worker=sk_live_YOUR_KEY/me
49
49
  ```
50
50
 
51
51
  ## Plans
@@ -60,4 +60,4 @@ https://hnow.info/api/worker=sk_live_YOUR_KEY/me
60
60
 
61
61
  ## Docs
62
62
 
63
- Full documentation: [https://hnow.info/SDK/docs/](https://hnow.info/SDK/docs/)
63
+ Full documentation: [https://fixle.ch/shieldpay/SDK/docs/](https://fixle.ch/shieldpay/SDK/docs/)
package/index.js CHANGED
@@ -1,18 +1,18 @@
1
1
  /**
2
- * shieldpay-sdk v2.0.0
2
+ * shieldpay-api-sdkjs v1.1.0
3
3
  * Official SDK for the ShieldPay API
4
4
  *
5
- * Install: npm install shieldpay-sdk
6
- * Docs: https://hnow.info/SDK/docs/
5
+ * Install: npm i shieldpay-api-sdkjs
6
+ * Docs: https://fixle.ch/shieldpay/SDK/docs/
7
7
  *
8
8
  * Usage:
9
- * import { ShieldPay } from 'shieldpay-sdk'
9
+ * import { ShieldPay } from 'shieldpay-api-sdkjs'
10
10
  * const sp = new ShieldPay({ apiKey: 'sk_live_xxx' })
11
- * // SDK calls: https://hnow.info/api/worker=sk_live_xxx/{endpoint}
11
+ * // SDK calls: https://fixle.ch/shieldpay/api/worker=sk_live_xxx/{endpoint}
12
12
  */
13
13
 
14
- const SDK_VERSION = '2.0.0'
15
- const DEFAULT_BASE = 'https://hnow.info'
14
+ const SDK_VERSION = '1.1.0'
15
+ const DEFAULT_BASE = 'https://fixle.ch/shieldpay'
16
16
 
17
17
  // ─── Main class ───────────────────────────────────────────────────────────────
18
18
 
@@ -42,7 +42,7 @@ export class ShieldPay {
42
42
 
43
43
  /**
44
44
  * Get the full API endpoint URL for a path.
45
- * Format: https://hnow.info/api/worker={KEY}/{path}
45
+ * Format: https://fixle.ch/shieldpay/api/worker={KEY}/{path}
46
46
  */
47
47
  endpoint(path = '') {
48
48
  return `${this.#baseUrl}/api/worker=${encodeURIComponent(this.#apiKey)}${path}`
@@ -65,7 +65,7 @@ export class ShieldPay {
65
65
  ...options,
66
66
  headers: {
67
67
  'Content-Type': 'application/json',
68
- 'X-SDK-Version': `shieldpay-sdk/${SDK_VERSION}`,
68
+ 'X-SDK-Version': `shieldpay-api-sdkjs/${SDK_VERSION}`,
69
69
  ...(options.headers || {}),
70
70
  },
71
71
  signal: controller.signal,
@@ -274,7 +274,7 @@ ShieldPay.register = async function(params, baseUrl = DEFAULT_BASE) {
274
274
  const url = `${baseUrl}/api/auth/register`
275
275
  const res = await fetch(url, {
276
276
  method: 'POST',
277
- headers: { 'Content-Type': 'application/json', 'X-SDK-Version': `shieldpay-sdk/${SDK_VERSION}` },
277
+ headers: { 'Content-Type': 'application/json', 'X-SDK-Version': `shieldpay-api-sdkjs/${SDK_VERSION}` },
278
278
  body: JSON.stringify({ company, email, password }),
279
279
  })
280
280
  const data = await res.json()
@@ -286,7 +286,7 @@ ShieldPay.verifyEmail = async function(token, baseUrl = DEFAULT_BASE) {
286
286
  const url = `${baseUrl}/api/auth/verify`
287
287
  const res = await fetch(url, {
288
288
  method: 'POST',
289
- headers: { 'Content-Type': 'application/json', 'X-SDK-Version': `shieldpay-sdk/${SDK_VERSION}` },
289
+ headers: { 'Content-Type': 'application/json', 'X-SDK-Version': `shieldpay-api-sdkjs/${SDK_VERSION}` },
290
290
  body: JSON.stringify({ token }),
291
291
  })
292
292
  const data = await res.json()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shieldpay-api-sdkjs",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "FIXLE SDK for the ShieldPay API — payments, anti-spam, VPN blocking and email auth",
5
5
  "main": "index.js",
6
6
  "module": "index.js",
@@ -14,13 +14,20 @@
14
14
  "scripts": {
15
15
  "test": "node test.js"
16
16
  },
17
- "keywords": ["paypal", "payments", "anti-spam", "vpn-detection", "cloudflare-workers", "shieldpay"],
17
+ "keywords": [
18
+ "paypal",
19
+ "payments",
20
+ "anti-spam",
21
+ "vpn-detection",
22
+ "cloudflare-workers",
23
+ "shieldpay"
24
+ ],
18
25
  "author": "Fixle",
19
26
  "license": "MIT",
20
27
  "homepage": "https://fixle.ch/shieldpay/SDK/docs/",
21
28
  "repository": {
22
29
  "type": "git",
23
- "url": "https://github.com/FixleCH/shieldpay-sdk"
30
+ "url": "https://github.com/FixleCH/shieldpay-api-sdkjs"
24
31
  },
25
32
  "engines": {
26
33
  "node": ">=18.0.0"
package/docs/index.html DELETED
@@ -1,412 +0,0 @@
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>