@veloris/stripe 1.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,241 @@
1
+ # @veloris/stripe
2
+
3
+ Veloris Shield + Stripe integration for React and Node.js.
4
+
5
+ Evaluates every transaction with [Veloris Shield](https://veloris.app) before confirming a Stripe PaymentIntent — blocking fraud before it incurs interchange fees.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @veloris/stripe
11
+ # or
12
+ yarn add @veloris/stripe
13
+ ```
14
+
15
+ ## How it works
16
+
17
+ ```
18
+ Browser
19
+ 1. Shield.js collects device fingerprint
20
+ 2. Stripe.js collects card → PaymentMethod
21
+ 3. POST { paymentMethodId, fingerprint } to your backend
22
+
23
+ Your backend
24
+ 4. POST /shield/evaluate → verdict (APPROVE / FLAG / CHALLENGE / DECLINE)
25
+ 5. APPROVE/FLAG → confirm Stripe PaymentIntent
26
+ 6. CHALLENGE → confirm with request_three_d_secure: 'any'
27
+ 7. DECLINE → return error; never charge Stripe
28
+ ```
29
+
30
+ Your secret key (`vsk_…`) stays on the server. The publishable key (`vpk_…`) is safe in the browser.
31
+
32
+ ---
33
+
34
+ ## React usage
35
+
36
+ ### 1. Wrap your checkout with the provider
37
+
38
+ ```tsx
39
+ import { loadStripe } from '@stripe/stripe-js';
40
+ import { Elements } from '@stripe/react-stripe-js';
41
+ import { VelorisStripeProvider } from '@veloris/stripe/react';
42
+
43
+ const stripePromise = loadStripe('pk_live_…');
44
+
45
+ export default function App() {
46
+ const stripe = useStripe(); // or pass the resolved Stripe instance
47
+
48
+ return (
49
+ <Elements stripe={stripePromise}>
50
+ <StripeInner />
51
+ </Elements>
52
+ );
53
+ }
54
+
55
+ function StripeInner() {
56
+ const stripe = useStripe();
57
+ const elements = useElements();
58
+
59
+ if (!stripe || !elements) return null;
60
+
61
+ return (
62
+ <VelorisStripeProvider options={{ publicKey: 'vpk_…', stripe, elements }}>
63
+ <CheckoutForm />
64
+ </VelorisStripeProvider>
65
+ );
66
+ }
67
+ ```
68
+
69
+ ### 2. Use the hook in your checkout form
70
+
71
+ ```tsx
72
+ import { useVelorisStripe } from '@veloris/stripe/react';
73
+ import { useStripe, useElements, CardElement } from '@stripe/react-stripe-js';
74
+
75
+ export function CheckoutForm() {
76
+ const stripe = useStripe();
77
+ const elements = useElements();
78
+ const { ready, getPayload } = useVelorisStripe();
79
+ const [error, setError] = useState('');
80
+
81
+ async function handleSubmit(e: React.FormEvent) {
82
+ e.preventDefault();
83
+
84
+ const [{ fingerprint }, { paymentMethod, error: pmError }] = await Promise.all([
85
+ getPayload(),
86
+ stripe!.createPaymentMethod({ type: 'card', card: elements!.getElement(CardElement)! }),
87
+ ]);
88
+
89
+ if (pmError) { setError(pmError.message); return; }
90
+
91
+ const res = await fetch('/checkout', {
92
+ method: 'POST',
93
+ headers: { 'Content-Type': 'application/json' },
94
+ body: JSON.stringify({ paymentMethodId: paymentMethod!.id, fingerprint }),
95
+ });
96
+
97
+ const data = await res.json();
98
+ if (!res.ok) setError(data.error);
99
+ }
100
+
101
+ return (
102
+ <form onSubmit={handleSubmit}>
103
+ <CardElement />
104
+ {error && <p>{error}</p>}
105
+ <button type="submit" disabled={!ready}>Pay</button>
106
+ </form>
107
+ );
108
+ }
109
+ ```
110
+
111
+ ### Alternative: useVelorisAutoHook
112
+
113
+ For a lower-boilerplate option, `useVelorisAutoHook` intercepts the form submit event automatically:
114
+
115
+ ```tsx
116
+ import { useVelorisAutoHook } from '@veloris/stripe/react';
117
+
118
+ function CheckoutForm() {
119
+ const formRef = useRef<HTMLFormElement>(null);
120
+
121
+ useVelorisAutoHook({
122
+ formRef,
123
+ handler: async ({ paymentMethodId, fingerprint }) => {
124
+ await fetch('/checkout', {
125
+ method: 'POST',
126
+ headers: { 'Content-Type': 'application/json' },
127
+ body: JSON.stringify({ paymentMethodId, fingerprint }),
128
+ });
129
+ },
130
+ });
131
+
132
+ return (
133
+ <form ref={formRef}>
134
+ <CardElement />
135
+ <button type="submit">Pay</button>
136
+ </form>
137
+ );
138
+ }
139
+ ```
140
+
141
+ ---
142
+
143
+ ## Vanilla JS / Next.js (no provider)
144
+
145
+ ```ts
146
+ import { VelorisStripe } from '@veloris/stripe';
147
+
148
+ const veloris = new VelorisStripe({
149
+ publicKey: 'vpk_…',
150
+ stripe, // Stripe instance
151
+ elements, // Stripe Elements instance
152
+ });
153
+
154
+ // Option A: get payload manually
155
+ const { fingerprint } = await veloris.getPayload();
156
+
157
+ // Option B: hook into form submit automatically
158
+ veloris.autoHook(formEl, async ({ paymentMethodId, fingerprint }) => {
159
+ await fetch('/checkout', {
160
+ method: 'POST',
161
+ body: JSON.stringify({ paymentMethodId, fingerprint }),
162
+ });
163
+ });
164
+ ```
165
+
166
+ ---
167
+
168
+ ## Backend — Node.js example
169
+
170
+ ```js
171
+ const Stripe = require('stripe');
172
+ const axios = require('axios');
173
+
174
+ const stripe = Stripe(process.env.STRIPE_SECRET_KEY);
175
+
176
+ app.post('/checkout', async (req, res) => {
177
+ const { paymentMethodId, fingerprint, orderId, amount, currency, email } = req.body;
178
+
179
+ // 1. Evaluate with Shield
180
+ const { data } = await axios.post(
181
+ 'https://api.veloris.app/v1/shield/evaluate',
182
+ {
183
+ order_id: orderId,
184
+ amount,
185
+ currency,
186
+ customer: { email },
187
+ device: { ip_address: req.ip, fingerprint },
188
+ },
189
+ { headers: { 'X-Veloris-API-Key': process.env.VELORIS_SECRET_KEY } }
190
+ );
191
+
192
+ if (data.verdict === 'DECLINE') {
193
+ return res.status(402).json({ error: 'Transaction declined.' });
194
+ }
195
+
196
+ // 2. Confirm with Stripe
197
+ const intent = await stripe.paymentIntents.create({
198
+ amount,
199
+ currency: currency.toLowerCase(),
200
+ payment_method: paymentMethodId,
201
+ confirm: true,
202
+ payment_method_options: {
203
+ card: { request_three_d_secure: data.verdict === 'CHALLENGE' ? 'any' : 'automatic' },
204
+ },
205
+ metadata: { veloris_evaluation_id: data.evaluation_id },
206
+ });
207
+
208
+ res.json({ clientSecret: intent.client_secret });
209
+ });
210
+ ```
211
+
212
+ ---
213
+
214
+ ## API reference
215
+
216
+ ### `new VelorisStripe(options)`
217
+
218
+ | Option | Type | Required | Description |
219
+ |---|---|---|---|
220
+ | `publicKey` | `string` | ✓ | Publishable key (`vpk_…`) from your Veloris dashboard |
221
+ | `stripe` | `Stripe` | ✓ | Stripe.js instance |
222
+ | `elements` | `StripeElements` | For `autoHook` | Stripe Elements instance |
223
+ | `onVerdict` | `fn(verdict, evaluationId)` | | Called after each verdict |
224
+ | `debug` | `boolean` | | Log debug info to console |
225
+
226
+ ### `.init()` → `Promise<void>`
227
+ Loads Shield.js and initialises signal collection. Called automatically by `getPayload()`.
228
+
229
+ ### `.getPayload()` → `Promise<{ fingerprint, fp_version }>`
230
+ Returns the device fingerprint. Safe to call multiple times.
231
+
232
+ ### `.autoHook(formEl, handler, billingDetails?)` → `void`
233
+ Intercepts form submit, collects fingerprint + PaymentMethod, calls `handler({ paymentMethodId, fingerprint, fp_version })`.
234
+
235
+ ---
236
+
237
+ ## Links
238
+
239
+ - [Veloris Dashboard](https://dashboard.veloris.app)
240
+ - [Full documentation](https://veloris.app/docs/stripe)
241
+ - [API reference](https://veloris.app/docs/api)
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";exports.VelorisStripe=class{constructor(e){if(this.shield=null,this.initPromise=null,!e.publicKey)throw new Error("[VelorisStripe] publicKey is required");if(!e.stripe)throw new Error("[VelorisStripe] stripe instance is required");this.publicKey=e.publicKey,this.stripe=e.stripe,this.elements=e.elements,this.onVerdict=e.onVerdict,this.debug=e.debug??!1}log(...e){this.debug&&console.log("[VelorisStripe]",...e)}init(){return this.initPromise||(this.initPromise=(e="https://dashboard.veloris.app/shield.min.js",new Promise((i,t)=>{if("undefined"==typeof document)return void i();if(document.querySelector(`script[src="${e}"]`))return void i();const r=document.createElement("script");r.src=e,r.onload=()=>i(),r.onerror=()=>t(new Error(`Failed to load ${e}`)),document.head.appendChild(r)})).then(()=>{if(!window.VelorisShield)throw new Error("Shield.js did not load");return this.shield=new window.VelorisShield.VelorisShield({publicKey:this.publicKey}),this.shield.init()}).then(()=>{this.log("ready")})),this.initPromise;var e}async getPayload(){await this.init();try{const e=await this.shield.getAPIPayload(),i={fingerprint:e.fingerprint??null,fp_version:e.fp_version??"0.1.0"};return this.log("payload",i),i}catch(e){return this.log("getPayload error (non-fatal):",e),{fingerprint:null,fp_version:"0.1.0"}}}autoHook(e,i,t){if(!e)throw new Error("[VelorisStripe] autoHook: formEl is required");if(!i)throw new Error("[VelorisStripe] autoHook: handler is required");if(!this.elements)throw new Error("[VelorisStripe] autoHook: pass elements via options.elements");this.init().catch(e=>this.log("init error:",e)),e.addEventListener("submit",r=>{r.preventDefault();const o=e.querySelector("[type=submit]");o&&(o.disabled=!0),Promise.all([this.getPayload(),this.stripe.createPaymentMethod({type:"card",card:this.elements.getElement("card")??this.elements.getElement("cardNumber"),billing_details:t??{}})]).then(([e,t])=>{if(t.error)throw new Error(t.error.message);return i({paymentMethodId:t.paymentMethod.id,fingerprint:e.fingerprint,fp_version:e.fp_version})}).catch(i=>{this.log("autoHook error:",i),o&&(o.disabled=!1),e.dispatchEvent(new CustomEvent("veloris:error",{detail:{message:i.message},bubbles:!0}))})}),this.log("autoHook attached to",e)}};
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":["../src/index.ts"],"sourcesContent":["const SHIELD_SDK_URL = 'https://dashboard.veloris.app/shield.min.js';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport interface VelorisStripeOptions {\n /** Publishable key (vpk_…) from your Veloris dashboard */\n publicKey: string;\n /** Stripe.js instance returned by loadStripe() or window.Stripe() */\n stripe: Stripe;\n /** Stripe Elements instance (required for autoHook) */\n elements?: StripeElements;\n /** Called after every evaluation with verdict and evaluation_id */\n onVerdict?: (verdict: string, evaluationId: string) => void;\n /** Log debug info to console */\n debug?: boolean;\n}\n\nexport interface VelorisPayload {\n fingerprint: string | null;\n fp_version: string;\n}\n\nexport interface AutoHookPayload {\n paymentMethodId: string;\n fingerprint: string | null;\n fp_version: string;\n}\n\n// Minimal Stripe typings so consumers don't need @stripe/stripe-js as a dep\ninterface Stripe {\n createPaymentMethod(opts: Record<string, unknown>): Promise<{ paymentMethod?: { id: string }; error?: { message: string } }>;\n}\ninterface StripeElements {\n getElement(type: string): unknown;\n}\n\n// Shield.js global\ninterface VelorisShieldSDK {\n new(opts: { publicKey: string }): {\n init(): Promise<void>;\n getAPIPayload(): Promise<{ fingerprint?: string; fp_version?: string }>;\n };\n}\ndeclare global {\n interface Window {\n VelorisShield?: { VelorisShield: VelorisShieldSDK };\n }\n}\n\n// ── Loader ────────────────────────────────────────────────────────────────────\n\nfunction loadScript(src: string): Promise<void> {\n return new Promise((resolve, reject) => {\n if (typeof document === 'undefined') { resolve(); return; }\n if (document.querySelector(`script[src=\"${src}\"]`)) { resolve(); return; }\n const s = document.createElement('script');\n s.src = src;\n s.onload = () => resolve();\n s.onerror = () => reject(new Error(`Failed to load ${src}`));\n document.head.appendChild(s);\n });\n}\n\n// ── VelorisStripe class ───────────────────────────────────────────────────────\n\nexport class VelorisStripe {\n private readonly publicKey: string;\n private readonly stripe: Stripe;\n private readonly elements?: StripeElements;\n private readonly onVerdict?: (verdict: string, evaluationId: string) => void;\n private readonly debug: boolean;\n\n private shield: InstanceType<VelorisShieldSDK> | null = null;\n private initPromise: Promise<void> | null = null;\n\n constructor(options: VelorisStripeOptions) {\n if (!options.publicKey) throw new Error('[VelorisStripe] publicKey is required');\n if (!options.stripe) throw new Error('[VelorisStripe] stripe instance is required');\n\n this.publicKey = options.publicKey;\n this.stripe = options.stripe;\n this.elements = options.elements;\n this.onVerdict = options.onVerdict;\n this.debug = options.debug ?? false;\n }\n\n private log(...args: unknown[]): void {\n if (this.debug) console.log('[VelorisStripe]', ...args);\n }\n\n /**\n * Load Shield.js and initialise signal collection.\n * Safe to call multiple times — returns the same promise.\n */\n init(): Promise<void> {\n if (this.initPromise) return this.initPromise;\n this.initPromise = loadScript(SHIELD_SDK_URL).then(() => {\n if (!window.VelorisShield) throw new Error('Shield.js did not load');\n this.shield = new window.VelorisShield.VelorisShield({ publicKey: this.publicKey });\n return this.shield.init();\n }).then(() => {\n this.log('ready');\n });\n return this.initPromise;\n }\n\n /**\n * Returns the device fingerprint payload to send to your backend.\n * Call this just before submitting the order.\n */\n async getPayload(): Promise<VelorisPayload> {\n await this.init();\n try {\n const p = await this.shield!.getAPIPayload();\n const payload: VelorisPayload = {\n fingerprint: p.fingerprint ?? null,\n fp_version: p.fp_version ?? '0.1.0',\n };\n this.log('payload', payload);\n return payload;\n } catch (err) {\n this.log('getPayload error (non-fatal):', err);\n return { fingerprint: null, fp_version: '0.1.0' };\n }\n }\n\n /**\n * Intercepts a checkout form's submit event.\n * Collects fingerprint and Stripe PaymentMethod in parallel, then calls\n * handler({ paymentMethodId, fingerprint, fp_version }).\n *\n * Your handler should POST to your backend, which calls /shield/evaluate\n * then confirms the Stripe PaymentIntent.\n */\n autoHook(\n formEl: HTMLFormElement,\n handler: (payload: AutoHookPayload) => Promise<void>,\n billingDetails?: Record<string, unknown>,\n ): void {\n if (!formEl) throw new Error('[VelorisStripe] autoHook: formEl is required');\n if (!handler) throw new Error('[VelorisStripe] autoHook: handler is required');\n if (!this.elements) throw new Error('[VelorisStripe] autoHook: pass elements via options.elements');\n\n // Warm up Shield.js immediately\n this.init().catch(e => this.log('init error:', e));\n\n formEl.addEventListener('submit', (e: Event) => {\n e.preventDefault();\n const submitBtn = formEl.querySelector<HTMLButtonElement>('[type=submit]');\n if (submitBtn) submitBtn.disabled = true;\n\n Promise.all([\n this.getPayload(),\n this.stripe.createPaymentMethod({\n type: 'card',\n card: this.elements!.getElement('card') ?? this.elements!.getElement('cardNumber'),\n billing_details: billingDetails ?? {},\n }),\n ]).then(([velorisPayload, pmResult]) => {\n if (pmResult.error) throw new Error(pmResult.error.message);\n return handler({\n paymentMethodId: pmResult.paymentMethod!.id,\n fingerprint: velorisPayload.fingerprint,\n fp_version: velorisPayload.fp_version,\n });\n }).catch(err => {\n this.log('autoHook error:', err);\n if (submitBtn) submitBtn.disabled = false;\n formEl.dispatchEvent(\n new CustomEvent('veloris:error', { detail: { message: (err as Error).message }, bubbles: true })\n );\n });\n });\n\n this.log('autoHook attached to', formEl);\n }\n}\n"],"names":["constructor","options","this","shield","initPromise","publicKey","Error","stripe","elements","onVerdict","debug","log","args","console","init","src","Promise","resolve","reject","document","querySelector","s","createElement","onload","onerror","head","appendChild","then","window","VelorisShield","getPayload","p","getAPIPayload","payload","fingerprint","fp_version","err","autoHook","formEl","handler","billingDetails","catch","e","addEventListener","preventDefault","submitBtn","disabled","all","createPaymentMethod","type","card","getElement","billing_details","velorisPayload","pmResult","error","message","paymentMethodId","paymentMethod","id","dispatchEvent","CustomEvent","detail","bubbles"],"mappings":"yCA2EE,WAAAA,CAAYC,GACV,GAJMC,KAAAC,OAAqD,KACrDD,KAAAE,YAAqD,MAGtDH,EAAQI,UAAW,MAAM,IAAIC,MAAM,yCACxC,IAAKL,EAAQM,OAAW,MAAM,IAAID,MAAM,+CAExCJ,KAAKG,UAAaJ,EAAQI,UAC1BH,KAAKK,OAAaN,EAAQM,OAC1BL,KAAKM,SAAaP,EAAQO,SAC1BN,KAAKO,UAAaR,EAAQQ,UAC1BP,KAAKQ,MAAaT,EAAQS,QAAS,CACrC,CAEQ,GAAAC,IAAOC,GACTV,KAAKQ,OAAOG,QAAQF,IAAI,qBAAsBC,EACpD,CAMA,IAAAE,GACE,OAAIZ,KAAKE,cACTF,KAAKE,aA7CWW,EAnDG,8CAoDd,IAAIC,QAAQ,CAACC,EAASC,KAC3B,GAAwB,oBAAbC,SAAuC,YAAXF,IACvC,GAAIE,SAASC,cAAc,eAAeL,OAAuB,YAAXE,IACtD,MAAMI,EAAUF,SAASG,cAAc,UACvCD,EAAEN,IAAcA,EAChBM,EAAEE,OAAc,IAAMN,IACtBI,EAAEG,QAAc,IAAMN,EAAO,IAAIZ,MAAM,kBAAkBS,MACzDI,SAASM,KAAKC,YAAYL,MAqCoBM,KAAK,KACjD,IAAKC,OAAOC,cAAe,MAAM,IAAIvB,MAAM,0BAE3C,OADAJ,KAAKC,OAAS,IAAIyB,OAAOC,cAAcA,cAAc,CAAExB,UAAWH,KAAKG,YAChEH,KAAKC,OAAOW,SAClBa,KAAK,KACNzB,KAAKS,IAAI,YANkBT,KAAKE,YA5CtC,IAAoBW,CAqDlB,CAMA,gBAAMe,SACE5B,KAAKY,OACX,IACE,MAAMiB,QAAU7B,KAAKC,OAAQ6B,gBACvBC,EAA0B,CAC9BC,YAAaH,EAAEG,aAAe,KAC9BC,WAAaJ,EAAEI,YAAe,SAGhC,OADAjC,KAAKS,IAAI,UAAWsB,GACbA,CACT,CAAE,MAAOG,GAEP,OADAlC,KAAKS,IAAI,gCAAiCyB,GACnC,CAAEF,YAAa,KAAMC,WAAY,QAC1C,CACF,CAUA,QAAAE,CACEC,EACAC,EACAC,GAEA,IAAKF,EAAW,MAAM,IAAIhC,MAAM,gDAChC,IAAKiC,EAAW,MAAM,IAAIjC,MAAM,iDAChC,IAAKJ,KAAKM,SAAU,MAAM,IAAIF,MAAM,gEAGpCJ,KAAKY,OAAO2B,MAAMC,GAAKxC,KAAKS,IAAI,cAAe+B,IAE/CJ,EAAOK,iBAAiB,SAAWD,IACjCA,EAAEE,iBACF,MAAMC,EAAYP,EAAOlB,cAAiC,iBACtDyB,IAAWA,EAAUC,UAAW,GAEpC9B,QAAQ+B,IAAI,CACV7C,KAAK4B,aACL5B,KAAKK,OAAOyC,oBAAoB,CAC9BC,KAAM,OACNC,KAAMhD,KAAKM,SAAU2C,WAAW,SAAWjD,KAAKM,SAAU2C,WAAW,cACrEC,gBAAiBZ,GAAkB,CAAA,MAEpCb,KAAK,EAAE0B,EAAgBC,MACxB,GAAIA,EAASC,MAAO,MAAM,IAAIjD,MAAMgD,EAASC,MAAMC,SACnD,OAAOjB,EAAQ,CACbkB,gBAAiBH,EAASI,cAAeC,GACzCzB,YAAiBmB,EAAenB,YAChCC,WAAiBkB,EAAelB,eAEjCM,MAAML,IACPlC,KAAKS,IAAI,kBAAmByB,GACxBS,IAAWA,EAAUC,UAAW,GACpCR,EAAOsB,cACL,IAAIC,YAAY,gBAAiB,CAAEC,OAAQ,CAAEN,QAAUpB,EAAcoB,SAAWO,SAAS,SAK/F7D,KAAKS,IAAI,uBAAwB2B,EACnC"}
@@ -0,0 +1,83 @@
1
+ export interface VelorisStripeOptions {
2
+ /** Publishable key (vpk_…) from your Veloris dashboard */
3
+ publicKey: string;
4
+ /** Stripe.js instance returned by loadStripe() or window.Stripe() */
5
+ stripe: Stripe;
6
+ /** Stripe Elements instance (required for autoHook) */
7
+ elements?: StripeElements;
8
+ /** Called after every evaluation with verdict and evaluation_id */
9
+ onVerdict?: (verdict: string, evaluationId: string) => void;
10
+ /** Log debug info to console */
11
+ debug?: boolean;
12
+ }
13
+ export interface VelorisPayload {
14
+ fingerprint: string | null;
15
+ fp_version: string;
16
+ }
17
+ export interface AutoHookPayload {
18
+ paymentMethodId: string;
19
+ fingerprint: string | null;
20
+ fp_version: string;
21
+ }
22
+ interface Stripe {
23
+ createPaymentMethod(opts: Record<string, unknown>): Promise<{
24
+ paymentMethod?: {
25
+ id: string;
26
+ };
27
+ error?: {
28
+ message: string;
29
+ };
30
+ }>;
31
+ }
32
+ interface StripeElements {
33
+ getElement(type: string): unknown;
34
+ }
35
+ interface VelorisShieldSDK {
36
+ new (opts: {
37
+ publicKey: string;
38
+ }): {
39
+ init(): Promise<void>;
40
+ getAPIPayload(): Promise<{
41
+ fingerprint?: string;
42
+ fp_version?: string;
43
+ }>;
44
+ };
45
+ }
46
+ declare global {
47
+ interface Window {
48
+ VelorisShield?: {
49
+ VelorisShield: VelorisShieldSDK;
50
+ };
51
+ }
52
+ }
53
+ export declare class VelorisStripe {
54
+ private readonly publicKey;
55
+ private readonly stripe;
56
+ private readonly elements?;
57
+ private readonly onVerdict?;
58
+ private readonly debug;
59
+ private shield;
60
+ private initPromise;
61
+ constructor(options: VelorisStripeOptions);
62
+ private log;
63
+ /**
64
+ * Load Shield.js and initialise signal collection.
65
+ * Safe to call multiple times — returns the same promise.
66
+ */
67
+ init(): Promise<void>;
68
+ /**
69
+ * Returns the device fingerprint payload to send to your backend.
70
+ * Call this just before submitting the order.
71
+ */
72
+ getPayload(): Promise<VelorisPayload>;
73
+ /**
74
+ * Intercepts a checkout form's submit event.
75
+ * Collects fingerprint and Stripe PaymentMethod in parallel, then calls
76
+ * handler({ paymentMethodId, fingerprint, fp_version }).
77
+ *
78
+ * Your handler should POST to your backend, which calls /shield/evaluate
79
+ * then confirms the Stripe PaymentIntent.
80
+ */
81
+ autoHook(formEl: HTMLFormElement, handler: (payload: AutoHookPayload) => Promise<void>, billingDetails?: Record<string, unknown>): void;
82
+ }
83
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ class e{constructor(e){if(this.shield=null,this.initPromise=null,!e.publicKey)throw new Error("[VelorisStripe] publicKey is required");if(!e.stripe)throw new Error("[VelorisStripe] stripe instance is required");this.publicKey=e.publicKey,this.stripe=e.stripe,this.elements=e.elements,this.onVerdict=e.onVerdict,this.debug=e.debug??!1}log(...e){this.debug&&console.log("[VelorisStripe]",...e)}init(){return this.initPromise||(this.initPromise=(e="https://dashboard.veloris.app/shield.min.js",new Promise((i,t)=>{if("undefined"==typeof document)return void i();if(document.querySelector(`script[src="${e}"]`))return void i();const r=document.createElement("script");r.src=e,r.onload=()=>i(),r.onerror=()=>t(new Error(`Failed to load ${e}`)),document.head.appendChild(r)})).then(()=>{if(!window.VelorisShield)throw new Error("Shield.js did not load");return this.shield=new window.VelorisShield.VelorisShield({publicKey:this.publicKey}),this.shield.init()}).then(()=>{this.log("ready")})),this.initPromise;var e}async getPayload(){await this.init();try{const e=await this.shield.getAPIPayload(),i={fingerprint:e.fingerprint??null,fp_version:e.fp_version??"0.1.0"};return this.log("payload",i),i}catch(e){return this.log("getPayload error (non-fatal):",e),{fingerprint:null,fp_version:"0.1.0"}}}autoHook(e,i,t){if(!e)throw new Error("[VelorisStripe] autoHook: formEl is required");if(!i)throw new Error("[VelorisStripe] autoHook: handler is required");if(!this.elements)throw new Error("[VelorisStripe] autoHook: pass elements via options.elements");this.init().catch(e=>this.log("init error:",e)),e.addEventListener("submit",r=>{r.preventDefault();const o=e.querySelector("[type=submit]");o&&(o.disabled=!0),Promise.all([this.getPayload(),this.stripe.createPaymentMethod({type:"card",card:this.elements.getElement("card")??this.elements.getElement("cardNumber"),billing_details:t??{}})]).then(([e,t])=>{if(t.error)throw new Error(t.error.message);return i({paymentMethodId:t.paymentMethod.id,fingerprint:e.fingerprint,fp_version:e.fp_version})}).catch(i=>{this.log("autoHook error:",i),o&&(o.disabled=!1),e.dispatchEvent(new CustomEvent("veloris:error",{detail:{message:i.message},bubbles:!0}))})}),this.log("autoHook attached to",e)}}export{e as VelorisStripe};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["const SHIELD_SDK_URL = 'https://dashboard.veloris.app/shield.min.js';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport interface VelorisStripeOptions {\n /** Publishable key (vpk_…) from your Veloris dashboard */\n publicKey: string;\n /** Stripe.js instance returned by loadStripe() or window.Stripe() */\n stripe: Stripe;\n /** Stripe Elements instance (required for autoHook) */\n elements?: StripeElements;\n /** Called after every evaluation with verdict and evaluation_id */\n onVerdict?: (verdict: string, evaluationId: string) => void;\n /** Log debug info to console */\n debug?: boolean;\n}\n\nexport interface VelorisPayload {\n fingerprint: string | null;\n fp_version: string;\n}\n\nexport interface AutoHookPayload {\n paymentMethodId: string;\n fingerprint: string | null;\n fp_version: string;\n}\n\n// Minimal Stripe typings so consumers don't need @stripe/stripe-js as a dep\ninterface Stripe {\n createPaymentMethod(opts: Record<string, unknown>): Promise<{ paymentMethod?: { id: string }; error?: { message: string } }>;\n}\ninterface StripeElements {\n getElement(type: string): unknown;\n}\n\n// Shield.js global\ninterface VelorisShieldSDK {\n new(opts: { publicKey: string }): {\n init(): Promise<void>;\n getAPIPayload(): Promise<{ fingerprint?: string; fp_version?: string }>;\n };\n}\ndeclare global {\n interface Window {\n VelorisShield?: { VelorisShield: VelorisShieldSDK };\n }\n}\n\n// ── Loader ────────────────────────────────────────────────────────────────────\n\nfunction loadScript(src: string): Promise<void> {\n return new Promise((resolve, reject) => {\n if (typeof document === 'undefined') { resolve(); return; }\n if (document.querySelector(`script[src=\"${src}\"]`)) { resolve(); return; }\n const s = document.createElement('script');\n s.src = src;\n s.onload = () => resolve();\n s.onerror = () => reject(new Error(`Failed to load ${src}`));\n document.head.appendChild(s);\n });\n}\n\n// ── VelorisStripe class ───────────────────────────────────────────────────────\n\nexport class VelorisStripe {\n private readonly publicKey: string;\n private readonly stripe: Stripe;\n private readonly elements?: StripeElements;\n private readonly onVerdict?: (verdict: string, evaluationId: string) => void;\n private readonly debug: boolean;\n\n private shield: InstanceType<VelorisShieldSDK> | null = null;\n private initPromise: Promise<void> | null = null;\n\n constructor(options: VelorisStripeOptions) {\n if (!options.publicKey) throw new Error('[VelorisStripe] publicKey is required');\n if (!options.stripe) throw new Error('[VelorisStripe] stripe instance is required');\n\n this.publicKey = options.publicKey;\n this.stripe = options.stripe;\n this.elements = options.elements;\n this.onVerdict = options.onVerdict;\n this.debug = options.debug ?? false;\n }\n\n private log(...args: unknown[]): void {\n if (this.debug) console.log('[VelorisStripe]', ...args);\n }\n\n /**\n * Load Shield.js and initialise signal collection.\n * Safe to call multiple times — returns the same promise.\n */\n init(): Promise<void> {\n if (this.initPromise) return this.initPromise;\n this.initPromise = loadScript(SHIELD_SDK_URL).then(() => {\n if (!window.VelorisShield) throw new Error('Shield.js did not load');\n this.shield = new window.VelorisShield.VelorisShield({ publicKey: this.publicKey });\n return this.shield.init();\n }).then(() => {\n this.log('ready');\n });\n return this.initPromise;\n }\n\n /**\n * Returns the device fingerprint payload to send to your backend.\n * Call this just before submitting the order.\n */\n async getPayload(): Promise<VelorisPayload> {\n await this.init();\n try {\n const p = await this.shield!.getAPIPayload();\n const payload: VelorisPayload = {\n fingerprint: p.fingerprint ?? null,\n fp_version: p.fp_version ?? '0.1.0',\n };\n this.log('payload', payload);\n return payload;\n } catch (err) {\n this.log('getPayload error (non-fatal):', err);\n return { fingerprint: null, fp_version: '0.1.0' };\n }\n }\n\n /**\n * Intercepts a checkout form's submit event.\n * Collects fingerprint and Stripe PaymentMethod in parallel, then calls\n * handler({ paymentMethodId, fingerprint, fp_version }).\n *\n * Your handler should POST to your backend, which calls /shield/evaluate\n * then confirms the Stripe PaymentIntent.\n */\n autoHook(\n formEl: HTMLFormElement,\n handler: (payload: AutoHookPayload) => Promise<void>,\n billingDetails?: Record<string, unknown>,\n ): void {\n if (!formEl) throw new Error('[VelorisStripe] autoHook: formEl is required');\n if (!handler) throw new Error('[VelorisStripe] autoHook: handler is required');\n if (!this.elements) throw new Error('[VelorisStripe] autoHook: pass elements via options.elements');\n\n // Warm up Shield.js immediately\n this.init().catch(e => this.log('init error:', e));\n\n formEl.addEventListener('submit', (e: Event) => {\n e.preventDefault();\n const submitBtn = formEl.querySelector<HTMLButtonElement>('[type=submit]');\n if (submitBtn) submitBtn.disabled = true;\n\n Promise.all([\n this.getPayload(),\n this.stripe.createPaymentMethod({\n type: 'card',\n card: this.elements!.getElement('card') ?? this.elements!.getElement('cardNumber'),\n billing_details: billingDetails ?? {},\n }),\n ]).then(([velorisPayload, pmResult]) => {\n if (pmResult.error) throw new Error(pmResult.error.message);\n return handler({\n paymentMethodId: pmResult.paymentMethod!.id,\n fingerprint: velorisPayload.fingerprint,\n fp_version: velorisPayload.fp_version,\n });\n }).catch(err => {\n this.log('autoHook error:', err);\n if (submitBtn) submitBtn.disabled = false;\n formEl.dispatchEvent(\n new CustomEvent('veloris:error', { detail: { message: (err as Error).message }, bubbles: true })\n );\n });\n });\n\n this.log('autoHook attached to', formEl);\n }\n}\n"],"names":["VelorisStripe","constructor","options","this","shield","initPromise","publicKey","Error","stripe","elements","onVerdict","debug","log","args","console","init","src","Promise","resolve","reject","document","querySelector","s","createElement","onload","onerror","head","appendChild","then","window","VelorisShield","getPayload","p","getAPIPayload","payload","fingerprint","fp_version","err","autoHook","formEl","handler","billingDetails","catch","e","addEventListener","preventDefault","submitBtn","disabled","all","createPaymentMethod","type","card","getElement","billing_details","velorisPayload","pmResult","error","message","paymentMethodId","paymentMethod","id","dispatchEvent","CustomEvent","detail","bubbles"],"mappings":"MAiEaA,EAUX,WAAAC,CAAYC,GACV,GAJMC,KAAAC,OAAqD,KACrDD,KAAAE,YAAqD,MAGtDH,EAAQI,UAAW,MAAM,IAAIC,MAAM,yCACxC,IAAKL,EAAQM,OAAW,MAAM,IAAID,MAAM,+CAExCJ,KAAKG,UAAaJ,EAAQI,UAC1BH,KAAKK,OAAaN,EAAQM,OAC1BL,KAAKM,SAAaP,EAAQO,SAC1BN,KAAKO,UAAaR,EAAQQ,UAC1BP,KAAKQ,MAAaT,EAAQS,QAAS,CACrC,CAEQ,GAAAC,IAAOC,GACTV,KAAKQ,OAAOG,QAAQF,IAAI,qBAAsBC,EACpD,CAMA,IAAAE,GACE,OAAIZ,KAAKE,cACTF,KAAKE,aA7CWW,EAnDG,8CAoDd,IAAIC,QAAQ,CAACC,EAASC,KAC3B,GAAwB,oBAAbC,SAAuC,YAAXF,IACvC,GAAIE,SAASC,cAAc,eAAeL,OAAuB,YAAXE,IACtD,MAAMI,EAAUF,SAASG,cAAc,UACvCD,EAAEN,IAAcA,EAChBM,EAAEE,OAAc,IAAMN,IACtBI,EAAEG,QAAc,IAAMN,EAAO,IAAIZ,MAAM,kBAAkBS,MACzDI,SAASM,KAAKC,YAAYL,MAqCoBM,KAAK,KACjD,IAAKC,OAAOC,cAAe,MAAM,IAAIvB,MAAM,0BAE3C,OADAJ,KAAKC,OAAS,IAAIyB,OAAOC,cAAcA,cAAc,CAAExB,UAAWH,KAAKG,YAChEH,KAAKC,OAAOW,SAClBa,KAAK,KACNzB,KAAKS,IAAI,YANkBT,KAAKE,YA5CtC,IAAoBW,CAqDlB,CAMA,gBAAMe,SACE5B,KAAKY,OACX,IACE,MAAMiB,QAAU7B,KAAKC,OAAQ6B,gBACvBC,EAA0B,CAC9BC,YAAaH,EAAEG,aAAe,KAC9BC,WAAaJ,EAAEI,YAAe,SAGhC,OADAjC,KAAKS,IAAI,UAAWsB,GACbA,CACT,CAAE,MAAOG,GAEP,OADAlC,KAAKS,IAAI,gCAAiCyB,GACnC,CAAEF,YAAa,KAAMC,WAAY,QAC1C,CACF,CAUA,QAAAE,CACEC,EACAC,EACAC,GAEA,IAAKF,EAAW,MAAM,IAAIhC,MAAM,gDAChC,IAAKiC,EAAW,MAAM,IAAIjC,MAAM,iDAChC,IAAKJ,KAAKM,SAAU,MAAM,IAAIF,MAAM,gEAGpCJ,KAAKY,OAAO2B,MAAMC,GAAKxC,KAAKS,IAAI,cAAe+B,IAE/CJ,EAAOK,iBAAiB,SAAWD,IACjCA,EAAEE,iBACF,MAAMC,EAAYP,EAAOlB,cAAiC,iBACtDyB,IAAWA,EAAUC,UAAW,GAEpC9B,QAAQ+B,IAAI,CACV7C,KAAK4B,aACL5B,KAAKK,OAAOyC,oBAAoB,CAC9BC,KAAM,OACNC,KAAMhD,KAAKM,SAAU2C,WAAW,SAAWjD,KAAKM,SAAU2C,WAAW,cACrEC,gBAAiBZ,GAAkB,CAAA,MAEpCb,KAAK,EAAE0B,EAAgBC,MACxB,GAAIA,EAASC,MAAO,MAAM,IAAIjD,MAAMgD,EAASC,MAAMC,SACnD,OAAOjB,EAAQ,CACbkB,gBAAiBH,EAASI,cAAeC,GACzCzB,YAAiBmB,EAAenB,YAChCC,WAAiBkB,EAAelB,eAEjCM,MAAML,IACPlC,KAAKS,IAAI,kBAAmByB,GACxBS,IAAWA,EAAUC,UAAW,GACpCR,EAAOsB,cACL,IAAIC,YAAY,gBAAiB,CAAEC,OAAQ,CAAEN,QAAUpB,EAAcoB,SAAWO,SAAS,SAK/F7D,KAAKS,IAAI,uBAAwB2B,EACnC"}
package/dist/react.cjs ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";var e=require("react/jsx-runtime"),t=require("react");class r{constructor(e){if(this.shield=null,this.initPromise=null,!e.publicKey)throw new Error("[VelorisStripe] publicKey is required");if(!e.stripe)throw new Error("[VelorisStripe] stripe instance is required");this.publicKey=e.publicKey,this.stripe=e.stripe,this.elements=e.elements,this.onVerdict=e.onVerdict,this.debug=e.debug??!1}log(...e){this.debug&&console.log("[VelorisStripe]",...e)}init(){return this.initPromise||(this.initPromise=(e="https://dashboard.veloris.app/shield.min.js",new Promise((t,r)=>{if("undefined"==typeof document)return void t();if(document.querySelector(`script[src="${e}"]`))return void t();const i=document.createElement("script");i.src=e,i.onload=()=>t(),i.onerror=()=>r(new Error(`Failed to load ${e}`)),document.head.appendChild(i)})).then(()=>{if(!window.VelorisShield)throw new Error("Shield.js did not load");return this.shield=new window.VelorisShield.VelorisShield({publicKey:this.publicKey}),this.shield.init()}).then(()=>{this.log("ready")})),this.initPromise;var e}async getPayload(){await this.init();try{const e=await this.shield.getAPIPayload(),t={fingerprint:e.fingerprint??null,fp_version:e.fp_version??"0.1.0"};return this.log("payload",t),t}catch(e){return this.log("getPayload error (non-fatal):",e),{fingerprint:null,fp_version:"0.1.0"}}}autoHook(e,t,r){if(!e)throw new Error("[VelorisStripe] autoHook: formEl is required");if(!t)throw new Error("[VelorisStripe] autoHook: handler is required");if(!this.elements)throw new Error("[VelorisStripe] autoHook: pass elements via options.elements");this.init().catch(e=>this.log("init error:",e)),e.addEventListener("submit",i=>{i.preventDefault();const o=e.querySelector("[type=submit]");o&&(o.disabled=!0),Promise.all([this.getPayload(),this.stripe.createPaymentMethod({type:"card",card:this.elements.getElement("card")??this.elements.getElement("cardNumber"),billing_details:r??{}})]).then(([e,r])=>{if(r.error)throw new Error(r.error.message);return t({paymentMethodId:r.paymentMethod.id,fingerprint:e.fingerprint,fp_version:e.fp_version})}).catch(t=>{this.log("autoHook error:",t),o&&(o.disabled=!1),e.dispatchEvent(new CustomEvent("veloris:error",{detail:{message:t.message},bubbles:!0}))})}),this.log("autoHook attached to",e)}}const i=t.createContext({instance:null,ready:!1});exports.VelorisStripe=r,exports.VelorisStripeProvider=function({options:o,children:n}){const[s,l]=t.useState(!1),a=t.useRef(null);return t.useEffect(()=>{const e=new r(o);a.current=e,e.init().then(()=>l(!0)).catch(()=>l(!0))},[]),e.jsx(i.Provider,{value:{instance:a.current,ready:s},children:n})},exports.useVelorisAutoHook=function({formRef:e,handler:r,billingDetails:o}){const{instance:n,ready:s}=t.useContext(i);t.useEffect(()=>{s&&n&&e.current&&n.autoHook(e.current,r,o)},[s,n])},exports.useVelorisStripe=function(){const{instance:e,ready:r}=t.useContext(i);return{ready:r,getPayload:t.useCallback(async()=>e?e.getPayload():{fingerprint:null,fp_version:"0.1.0"},[e]),instance:e}};
2
+ //# sourceMappingURL=react.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.cjs","sources":["../src/index.ts","../src/react.tsx"],"sourcesContent":["const SHIELD_SDK_URL = 'https://dashboard.veloris.app/shield.min.js';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport interface VelorisStripeOptions {\n /** Publishable key (vpk_…) from your Veloris dashboard */\n publicKey: string;\n /** Stripe.js instance returned by loadStripe() or window.Stripe() */\n stripe: Stripe;\n /** Stripe Elements instance (required for autoHook) */\n elements?: StripeElements;\n /** Called after every evaluation with verdict and evaluation_id */\n onVerdict?: (verdict: string, evaluationId: string) => void;\n /** Log debug info to console */\n debug?: boolean;\n}\n\nexport interface VelorisPayload {\n fingerprint: string | null;\n fp_version: string;\n}\n\nexport interface AutoHookPayload {\n paymentMethodId: string;\n fingerprint: string | null;\n fp_version: string;\n}\n\n// Minimal Stripe typings so consumers don't need @stripe/stripe-js as a dep\ninterface Stripe {\n createPaymentMethod(opts: Record<string, unknown>): Promise<{ paymentMethod?: { id: string }; error?: { message: string } }>;\n}\ninterface StripeElements {\n getElement(type: string): unknown;\n}\n\n// Shield.js global\ninterface VelorisShieldSDK {\n new(opts: { publicKey: string }): {\n init(): Promise<void>;\n getAPIPayload(): Promise<{ fingerprint?: string; fp_version?: string }>;\n };\n}\ndeclare global {\n interface Window {\n VelorisShield?: { VelorisShield: VelorisShieldSDK };\n }\n}\n\n// ── Loader ────────────────────────────────────────────────────────────────────\n\nfunction loadScript(src: string): Promise<void> {\n return new Promise((resolve, reject) => {\n if (typeof document === 'undefined') { resolve(); return; }\n if (document.querySelector(`script[src=\"${src}\"]`)) { resolve(); return; }\n const s = document.createElement('script');\n s.src = src;\n s.onload = () => resolve();\n s.onerror = () => reject(new Error(`Failed to load ${src}`));\n document.head.appendChild(s);\n });\n}\n\n// ── VelorisStripe class ───────────────────────────────────────────────────────\n\nexport class VelorisStripe {\n private readonly publicKey: string;\n private readonly stripe: Stripe;\n private readonly elements?: StripeElements;\n private readonly onVerdict?: (verdict: string, evaluationId: string) => void;\n private readonly debug: boolean;\n\n private shield: InstanceType<VelorisShieldSDK> | null = null;\n private initPromise: Promise<void> | null = null;\n\n constructor(options: VelorisStripeOptions) {\n if (!options.publicKey) throw new Error('[VelorisStripe] publicKey is required');\n if (!options.stripe) throw new Error('[VelorisStripe] stripe instance is required');\n\n this.publicKey = options.publicKey;\n this.stripe = options.stripe;\n this.elements = options.elements;\n this.onVerdict = options.onVerdict;\n this.debug = options.debug ?? false;\n }\n\n private log(...args: unknown[]): void {\n if (this.debug) console.log('[VelorisStripe]', ...args);\n }\n\n /**\n * Load Shield.js and initialise signal collection.\n * Safe to call multiple times — returns the same promise.\n */\n init(): Promise<void> {\n if (this.initPromise) return this.initPromise;\n this.initPromise = loadScript(SHIELD_SDK_URL).then(() => {\n if (!window.VelorisShield) throw new Error('Shield.js did not load');\n this.shield = new window.VelorisShield.VelorisShield({ publicKey: this.publicKey });\n return this.shield.init();\n }).then(() => {\n this.log('ready');\n });\n return this.initPromise;\n }\n\n /**\n * Returns the device fingerprint payload to send to your backend.\n * Call this just before submitting the order.\n */\n async getPayload(): Promise<VelorisPayload> {\n await this.init();\n try {\n const p = await this.shield!.getAPIPayload();\n const payload: VelorisPayload = {\n fingerprint: p.fingerprint ?? null,\n fp_version: p.fp_version ?? '0.1.0',\n };\n this.log('payload', payload);\n return payload;\n } catch (err) {\n this.log('getPayload error (non-fatal):', err);\n return { fingerprint: null, fp_version: '0.1.0' };\n }\n }\n\n /**\n * Intercepts a checkout form's submit event.\n * Collects fingerprint and Stripe PaymentMethod in parallel, then calls\n * handler({ paymentMethodId, fingerprint, fp_version }).\n *\n * Your handler should POST to your backend, which calls /shield/evaluate\n * then confirms the Stripe PaymentIntent.\n */\n autoHook(\n formEl: HTMLFormElement,\n handler: (payload: AutoHookPayload) => Promise<void>,\n billingDetails?: Record<string, unknown>,\n ): void {\n if (!formEl) throw new Error('[VelorisStripe] autoHook: formEl is required');\n if (!handler) throw new Error('[VelorisStripe] autoHook: handler is required');\n if (!this.elements) throw new Error('[VelorisStripe] autoHook: pass elements via options.elements');\n\n // Warm up Shield.js immediately\n this.init().catch(e => this.log('init error:', e));\n\n formEl.addEventListener('submit', (e: Event) => {\n e.preventDefault();\n const submitBtn = formEl.querySelector<HTMLButtonElement>('[type=submit]');\n if (submitBtn) submitBtn.disabled = true;\n\n Promise.all([\n this.getPayload(),\n this.stripe.createPaymentMethod({\n type: 'card',\n card: this.elements!.getElement('card') ?? this.elements!.getElement('cardNumber'),\n billing_details: billingDetails ?? {},\n }),\n ]).then(([velorisPayload, pmResult]) => {\n if (pmResult.error) throw new Error(pmResult.error.message);\n return handler({\n paymentMethodId: pmResult.paymentMethod!.id,\n fingerprint: velorisPayload.fingerprint,\n fp_version: velorisPayload.fp_version,\n });\n }).catch(err => {\n this.log('autoHook error:', err);\n if (submitBtn) submitBtn.disabled = false;\n formEl.dispatchEvent(\n new CustomEvent('veloris:error', { detail: { message: (err as Error).message }, bubbles: true })\n );\n });\n });\n\n this.log('autoHook attached to', formEl);\n }\n}\n","import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport type { ReactNode } from 'react';\nimport { VelorisStripe } from './index.js';\nimport type { VelorisStripeOptions, VelorisPayload, AutoHookPayload } from './index.js';\n\n// ── Context ───────────────────────────────────────────────────────────────────\n\ninterface VelorisContextValue {\n instance: VelorisStripe | null;\n ready: boolean;\n}\n\nconst VelorisContext = createContext<VelorisContextValue>({ instance: null, ready: false });\n\n// ── Provider ──────────────────────────────────────────────────────────────────\n\nexport interface VelorisStripeProviderProps {\n options: VelorisStripeOptions;\n children: ReactNode;\n}\n\n/**\n * Wrap your checkout tree with this provider. It initialises Shield.js once\n * and makes the VelorisStripe instance available to child components.\n *\n * @example\n * <VelorisStripeProvider options={{ publicKey: 'vpk_…', stripe }}>\n * <CheckoutForm />\n * </VelorisStripeProvider>\n */\nexport function VelorisStripeProvider({ options, children }: VelorisStripeProviderProps) {\n const [ready, setReady] = useState(false);\n const instanceRef = useRef<VelorisStripe | null>(null);\n\n useEffect(() => {\n const v = new VelorisStripe(options);\n instanceRef.current = v;\n v.init()\n .then(() => setReady(true))\n .catch(() => setReady(true)); // fail open\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return (\n <VelorisContext.Provider value={{ instance: instanceRef.current, ready }}>\n {children}\n </VelorisContext.Provider>\n );\n}\n\n// ── useVelorisStripe hook ─────────────────────────────────────────────────────\n\nexport interface UseVelorisStripeReturn {\n /** True once Shield.js has initialised */\n ready: boolean;\n /** Collect fingerprint — call just before submitting the order */\n getPayload: () => Promise<VelorisPayload>;\n /** Full VelorisStripe instance (null until mounted) */\n instance: VelorisStripe | null;\n}\n\n/**\n * Access the Veloris Shield instance inside any component wrapped by\n * VelorisStripeProvider.\n *\n * @example\n * const { ready, getPayload } = useVelorisStripe();\n *\n * async function handleSubmit() {\n * const { fingerprint } = await getPayload();\n * const { paymentMethod } = await stripe.createPaymentMethod({ … });\n * await fetch('/checkout', {\n * method: 'POST',\n * body: JSON.stringify({ paymentMethodId: paymentMethod.id, fingerprint }),\n * });\n * }\n */\nexport function useVelorisStripe(): UseVelorisStripeReturn {\n const { instance, ready } = useContext(VelorisContext);\n\n const getPayload = useCallback(async (): Promise<VelorisPayload> => {\n if (!instance) return { fingerprint: null, fp_version: '0.1.0' };\n return instance.getPayload();\n }, [instance]);\n\n return { ready, getPayload, instance };\n}\n\n// ── useVelorisAutoHook ────────────────────────────────────────────────────────\n\nexport interface UseVelorisAutoHookOptions {\n formRef: React.RefObject<HTMLFormElement>;\n handler: (payload: AutoHookPayload) => Promise<void>;\n billingDetails?: Record<string, unknown>;\n}\n\n/**\n * Attach autoHook to a form ref. Runs once when the form mounts and the\n * instance is ready.\n *\n * @example\n * const formRef = useRef<HTMLFormElement>(null);\n * useVelorisAutoHook({ formRef, handler: async ({ paymentMethodId, fingerprint }) => {\n * await fetch('/checkout', { method: 'POST', body: JSON.stringify({ paymentMethodId, fingerprint }) });\n * }});\n * return <form ref={formRef}>…</form>;\n */\nexport function useVelorisAutoHook({ formRef, handler, billingDetails }: UseVelorisAutoHookOptions): void {\n const { instance, ready } = useContext(VelorisContext);\n\n useEffect(() => {\n if (!ready || !instance || !formRef.current) return;\n instance.autoHook(formRef.current, handler, billingDetails);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [ready, instance]);\n}\n\n// Re-export core for convenience\nexport { VelorisStripe } from './index.js';\nexport type { VelorisStripeOptions, VelorisPayload, AutoHookPayload } from './index.js';\n"],"names":["VelorisStripe","constructor","options","this","shield","initPromise","publicKey","Error","stripe","elements","onVerdict","debug","log","args","console","init","src","Promise","resolve","reject","document","querySelector","s","createElement","onload","onerror","head","appendChild","then","window","VelorisShield","getPayload","p","getAPIPayload","payload","fingerprint","fp_version","err","autoHook","formEl","handler","billingDetails","catch","e","addEventListener","preventDefault","submitBtn","disabled","all","createPaymentMethod","type","card","getElement","billing_details","velorisPayload","pmResult","error","message","paymentMethodId","paymentMethod","id","dispatchEvent","CustomEvent","detail","bubbles","VelorisContext","createContext","instance","ready","children","setReady","useState","instanceRef","useRef","useEffect","v","current","_jsx","Provider","value","formRef","useContext","useCallback","async"],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ import type { ReactNode } from 'react';
2
+ import { VelorisStripe } from './index.js';
3
+ import type { VelorisStripeOptions, VelorisPayload, AutoHookPayload } from './index.js';
4
+ export interface VelorisStripeProviderProps {
5
+ options: VelorisStripeOptions;
6
+ children: ReactNode;
7
+ }
8
+ /**
9
+ * Wrap your checkout tree with this provider. It initialises Shield.js once
10
+ * and makes the VelorisStripe instance available to child components.
11
+ *
12
+ * @example
13
+ * <VelorisStripeProvider options={{ publicKey: 'vpk_…', stripe }}>
14
+ * <CheckoutForm />
15
+ * </VelorisStripeProvider>
16
+ */
17
+ export declare function VelorisStripeProvider({ options, children }: VelorisStripeProviderProps): import("react").JSX.Element;
18
+ export interface UseVelorisStripeReturn {
19
+ /** True once Shield.js has initialised */
20
+ ready: boolean;
21
+ /** Collect fingerprint — call just before submitting the order */
22
+ getPayload: () => Promise<VelorisPayload>;
23
+ /** Full VelorisStripe instance (null until mounted) */
24
+ instance: VelorisStripe | null;
25
+ }
26
+ /**
27
+ * Access the Veloris Shield instance inside any component wrapped by
28
+ * VelorisStripeProvider.
29
+ *
30
+ * @example
31
+ * const { ready, getPayload } = useVelorisStripe();
32
+ *
33
+ * async function handleSubmit() {
34
+ * const { fingerprint } = await getPayload();
35
+ * const { paymentMethod } = await stripe.createPaymentMethod({ … });
36
+ * await fetch('/checkout', {
37
+ * method: 'POST',
38
+ * body: JSON.stringify({ paymentMethodId: paymentMethod.id, fingerprint }),
39
+ * });
40
+ * }
41
+ */
42
+ export declare function useVelorisStripe(): UseVelorisStripeReturn;
43
+ export interface UseVelorisAutoHookOptions {
44
+ formRef: React.RefObject<HTMLFormElement>;
45
+ handler: (payload: AutoHookPayload) => Promise<void>;
46
+ billingDetails?: Record<string, unknown>;
47
+ }
48
+ /**
49
+ * Attach autoHook to a form ref. Runs once when the form mounts and the
50
+ * instance is ready.
51
+ *
52
+ * @example
53
+ * const formRef = useRef<HTMLFormElement>(null);
54
+ * useVelorisAutoHook({ formRef, handler: async ({ paymentMethodId, fingerprint }) => {
55
+ * await fetch('/checkout', { method: 'POST', body: JSON.stringify({ paymentMethodId, fingerprint }) });
56
+ * }});
57
+ * return <form ref={formRef}>…</form>;
58
+ */
59
+ export declare function useVelorisAutoHook({ formRef, handler, billingDetails }: UseVelorisAutoHookOptions): void;
60
+ export { VelorisStripe } from './index.js';
61
+ export type { VelorisStripeOptions, VelorisPayload, AutoHookPayload } from './index.js';
package/dist/react.js ADDED
@@ -0,0 +1,2 @@
1
+ import{jsx as e}from"react/jsx-runtime";import{createContext as t,useState as r,useRef as i,useEffect as n,useContext as o,useCallback as s}from"react";class l{constructor(e){if(this.shield=null,this.initPromise=null,!e.publicKey)throw new Error("[VelorisStripe] publicKey is required");if(!e.stripe)throw new Error("[VelorisStripe] stripe instance is required");this.publicKey=e.publicKey,this.stripe=e.stripe,this.elements=e.elements,this.onVerdict=e.onVerdict,this.debug=e.debug??!1}log(...e){this.debug&&console.log("[VelorisStripe]",...e)}init(){return this.initPromise||(this.initPromise=(e="https://dashboard.veloris.app/shield.min.js",new Promise((t,r)=>{if("undefined"==typeof document)return void t();if(document.querySelector(`script[src="${e}"]`))return void t();const i=document.createElement("script");i.src=e,i.onload=()=>t(),i.onerror=()=>r(new Error(`Failed to load ${e}`)),document.head.appendChild(i)})).then(()=>{if(!window.VelorisShield)throw new Error("Shield.js did not load");return this.shield=new window.VelorisShield.VelorisShield({publicKey:this.publicKey}),this.shield.init()}).then(()=>{this.log("ready")})),this.initPromise;var e}async getPayload(){await this.init();try{const e=await this.shield.getAPIPayload(),t={fingerprint:e.fingerprint??null,fp_version:e.fp_version??"0.1.0"};return this.log("payload",t),t}catch(e){return this.log("getPayload error (non-fatal):",e),{fingerprint:null,fp_version:"0.1.0"}}}autoHook(e,t,r){if(!e)throw new Error("[VelorisStripe] autoHook: formEl is required");if(!t)throw new Error("[VelorisStripe] autoHook: handler is required");if(!this.elements)throw new Error("[VelorisStripe] autoHook: pass elements via options.elements");this.init().catch(e=>this.log("init error:",e)),e.addEventListener("submit",i=>{i.preventDefault();const n=e.querySelector("[type=submit]");n&&(n.disabled=!0),Promise.all([this.getPayload(),this.stripe.createPaymentMethod({type:"card",card:this.elements.getElement("card")??this.elements.getElement("cardNumber"),billing_details:r??{}})]).then(([e,r])=>{if(r.error)throw new Error(r.error.message);return t({paymentMethodId:r.paymentMethod.id,fingerprint:e.fingerprint,fp_version:e.fp_version})}).catch(t=>{this.log("autoHook error:",t),n&&(n.disabled=!1),e.dispatchEvent(new CustomEvent("veloris:error",{detail:{message:t.message},bubbles:!0}))})}),this.log("autoHook attached to",e)}}const a=t({instance:null,ready:!1});function d({options:t,children:o}){const[s,d]=r(!1),c=i(null);return n(()=>{const e=new l(t);c.current=e,e.init().then(()=>d(!0)).catch(()=>d(!0))},[]),e(a.Provider,{value:{instance:c.current,ready:s},children:o})}function c(){const{instance:e,ready:t}=o(a);return{ready:t,getPayload:s(async()=>e?e.getPayload():{fingerprint:null,fp_version:"0.1.0"},[e]),instance:e}}function h({formRef:e,handler:t,billingDetails:r}){const{instance:i,ready:s}=o(a);n(()=>{s&&i&&e.current&&i.autoHook(e.current,t,r)},[s,i])}export{l as VelorisStripe,d as VelorisStripeProvider,h as useVelorisAutoHook,c as useVelorisStripe};
2
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.js","sources":["../src/index.ts","../src/react.tsx"],"sourcesContent":["const SHIELD_SDK_URL = 'https://dashboard.veloris.app/shield.min.js';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport interface VelorisStripeOptions {\n /** Publishable key (vpk_…) from your Veloris dashboard */\n publicKey: string;\n /** Stripe.js instance returned by loadStripe() or window.Stripe() */\n stripe: Stripe;\n /** Stripe Elements instance (required for autoHook) */\n elements?: StripeElements;\n /** Called after every evaluation with verdict and evaluation_id */\n onVerdict?: (verdict: string, evaluationId: string) => void;\n /** Log debug info to console */\n debug?: boolean;\n}\n\nexport interface VelorisPayload {\n fingerprint: string | null;\n fp_version: string;\n}\n\nexport interface AutoHookPayload {\n paymentMethodId: string;\n fingerprint: string | null;\n fp_version: string;\n}\n\n// Minimal Stripe typings so consumers don't need @stripe/stripe-js as a dep\ninterface Stripe {\n createPaymentMethod(opts: Record<string, unknown>): Promise<{ paymentMethod?: { id: string }; error?: { message: string } }>;\n}\ninterface StripeElements {\n getElement(type: string): unknown;\n}\n\n// Shield.js global\ninterface VelorisShieldSDK {\n new(opts: { publicKey: string }): {\n init(): Promise<void>;\n getAPIPayload(): Promise<{ fingerprint?: string; fp_version?: string }>;\n };\n}\ndeclare global {\n interface Window {\n VelorisShield?: { VelorisShield: VelorisShieldSDK };\n }\n}\n\n// ── Loader ────────────────────────────────────────────────────────────────────\n\nfunction loadScript(src: string): Promise<void> {\n return new Promise((resolve, reject) => {\n if (typeof document === 'undefined') { resolve(); return; }\n if (document.querySelector(`script[src=\"${src}\"]`)) { resolve(); return; }\n const s = document.createElement('script');\n s.src = src;\n s.onload = () => resolve();\n s.onerror = () => reject(new Error(`Failed to load ${src}`));\n document.head.appendChild(s);\n });\n}\n\n// ── VelorisStripe class ───────────────────────────────────────────────────────\n\nexport class VelorisStripe {\n private readonly publicKey: string;\n private readonly stripe: Stripe;\n private readonly elements?: StripeElements;\n private readonly onVerdict?: (verdict: string, evaluationId: string) => void;\n private readonly debug: boolean;\n\n private shield: InstanceType<VelorisShieldSDK> | null = null;\n private initPromise: Promise<void> | null = null;\n\n constructor(options: VelorisStripeOptions) {\n if (!options.publicKey) throw new Error('[VelorisStripe] publicKey is required');\n if (!options.stripe) throw new Error('[VelorisStripe] stripe instance is required');\n\n this.publicKey = options.publicKey;\n this.stripe = options.stripe;\n this.elements = options.elements;\n this.onVerdict = options.onVerdict;\n this.debug = options.debug ?? false;\n }\n\n private log(...args: unknown[]): void {\n if (this.debug) console.log('[VelorisStripe]', ...args);\n }\n\n /**\n * Load Shield.js and initialise signal collection.\n * Safe to call multiple times — returns the same promise.\n */\n init(): Promise<void> {\n if (this.initPromise) return this.initPromise;\n this.initPromise = loadScript(SHIELD_SDK_URL).then(() => {\n if (!window.VelorisShield) throw new Error('Shield.js did not load');\n this.shield = new window.VelorisShield.VelorisShield({ publicKey: this.publicKey });\n return this.shield.init();\n }).then(() => {\n this.log('ready');\n });\n return this.initPromise;\n }\n\n /**\n * Returns the device fingerprint payload to send to your backend.\n * Call this just before submitting the order.\n */\n async getPayload(): Promise<VelorisPayload> {\n await this.init();\n try {\n const p = await this.shield!.getAPIPayload();\n const payload: VelorisPayload = {\n fingerprint: p.fingerprint ?? null,\n fp_version: p.fp_version ?? '0.1.0',\n };\n this.log('payload', payload);\n return payload;\n } catch (err) {\n this.log('getPayload error (non-fatal):', err);\n return { fingerprint: null, fp_version: '0.1.0' };\n }\n }\n\n /**\n * Intercepts a checkout form's submit event.\n * Collects fingerprint and Stripe PaymentMethod in parallel, then calls\n * handler({ paymentMethodId, fingerprint, fp_version }).\n *\n * Your handler should POST to your backend, which calls /shield/evaluate\n * then confirms the Stripe PaymentIntent.\n */\n autoHook(\n formEl: HTMLFormElement,\n handler: (payload: AutoHookPayload) => Promise<void>,\n billingDetails?: Record<string, unknown>,\n ): void {\n if (!formEl) throw new Error('[VelorisStripe] autoHook: formEl is required');\n if (!handler) throw new Error('[VelorisStripe] autoHook: handler is required');\n if (!this.elements) throw new Error('[VelorisStripe] autoHook: pass elements via options.elements');\n\n // Warm up Shield.js immediately\n this.init().catch(e => this.log('init error:', e));\n\n formEl.addEventListener('submit', (e: Event) => {\n e.preventDefault();\n const submitBtn = formEl.querySelector<HTMLButtonElement>('[type=submit]');\n if (submitBtn) submitBtn.disabled = true;\n\n Promise.all([\n this.getPayload(),\n this.stripe.createPaymentMethod({\n type: 'card',\n card: this.elements!.getElement('card') ?? this.elements!.getElement('cardNumber'),\n billing_details: billingDetails ?? {},\n }),\n ]).then(([velorisPayload, pmResult]) => {\n if (pmResult.error) throw new Error(pmResult.error.message);\n return handler({\n paymentMethodId: pmResult.paymentMethod!.id,\n fingerprint: velorisPayload.fingerprint,\n fp_version: velorisPayload.fp_version,\n });\n }).catch(err => {\n this.log('autoHook error:', err);\n if (submitBtn) submitBtn.disabled = false;\n formEl.dispatchEvent(\n new CustomEvent('veloris:error', { detail: { message: (err as Error).message }, bubbles: true })\n );\n });\n });\n\n this.log('autoHook attached to', formEl);\n }\n}\n","import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport type { ReactNode } from 'react';\nimport { VelorisStripe } from './index.js';\nimport type { VelorisStripeOptions, VelorisPayload, AutoHookPayload } from './index.js';\n\n// ── Context ───────────────────────────────────────────────────────────────────\n\ninterface VelorisContextValue {\n instance: VelorisStripe | null;\n ready: boolean;\n}\n\nconst VelorisContext = createContext<VelorisContextValue>({ instance: null, ready: false });\n\n// ── Provider ──────────────────────────────────────────────────────────────────\n\nexport interface VelorisStripeProviderProps {\n options: VelorisStripeOptions;\n children: ReactNode;\n}\n\n/**\n * Wrap your checkout tree with this provider. It initialises Shield.js once\n * and makes the VelorisStripe instance available to child components.\n *\n * @example\n * <VelorisStripeProvider options={{ publicKey: 'vpk_…', stripe }}>\n * <CheckoutForm />\n * </VelorisStripeProvider>\n */\nexport function VelorisStripeProvider({ options, children }: VelorisStripeProviderProps) {\n const [ready, setReady] = useState(false);\n const instanceRef = useRef<VelorisStripe | null>(null);\n\n useEffect(() => {\n const v = new VelorisStripe(options);\n instanceRef.current = v;\n v.init()\n .then(() => setReady(true))\n .catch(() => setReady(true)); // fail open\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return (\n <VelorisContext.Provider value={{ instance: instanceRef.current, ready }}>\n {children}\n </VelorisContext.Provider>\n );\n}\n\n// ── useVelorisStripe hook ─────────────────────────────────────────────────────\n\nexport interface UseVelorisStripeReturn {\n /** True once Shield.js has initialised */\n ready: boolean;\n /** Collect fingerprint — call just before submitting the order */\n getPayload: () => Promise<VelorisPayload>;\n /** Full VelorisStripe instance (null until mounted) */\n instance: VelorisStripe | null;\n}\n\n/**\n * Access the Veloris Shield instance inside any component wrapped by\n * VelorisStripeProvider.\n *\n * @example\n * const { ready, getPayload } = useVelorisStripe();\n *\n * async function handleSubmit() {\n * const { fingerprint } = await getPayload();\n * const { paymentMethod } = await stripe.createPaymentMethod({ … });\n * await fetch('/checkout', {\n * method: 'POST',\n * body: JSON.stringify({ paymentMethodId: paymentMethod.id, fingerprint }),\n * });\n * }\n */\nexport function useVelorisStripe(): UseVelorisStripeReturn {\n const { instance, ready } = useContext(VelorisContext);\n\n const getPayload = useCallback(async (): Promise<VelorisPayload> => {\n if (!instance) return { fingerprint: null, fp_version: '0.1.0' };\n return instance.getPayload();\n }, [instance]);\n\n return { ready, getPayload, instance };\n}\n\n// ── useVelorisAutoHook ────────────────────────────────────────────────────────\n\nexport interface UseVelorisAutoHookOptions {\n formRef: React.RefObject<HTMLFormElement>;\n handler: (payload: AutoHookPayload) => Promise<void>;\n billingDetails?: Record<string, unknown>;\n}\n\n/**\n * Attach autoHook to a form ref. Runs once when the form mounts and the\n * instance is ready.\n *\n * @example\n * const formRef = useRef<HTMLFormElement>(null);\n * useVelorisAutoHook({ formRef, handler: async ({ paymentMethodId, fingerprint }) => {\n * await fetch('/checkout', { method: 'POST', body: JSON.stringify({ paymentMethodId, fingerprint }) });\n * }});\n * return <form ref={formRef}>…</form>;\n */\nexport function useVelorisAutoHook({ formRef, handler, billingDetails }: UseVelorisAutoHookOptions): void {\n const { instance, ready } = useContext(VelorisContext);\n\n useEffect(() => {\n if (!ready || !instance || !formRef.current) return;\n instance.autoHook(formRef.current, handler, billingDetails);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [ready, instance]);\n}\n\n// Re-export core for convenience\nexport { VelorisStripe } from './index.js';\nexport type { VelorisStripeOptions, VelorisPayload, AutoHookPayload } from './index.js';\n"],"names":["VelorisStripe","constructor","options","this","shield","initPromise","publicKey","Error","stripe","elements","onVerdict","debug","log","args","console","init","src","Promise","resolve","reject","document","querySelector","s","createElement","onload","onerror","head","appendChild","then","window","VelorisShield","getPayload","p","getAPIPayload","payload","fingerprint","fp_version","err","autoHook","formEl","handler","billingDetails","catch","e","addEventListener","preventDefault","submitBtn","disabled","all","createPaymentMethod","type","card","getElement","billing_details","velorisPayload","pmResult","error","message","paymentMethodId","paymentMethod","id","dispatchEvent","CustomEvent","detail","bubbles","VelorisContext","createContext","instance","ready","VelorisStripeProvider","children","setReady","useState","instanceRef","useRef","useEffect","v","current","_jsx","Provider","value","useVelorisStripe","useContext","useCallback","async","useVelorisAutoHook","formRef"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@veloris/stripe",
3
+ "version": "1.0.0",
4
+ "description": "Veloris Shield + Stripe integration for React and Node.js",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs",
13
+ "types": "./dist/index.d.ts"
14
+ },
15
+ "./react": {
16
+ "import": "./dist/react.js",
17
+ "require": "./dist/react.cjs",
18
+ "types": "./dist/react.d.ts"
19
+ }
20
+ },
21
+ "scripts": {
22
+ "build": "rollup -c",
23
+ "prepublishOnly": "npm run build"
24
+ },
25
+ "keywords": [
26
+ "veloris",
27
+ "shield",
28
+ "stripe",
29
+ "fraud",
30
+ "woocommerce",
31
+ "payments"
32
+ ],
33
+ "author": "Veloris <hello@veloris.app>",
34
+ "license": "MIT",
35
+ "peerDependencies": {
36
+ "react": ">=18.0.0",
37
+ "react-dom": ">=18.0.0"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "react": {
41
+ "optional": true
42
+ },
43
+ "react-dom": {
44
+ "optional": true
45
+ }
46
+ },
47
+ "devDependencies": {
48
+ "@rollup/plugin-node-resolve": "^15.0.0",
49
+ "@rollup/plugin-terser": "^0.4.4",
50
+ "@rollup/plugin-typescript": "^12.0.0",
51
+ "@types/react": "^19.2.17",
52
+ "@types/react-dom": "^19.2.3",
53
+ "rollup": "^4.24.0",
54
+ "tslib": "^2.6.0",
55
+ "typescript": "^5.0.0"
56
+ }
57
+ }
@@ -0,0 +1,36 @@
1
+ import resolve from '@rollup/plugin-node-resolve';
2
+ import typescript from '@rollup/plugin-typescript';
3
+ import terser from '@rollup/plugin-terser';
4
+
5
+ const external = ['react', 'react/jsx-runtime', 'react-dom'];
6
+
7
+ export default [
8
+ // Core — ESM + declarations
9
+ {
10
+ input: 'src/index.ts',
11
+ external,
12
+ plugins: [resolve(), typescript({ declaration: true, declarationDir: './dist', outDir: './dist' }), terser()],
13
+ output: { file: 'dist/index.js', format: 'es', sourcemap: true },
14
+ },
15
+ // Core — CJS (no declarations needed)
16
+ {
17
+ input: 'src/index.ts',
18
+ external,
19
+ plugins: [resolve(), typescript(), terser()],
20
+ output: { file: 'dist/index.cjs', format: 'cjs', sourcemap: true },
21
+ },
22
+ // React — ESM + declarations
23
+ {
24
+ input: 'src/react.tsx',
25
+ external,
26
+ plugins: [resolve(), typescript({ declaration: true, declarationDir: './dist', outDir: './dist' }), terser()],
27
+ output: { file: 'dist/react.js', format: 'es', sourcemap: true },
28
+ },
29
+ // React — CJS
30
+ {
31
+ input: 'src/react.tsx',
32
+ external,
33
+ plugins: [resolve(), typescript(), terser()],
34
+ output: { file: 'dist/react.cjs', format: 'cjs', sourcemap: true },
35
+ },
36
+ ];
package/src/index.ts ADDED
@@ -0,0 +1,177 @@
1
+ const SHIELD_SDK_URL = 'https://dashboard.veloris.app/shield.min.js';
2
+
3
+ // ── Types ─────────────────────────────────────────────────────────────────────
4
+
5
+ export interface VelorisStripeOptions {
6
+ /** Publishable key (vpk_…) from your Veloris dashboard */
7
+ publicKey: string;
8
+ /** Stripe.js instance returned by loadStripe() or window.Stripe() */
9
+ stripe: Stripe;
10
+ /** Stripe Elements instance (required for autoHook) */
11
+ elements?: StripeElements;
12
+ /** Called after every evaluation with verdict and evaluation_id */
13
+ onVerdict?: (verdict: string, evaluationId: string) => void;
14
+ /** Log debug info to console */
15
+ debug?: boolean;
16
+ }
17
+
18
+ export interface VelorisPayload {
19
+ fingerprint: string | null;
20
+ fp_version: string;
21
+ }
22
+
23
+ export interface AutoHookPayload {
24
+ paymentMethodId: string;
25
+ fingerprint: string | null;
26
+ fp_version: string;
27
+ }
28
+
29
+ // Minimal Stripe typings so consumers don't need @stripe/stripe-js as a dep
30
+ interface Stripe {
31
+ createPaymentMethod(opts: Record<string, unknown>): Promise<{ paymentMethod?: { id: string }; error?: { message: string } }>;
32
+ }
33
+ interface StripeElements {
34
+ getElement(type: string): unknown;
35
+ }
36
+
37
+ // Shield.js global
38
+ interface VelorisShieldSDK {
39
+ new(opts: { publicKey: string }): {
40
+ init(): Promise<void>;
41
+ getAPIPayload(): Promise<{ fingerprint?: string; fp_version?: string }>;
42
+ };
43
+ }
44
+ declare global {
45
+ interface Window {
46
+ VelorisShield?: { VelorisShield: VelorisShieldSDK };
47
+ }
48
+ }
49
+
50
+ // ── Loader ────────────────────────────────────────────────────────────────────
51
+
52
+ function loadScript(src: string): Promise<void> {
53
+ return new Promise((resolve, reject) => {
54
+ if (typeof document === 'undefined') { resolve(); return; }
55
+ if (document.querySelector(`script[src="${src}"]`)) { resolve(); return; }
56
+ const s = document.createElement('script');
57
+ s.src = src;
58
+ s.onload = () => resolve();
59
+ s.onerror = () => reject(new Error(`Failed to load ${src}`));
60
+ document.head.appendChild(s);
61
+ });
62
+ }
63
+
64
+ // ── VelorisStripe class ───────────────────────────────────────────────────────
65
+
66
+ export class VelorisStripe {
67
+ private readonly publicKey: string;
68
+ private readonly stripe: Stripe;
69
+ private readonly elements?: StripeElements;
70
+ private readonly onVerdict?: (verdict: string, evaluationId: string) => void;
71
+ private readonly debug: boolean;
72
+
73
+ private shield: InstanceType<VelorisShieldSDK> | null = null;
74
+ private initPromise: Promise<void> | null = null;
75
+
76
+ constructor(options: VelorisStripeOptions) {
77
+ if (!options.publicKey) throw new Error('[VelorisStripe] publicKey is required');
78
+ if (!options.stripe) throw new Error('[VelorisStripe] stripe instance is required');
79
+
80
+ this.publicKey = options.publicKey;
81
+ this.stripe = options.stripe;
82
+ this.elements = options.elements;
83
+ this.onVerdict = options.onVerdict;
84
+ this.debug = options.debug ?? false;
85
+ }
86
+
87
+ private log(...args: unknown[]): void {
88
+ if (this.debug) console.log('[VelorisStripe]', ...args);
89
+ }
90
+
91
+ /**
92
+ * Load Shield.js and initialise signal collection.
93
+ * Safe to call multiple times — returns the same promise.
94
+ */
95
+ init(): Promise<void> {
96
+ if (this.initPromise) return this.initPromise;
97
+ this.initPromise = loadScript(SHIELD_SDK_URL).then(() => {
98
+ if (!window.VelorisShield) throw new Error('Shield.js did not load');
99
+ this.shield = new window.VelorisShield.VelorisShield({ publicKey: this.publicKey });
100
+ return this.shield.init();
101
+ }).then(() => {
102
+ this.log('ready');
103
+ });
104
+ return this.initPromise;
105
+ }
106
+
107
+ /**
108
+ * Returns the device fingerprint payload to send to your backend.
109
+ * Call this just before submitting the order.
110
+ */
111
+ async getPayload(): Promise<VelorisPayload> {
112
+ await this.init();
113
+ try {
114
+ const p = await this.shield!.getAPIPayload();
115
+ const payload: VelorisPayload = {
116
+ fingerprint: p.fingerprint ?? null,
117
+ fp_version: p.fp_version ?? '0.1.0',
118
+ };
119
+ this.log('payload', payload);
120
+ return payload;
121
+ } catch (err) {
122
+ this.log('getPayload error (non-fatal):', err);
123
+ return { fingerprint: null, fp_version: '0.1.0' };
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Intercepts a checkout form's submit event.
129
+ * Collects fingerprint and Stripe PaymentMethod in parallel, then calls
130
+ * handler({ paymentMethodId, fingerprint, fp_version }).
131
+ *
132
+ * Your handler should POST to your backend, which calls /shield/evaluate
133
+ * then confirms the Stripe PaymentIntent.
134
+ */
135
+ autoHook(
136
+ formEl: HTMLFormElement,
137
+ handler: (payload: AutoHookPayload) => Promise<void>,
138
+ billingDetails?: Record<string, unknown>,
139
+ ): void {
140
+ if (!formEl) throw new Error('[VelorisStripe] autoHook: formEl is required');
141
+ if (!handler) throw new Error('[VelorisStripe] autoHook: handler is required');
142
+ if (!this.elements) throw new Error('[VelorisStripe] autoHook: pass elements via options.elements');
143
+
144
+ // Warm up Shield.js immediately
145
+ this.init().catch(e => this.log('init error:', e));
146
+
147
+ formEl.addEventListener('submit', (e: Event) => {
148
+ e.preventDefault();
149
+ const submitBtn = formEl.querySelector<HTMLButtonElement>('[type=submit]');
150
+ if (submitBtn) submitBtn.disabled = true;
151
+
152
+ Promise.all([
153
+ this.getPayload(),
154
+ this.stripe.createPaymentMethod({
155
+ type: 'card',
156
+ card: this.elements!.getElement('card') ?? this.elements!.getElement('cardNumber'),
157
+ billing_details: billingDetails ?? {},
158
+ }),
159
+ ]).then(([velorisPayload, pmResult]) => {
160
+ if (pmResult.error) throw new Error(pmResult.error.message);
161
+ return handler({
162
+ paymentMethodId: pmResult.paymentMethod!.id,
163
+ fingerprint: velorisPayload.fingerprint,
164
+ fp_version: velorisPayload.fp_version,
165
+ });
166
+ }).catch(err => {
167
+ this.log('autoHook error:', err);
168
+ if (submitBtn) submitBtn.disabled = false;
169
+ formEl.dispatchEvent(
170
+ new CustomEvent('veloris:error', { detail: { message: (err as Error).message }, bubbles: true })
171
+ );
172
+ });
173
+ });
174
+
175
+ this.log('autoHook attached to', formEl);
176
+ }
177
+ }
package/src/react.tsx ADDED
@@ -0,0 +1,127 @@
1
+ import {
2
+ createContext,
3
+ useCallback,
4
+ useContext,
5
+ useEffect,
6
+ useRef,
7
+ useState,
8
+ } from 'react';
9
+ import type { ReactNode } from 'react';
10
+ import { VelorisStripe } from './index.js';
11
+ import type { VelorisStripeOptions, VelorisPayload, AutoHookPayload } from './index.js';
12
+
13
+ // ── Context ───────────────────────────────────────────────────────────────────
14
+
15
+ interface VelorisContextValue {
16
+ instance: VelorisStripe | null;
17
+ ready: boolean;
18
+ }
19
+
20
+ const VelorisContext = createContext<VelorisContextValue>({ instance: null, ready: false });
21
+
22
+ // ── Provider ──────────────────────────────────────────────────────────────────
23
+
24
+ export interface VelorisStripeProviderProps {
25
+ options: VelorisStripeOptions;
26
+ children: ReactNode;
27
+ }
28
+
29
+ /**
30
+ * Wrap your checkout tree with this provider. It initialises Shield.js once
31
+ * and makes the VelorisStripe instance available to child components.
32
+ *
33
+ * @example
34
+ * <VelorisStripeProvider options={{ publicKey: 'vpk_…', stripe }}>
35
+ * <CheckoutForm />
36
+ * </VelorisStripeProvider>
37
+ */
38
+ export function VelorisStripeProvider({ options, children }: VelorisStripeProviderProps) {
39
+ const [ready, setReady] = useState(false);
40
+ const instanceRef = useRef<VelorisStripe | null>(null);
41
+
42
+ useEffect(() => {
43
+ const v = new VelorisStripe(options);
44
+ instanceRef.current = v;
45
+ v.init()
46
+ .then(() => setReady(true))
47
+ .catch(() => setReady(true)); // fail open
48
+ // eslint-disable-next-line react-hooks/exhaustive-deps
49
+ }, []);
50
+
51
+ return (
52
+ <VelorisContext.Provider value={{ instance: instanceRef.current, ready }}>
53
+ {children}
54
+ </VelorisContext.Provider>
55
+ );
56
+ }
57
+
58
+ // ── useVelorisStripe hook ─────────────────────────────────────────────────────
59
+
60
+ export interface UseVelorisStripeReturn {
61
+ /** True once Shield.js has initialised */
62
+ ready: boolean;
63
+ /** Collect fingerprint — call just before submitting the order */
64
+ getPayload: () => Promise<VelorisPayload>;
65
+ /** Full VelorisStripe instance (null until mounted) */
66
+ instance: VelorisStripe | null;
67
+ }
68
+
69
+ /**
70
+ * Access the Veloris Shield instance inside any component wrapped by
71
+ * VelorisStripeProvider.
72
+ *
73
+ * @example
74
+ * const { ready, getPayload } = useVelorisStripe();
75
+ *
76
+ * async function handleSubmit() {
77
+ * const { fingerprint } = await getPayload();
78
+ * const { paymentMethod } = await stripe.createPaymentMethod({ … });
79
+ * await fetch('/checkout', {
80
+ * method: 'POST',
81
+ * body: JSON.stringify({ paymentMethodId: paymentMethod.id, fingerprint }),
82
+ * });
83
+ * }
84
+ */
85
+ export function useVelorisStripe(): UseVelorisStripeReturn {
86
+ const { instance, ready } = useContext(VelorisContext);
87
+
88
+ const getPayload = useCallback(async (): Promise<VelorisPayload> => {
89
+ if (!instance) return { fingerprint: null, fp_version: '0.1.0' };
90
+ return instance.getPayload();
91
+ }, [instance]);
92
+
93
+ return { ready, getPayload, instance };
94
+ }
95
+
96
+ // ── useVelorisAutoHook ────────────────────────────────────────────────────────
97
+
98
+ export interface UseVelorisAutoHookOptions {
99
+ formRef: React.RefObject<HTMLFormElement>;
100
+ handler: (payload: AutoHookPayload) => Promise<void>;
101
+ billingDetails?: Record<string, unknown>;
102
+ }
103
+
104
+ /**
105
+ * Attach autoHook to a form ref. Runs once when the form mounts and the
106
+ * instance is ready.
107
+ *
108
+ * @example
109
+ * const formRef = useRef<HTMLFormElement>(null);
110
+ * useVelorisAutoHook({ formRef, handler: async ({ paymentMethodId, fingerprint }) => {
111
+ * await fetch('/checkout', { method: 'POST', body: JSON.stringify({ paymentMethodId, fingerprint }) });
112
+ * }});
113
+ * return <form ref={formRef}>…</form>;
114
+ */
115
+ export function useVelorisAutoHook({ formRef, handler, billingDetails }: UseVelorisAutoHookOptions): void {
116
+ const { instance, ready } = useContext(VelorisContext);
117
+
118
+ useEffect(() => {
119
+ if (!ready || !instance || !formRef.current) return;
120
+ instance.autoHook(formRef.current, handler, billingDetails);
121
+ // eslint-disable-next-line react-hooks/exhaustive-deps
122
+ }, [ready, instance]);
123
+ }
124
+
125
+ // Re-export core for convenience
126
+ export { VelorisStripe } from './index.js';
127
+ export type { VelorisStripeOptions, VelorisPayload, AutoHookPayload } from './index.js';
package/tsconfig.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "jsx": "react-jsx"
10
+ },
11
+ "include": ["src"]
12
+ }