tupay 0.16.0 → 0.18.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 +33 -0
- package/dist/cjs/cli-arguments.d.ts +28 -0
- package/dist/cjs/cli-arguments.d.ts.map +1 -0
- package/dist/cjs/cli-arguments.js +101 -0
- package/dist/cjs/cli-arguments.js.map +1 -0
- package/dist/cjs/cli.d.ts +9 -0
- package/dist/cjs/cli.d.ts.map +1 -0
- package/dist/cjs/cli.js +56 -0
- package/dist/cjs/cli.js.map +1 -0
- package/dist/cjs/index.d.ts +9 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +10 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/relais.d.ts +88 -0
- package/dist/cjs/relais.d.ts.map +1 -0
- package/dist/cjs/relais.js +156 -0
- package/dist/cjs/relais.js.map +1 -0
- package/dist/cjs/resources/accounts.d.ts +2 -1
- package/dist/cjs/resources/accounts.d.ts.map +1 -1
- package/dist/cjs/resources/accounts.js.map +1 -1
- package/dist/cjs/webhooks.d.ts +20 -0
- package/dist/cjs/webhooks.d.ts.map +1 -1
- package/dist/cjs/webhooks.js +34 -9
- package/dist/cjs/webhooks.js.map +1 -1
- package/dist/esm/cli-arguments.d.ts +28 -0
- package/dist/esm/cli-arguments.d.ts.map +1 -0
- package/dist/esm/cli-arguments.js +96 -0
- package/dist/esm/cli-arguments.js.map +1 -0
- package/dist/esm/cli.d.ts +9 -0
- package/dist/esm/cli.d.ts.map +1 -0
- package/dist/esm/cli.js +54 -0
- package/dist/esm/cli.js.map +1 -0
- package/dist/esm/index.d.ts +9 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +7 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/relais.d.ts +88 -0
- package/dist/esm/relais.d.ts.map +1 -0
- package/dist/esm/relais.js +150 -0
- package/dist/esm/relais.js.map +1 -0
- package/dist/esm/resources/accounts.d.ts +2 -1
- package/dist/esm/resources/accounts.d.ts.map +1 -1
- package/dist/esm/resources/accounts.js.map +1 -1
- package/dist/esm/webhooks.d.ts +20 -0
- package/dist/esm/webhooks.d.ts.map +1 -1
- package/dist/esm/webhooks.js +33 -9
- package/dist/esm/webhooks.js.map +1 -1
- package/package.json +4 -1
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relayer les événements de TEST vers votre poste — `tupay listen`.
|
|
3
|
+
*
|
|
4
|
+
* ── Le problème ────────────────────────────────────────────────────
|
|
5
|
+
* Tupay ne joint pas `localhost` : pour recevoir un webhook pendant que
|
|
6
|
+
* vous développez, il fallait un tunnel (Cloudflare Tunnel, ngrok) et
|
|
7
|
+
* déclarer son URL publique comme endpoint. C'est l'équivalent de
|
|
8
|
+
* `stripe listen` qui manquait.
|
|
9
|
+
*
|
|
10
|
+
* ── Comment, sans rien ouvrir ──────────────────────────────────────
|
|
11
|
+
* Le relais INTERROGE l'API (`GET /events`, avec votre clé de test) et
|
|
12
|
+
* POSTE chaque événement nouveau à votre serveur local. Aucun port à
|
|
13
|
+
* ouvrir, aucun compte de tunnel, aucun endpoint à déclarer : seule une
|
|
14
|
+
* connexion sortante vers Tupay, comme n'importe quel appel du SDK.
|
|
15
|
+
*
|
|
16
|
+
* La signature est faite avec un secret LOCAL (`whsec_…`, affiché au
|
|
17
|
+
* démarrage), et non celui d'un endpoint : le relais ne connaît pas vos
|
|
18
|
+
* secrets d'endpoint, et n'a pas à les connaître. Votre code de
|
|
19
|
+
* vérification est le même — seul le secret change.
|
|
20
|
+
*
|
|
21
|
+
* ── Ce qu'il ne fait pas ───────────────────────────────────────────
|
|
22
|
+
* Il ne relaie que ce qui naît APRÈS son démarrage (les anciens se
|
|
23
|
+
* rejouent depuis l'écran Événements), et n'enregistre aucune livraison
|
|
24
|
+
* chez Tupay : votre écran Webhooks ne le voit pas.
|
|
25
|
+
*/
|
|
26
|
+
import { randomBytes } from 'node:crypto';
|
|
27
|
+
import { generateTestHeader } from './webhooks.js';
|
|
28
|
+
/** Le plafond de l'API par page. */
|
|
29
|
+
const PAGE = 100;
|
|
30
|
+
/** Au-delà, votre serveur est considéré comme muet pour cet envoi. */
|
|
31
|
+
const DELAI_ENVOI_MS = 10_000;
|
|
32
|
+
export function genererSecretLocal() {
|
|
33
|
+
return 'whsec_' + randomBytes(32).toString('hex');
|
|
34
|
+
}
|
|
35
|
+
export class Relais {
|
|
36
|
+
/** Le plus récent événement déjà vu. `undefined` : il n'y en avait aucun. */
|
|
37
|
+
#curseur;
|
|
38
|
+
#demarre = false;
|
|
39
|
+
#o;
|
|
40
|
+
constructor(options) {
|
|
41
|
+
this.#o = {
|
|
42
|
+
source: options.source,
|
|
43
|
+
forwardTo: options.forwardTo,
|
|
44
|
+
secret: options.secret,
|
|
45
|
+
apiVersion: options.apiVersion ?? '2026-01-01',
|
|
46
|
+
journal: options.journal ?? ((l) => console.log(l)),
|
|
47
|
+
fetch: options.fetch ?? globalThis.fetch,
|
|
48
|
+
types: options.types && options.types.length > 0 ? new Set(options.types) : null,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Retient le plus récent événement existant : seuls ceux qui naîtront
|
|
53
|
+
* ENSUITE seront relayés. Relayer l'historique au démarrage ferait
|
|
54
|
+
* recevoir à votre serveur des paiements d'hier comme s'ils arrivaient.
|
|
55
|
+
*/
|
|
56
|
+
async demarrer() {
|
|
57
|
+
const page = await this.#o.source.list({ limit: 1 });
|
|
58
|
+
this.#curseur = page.data[0]?.id;
|
|
59
|
+
this.#demarre = true;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Un passage : lit ce qui est né depuis le dernier, le relaie du plus
|
|
63
|
+
* ANCIEN au plus récent — l'ordre où les faits sont arrivés. Rend le
|
|
64
|
+
* nombre d'événements relayés.
|
|
65
|
+
*/
|
|
66
|
+
async tour() {
|
|
67
|
+
if (!this.#demarre)
|
|
68
|
+
await this.demarrer();
|
|
69
|
+
let relayes = 0;
|
|
70
|
+
for (;;) {
|
|
71
|
+
// `ending_before` rend la page ADJACENTE au curseur, du plus
|
|
72
|
+
// récent au plus ancien ; `has_more` dit qu'il en reste de plus
|
|
73
|
+
// récents. Sans curseur (aucun événement au démarrage), tout ce qui
|
|
74
|
+
// existe est nouveau.
|
|
75
|
+
const page = await this.#o.source.list(this.#curseur === undefined ? { limit: PAGE } : { limit: PAGE, endingBefore: this.#curseur });
|
|
76
|
+
const nouveaux = [...page.data].reverse();
|
|
77
|
+
for (const evenement of nouveaux) {
|
|
78
|
+
if (this.#o.types === null || this.#o.types.has(evenement.type)) {
|
|
79
|
+
await this.#transmettre(evenement);
|
|
80
|
+
relayes++;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (page.data[0])
|
|
84
|
+
this.#curseur = page.data[0].id;
|
|
85
|
+
if (!page.has_more || page.data.length === 0 || this.#curseur === undefined)
|
|
86
|
+
return relayes;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async #transmettre(evenement) {
|
|
90
|
+
// L'enveloppe telle que Tupay la livre à un endpoint : même forme,
|
|
91
|
+
// donc le même code de réception.
|
|
92
|
+
const corps = JSON.stringify({
|
|
93
|
+
id: evenement.id,
|
|
94
|
+
object: 'event',
|
|
95
|
+
type: evenement.type,
|
|
96
|
+
apiVersion: this.#o.apiVersion,
|
|
97
|
+
created: evenement.created,
|
|
98
|
+
livemode: evenement.livemode,
|
|
99
|
+
data: evenement.data,
|
|
100
|
+
});
|
|
101
|
+
const heure = new Date().toLocaleTimeString('fr-FR');
|
|
102
|
+
try {
|
|
103
|
+
const reponse = await this.#o.fetch(this.#o.forwardTo, {
|
|
104
|
+
method: 'POST',
|
|
105
|
+
headers: {
|
|
106
|
+
'content-type': 'application/json',
|
|
107
|
+
'user-agent': 'Tupay-Listen/1.0',
|
|
108
|
+
'tupay-signature': generateTestHeader({ payload: corps, secret: this.#o.secret }),
|
|
109
|
+
'tupay-event-id': evenement.id,
|
|
110
|
+
'tupay-event-type': evenement.type,
|
|
111
|
+
},
|
|
112
|
+
body: corps,
|
|
113
|
+
signal: AbortSignal.timeout(DELAI_ENVOI_MS),
|
|
114
|
+
});
|
|
115
|
+
this.#o.journal(`${heure} ${evenement.type} ${evenement.id} → ${reponse.status}`);
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
// Votre serveur est arrêté ou a planté : on le dit, et on continue.
|
|
119
|
+
// Le relais ne rejoue pas — l'écran Événements le fait.
|
|
120
|
+
const raison = err instanceof Error ? err.message : String(err);
|
|
121
|
+
this.#o.journal(`${heure} ${evenement.type} ${evenement.id} → échec : ${raison}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Tourne jusqu'à `signal`. Une erreur de l'API (réseau coupé, clé
|
|
127
|
+
* révoquée) est dite et le passage suivant réessaie : un relais qui
|
|
128
|
+
* s'arrête au premier hoquet obligerait à surveiller le relais.
|
|
129
|
+
*/
|
|
130
|
+
export async function ecouter(relais, options = {}) {
|
|
131
|
+
const intervalle = options.intervalleMs ?? 2000;
|
|
132
|
+
const journal = options.journal ?? ((l) => console.error(l));
|
|
133
|
+
await relais.demarrer();
|
|
134
|
+
while (!options.signal?.aborted) {
|
|
135
|
+
try {
|
|
136
|
+
await relais.tour();
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
journal(`Lecture des événements impossible : ${err instanceof Error ? err.message : String(err)}`);
|
|
140
|
+
}
|
|
141
|
+
await new Promise((fin) => {
|
|
142
|
+
const minuteur = setTimeout(fin, intervalle);
|
|
143
|
+
options.signal?.addEventListener('abort', () => {
|
|
144
|
+
clearTimeout(minuteur);
|
|
145
|
+
fin();
|
|
146
|
+
}, { once: true });
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=relais.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relais.js","sourceRoot":"","sources":["../../src/relais.ts"],"names":[],"mappings":"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"}
|
|
@@ -19,7 +19,8 @@ export interface CreateAccountParams {
|
|
|
19
19
|
tgcRate?: TgcRate;
|
|
20
20
|
/**
|
|
21
21
|
* Coordonnées bancaires XPF du vendeur. Facultatives à la création —
|
|
22
|
-
* tant qu'elles manquent, `payoutsEnabled` est faux.
|
|
22
|
+
* tant qu'elles manquent, `payoutsEnabled` est faux. IBAN français ;
|
|
23
|
+
* la clé de contrôle est vérifiée, un caractère mal recopié est refusé.
|
|
23
24
|
*/
|
|
24
25
|
ibanXpf?: string;
|
|
25
26
|
/** Omise, le SDK génère un UUID v4 — la requête devient rejouable. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accounts.d.ts","sourceRoot":"","sources":["../../../src/resources/accounts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EAIL,KAAK,UAAU,EAChB,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EACV,OAAO,EACP,cAAc,EACd,UAAU,EACV,MAAM,EACN,OAAO,EACR,MAAM,aAAa,CAAA;AAEpB,MAAM,WAAW,mBAAmB;IAClC,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB
|
|
1
|
+
{"version":3,"file":"accounts.d.ts","sourceRoot":"","sources":["../../../src/resources/accounts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EAIL,KAAK,UAAU,EAChB,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EACV,OAAO,EACP,cAAc,EACd,UAAU,EACV,MAAM,EACN,OAAO,EACR,MAAM,aAAa,CAAA;AAEpB,MAAM,WAAW,mBAAmB;IAClC,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,sEAAsE;IACtE,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,qBAAa,QAAQ;IACP,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,WAAW;IAE1C,MAAM,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC;IAWrD,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQ5C;;;;;;OAMG;IACH;;;;;;;;;;;OAWG;IACG,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAQ1D;;;;;;OAMG;IACG,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,GAAE,UAAe,GAAG,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IAU7E,IAAI,IAAI,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;CAQ3C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accounts.js","sourceRoot":"","sources":["../../../src/resources/accounts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EACL,eAAe,EACf,eAAe,GAGhB,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"accounts.js","sourceRoot":"","sources":["../../../src/resources/accounts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EACL,eAAe,EACf,eAAe,GAGhB,MAAM,aAAa,CAAA;AAmCpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,OAAO,QAAQ;IACU;IAA7B,YAA6B,MAAmB;QAAnB,WAAM,GAAN,MAAM,CAAa;IAAG,CAAC;IAEpD,KAAK,CAAC,MAAM,CAAC,MAA2B;QACtC,MAAM,EAAE,cAAc,EAAE,GAAG,IAAI,EAAE,GAAG,MAAM,CAAA;QAC1C,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,CAAoB;YAC5D,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,WAAW;YACjB,IAAI;YACJ,cAAc,EAAE,cAAc,IAAI,WAAW,CAAC,cAAc,EAAE;SAC/D,CAAC,CAAA;QACF,OAAO,IAAI,CAAA;IACb,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,EAAU;QACvB,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,CAAoB;YAC5D,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,aAAa,kBAAkB,CAAC,EAAE,CAAC,EAAE;SAC5C,CAAC,CAAA;QACF,OAAO,IAAI,CAAA;IACb,CAAC;IAED;;;;;;OAMG;IACH;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,eAAe,CAAC,EAAU;QAC9B,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,CAA2B;YACnE,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,aAAa,kBAAkB,CAAC,EAAE,CAAC,UAAU;SACpD,CAAC,CAAA;QACF,OAAO,IAAI,CAAA;IACb,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,WAAW,CAAC,EAAU,EAAE,SAAqB,EAAE;QACnD,OAAO,eAAe,CACpB,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,CAAqB;YAC5C,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,aAAa,kBAAkB,CAAC,EAAE,CAAC,UAAU;YACnD,KAAK,EAAE,eAAe,CAAC,MAAM,CAAC;SAC/B,CAAC,CACH,CAAA;IACH,CAAC;IAED,KAAK,CAAC,IAAI;QACR,OAAO,eAAe,CACpB,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,CAAsB;YAC7C,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,WAAW;SAClB,CAAC,CACH,CAAA;IACH,CAAC;CACF"}
|
package/dist/esm/webhooks.d.ts
CHANGED
|
@@ -28,4 +28,24 @@ export interface ConstructEventOptions {
|
|
|
28
28
|
* est invalide — répondez alors 400 sans traiter l'événement.
|
|
29
29
|
*/
|
|
30
30
|
export declare function constructEvent(payload: string | Buffer, header: string | null | undefined, secret: string, options?: ConstructEventOptions): WebhookEvent;
|
|
31
|
+
/**
|
|
32
|
+
* Fabrique un en-tête `Tupay-Signature` valide — pour vos TESTS.
|
|
33
|
+
*
|
|
34
|
+
* Votre gestionnaire de webhooks se teste sans réseau : signez un corps
|
|
35
|
+
* avec le secret de test, passez-le à votre route, vérifiez qu'elle
|
|
36
|
+
* l'accepte — et qu'elle refuse un corps modifié.
|
|
37
|
+
*
|
|
38
|
+
* C'est aussi ce que fait `tupay listen` pour relayer vers votre poste.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* const corps = JSON.stringify(evenement)
|
|
42
|
+
* const entete = generateTestHeader({ payload: corps, secret: 'whsec_test' })
|
|
43
|
+
* await request(app).post('/hook').set('tupay-signature', entete).send(corps)
|
|
44
|
+
*/
|
|
45
|
+
export declare function generateTestHeader(params: {
|
|
46
|
+
payload: string;
|
|
47
|
+
secret: string;
|
|
48
|
+
/** Secondes Unix. Défaut : maintenant. */
|
|
49
|
+
timestamp?: number;
|
|
50
|
+
}): string;
|
|
31
51
|
//# sourceMappingURL=webhooks.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../../src/webhooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9C,uEAAuE;AACvE,eAAO,MAAM,yBAAyB,MAAM,CAAA;AAE5C,MAAM,WAAW,qBAAqB;IACpC,kEAAkE;IAClE,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,iCAAiC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;
|
|
1
|
+
{"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../../src/webhooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9C,uEAAuE;AACvE,eAAO,MAAM,yBAAyB,MAAM,CAAA;AAE5C,MAAM,WAAW,qBAAqB;IACpC,kEAAkE;IAClE,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,iCAAiC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAgCD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,GAAG,MAAM,EACxB,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACjC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,qBAA0B,GAClC,YAAY,CAsDd;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE;IACzC,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,GAAG,MAAM,CAIT"}
|
package/dist/esm/webhooks.js
CHANGED
|
@@ -15,7 +15,7 @@ import { TupaySignatureVerificationError } from './errors.js';
|
|
|
15
15
|
export const DEFAULT_TOLERANCE_SECONDS = 300;
|
|
16
16
|
function parseHeader(header) {
|
|
17
17
|
let timestamp = null;
|
|
18
|
-
|
|
18
|
+
const v1 = [];
|
|
19
19
|
for (const part of header.split(',')) {
|
|
20
20
|
const separator = part.indexOf('=');
|
|
21
21
|
if (separator === -1)
|
|
@@ -28,10 +28,10 @@ function parseHeader(header) {
|
|
|
28
28
|
timestamp = parsed;
|
|
29
29
|
}
|
|
30
30
|
else if (key === 'v1') {
|
|
31
|
-
v1
|
|
31
|
+
v1.push(value);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
-
return timestamp === null || v1 ===
|
|
34
|
+
return timestamp === null || v1.length === 0 ? null : { timestamp, v1 };
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
37
37
|
* Vérifie la signature et renvoie l'événement typé.
|
|
@@ -59,12 +59,17 @@ export function constructEvent(payload, header, secret, options = {}) {
|
|
|
59
59
|
throw new TupaySignatureVerificationError(`Horodatage hors tolérance (${tolerance} s) — possible rejeu d’une livraison capturée.`);
|
|
60
60
|
}
|
|
61
61
|
const raw = typeof payload === 'string' ? payload : payload.toString('utf8');
|
|
62
|
-
const expected = createHmac('sha256', secret)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
const expected = Buffer.from(createHmac('sha256', secret).update(`${parts.timestamp}.${raw}`, 'utf8').digest('hex'), 'utf8');
|
|
63
|
+
// UNE signature qui correspond suffit (renouvellement du secret en
|
|
64
|
+
// cours). Toutes sont comparées, sans sortie anticipée. Longueurs
|
|
65
|
+
// comparées d'abord : timingSafeEqual lève si elles diffèrent.
|
|
66
|
+
let valide = false;
|
|
67
|
+
for (const candidat of parts.v1) {
|
|
68
|
+
const recu = Buffer.from(candidat, 'utf8');
|
|
69
|
+
if (recu.length === expected.length && timingSafeEqual(expected, recu))
|
|
70
|
+
valide = true;
|
|
71
|
+
}
|
|
72
|
+
if (!valide) {
|
|
68
73
|
throw new TupaySignatureVerificationError('Signature invalide — la charge utile ne correspond pas au secret de cet endpoint.');
|
|
69
74
|
}
|
|
70
75
|
try {
|
|
@@ -74,4 +79,23 @@ export function constructEvent(payload, header, secret, options = {}) {
|
|
|
74
79
|
throw new TupaySignatureVerificationError('Signature valide mais corps illisible : le JSON est malformé.');
|
|
75
80
|
}
|
|
76
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Fabrique un en-tête `Tupay-Signature` valide — pour vos TESTS.
|
|
84
|
+
*
|
|
85
|
+
* Votre gestionnaire de webhooks se teste sans réseau : signez un corps
|
|
86
|
+
* avec le secret de test, passez-le à votre route, vérifiez qu'elle
|
|
87
|
+
* l'accepte — et qu'elle refuse un corps modifié.
|
|
88
|
+
*
|
|
89
|
+
* C'est aussi ce que fait `tupay listen` pour relayer vers votre poste.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* const corps = JSON.stringify(evenement)
|
|
93
|
+
* const entete = generateTestHeader({ payload: corps, secret: 'whsec_test' })
|
|
94
|
+
* await request(app).post('/hook').set('tupay-signature', entete).send(corps)
|
|
95
|
+
*/
|
|
96
|
+
export function generateTestHeader(params) {
|
|
97
|
+
const t = params.timestamp ?? Math.floor(Date.now() / 1000);
|
|
98
|
+
const v1 = createHmac('sha256', params.secret).update(`${t}.${params.payload}`, 'utf8').digest('hex');
|
|
99
|
+
return `t=${t},v1=${v1}`;
|
|
100
|
+
}
|
|
77
101
|
//# sourceMappingURL=webhooks.js.map
|
package/dist/esm/webhooks.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhooks.js","sourceRoot":"","sources":["../../src/webhooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AACzD,OAAO,EAAE,+BAA+B,EAAE,MAAM,aAAa,CAAA;AAG7D,uEAAuE;AACvE,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,CAAA;
|
|
1
|
+
{"version":3,"file":"webhooks.js","sourceRoot":"","sources":["../../src/webhooks.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tupay",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "SDK Node officiel de Tupay : encaissement en XPF pour la Polynésie française.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
"main": "./dist/cjs/index.js",
|
|
11
11
|
"module": "./dist/esm/index.js",
|
|
12
12
|
"types": "./dist/esm/index.d.ts",
|
|
13
|
+
"bin": {
|
|
14
|
+
"tupay": "./dist/esm/cli.js"
|
|
15
|
+
},
|
|
13
16
|
"exports": {
|
|
14
17
|
".": {
|
|
15
18
|
"import": {
|