@eupi/taler 0.1.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,62 @@
1
+ # @eupi/taler
2
+
3
+ Top up GNU Taler reserves with standard European payment QR codes.
4
+
5
+ ## How it works
6
+
7
+ A Taler wallet is funded by wiring money to the exchange's bank account with the
8
+ **reserve public key** as the transfer subject. The exchange watches its account
9
+ (via LibEuFin/Nexus), finds the key in the subject of an incoming credit, and
10
+ credits that reserve.
11
+
12
+ This package encodes that flow as an **EPC069-12 QR code** (the "EPC QR" / GiroCode
13
+ that many European banking apps scan natively). The QR carries the exchange's IBAN,
14
+ an optional amount, and the reserve public key as remittance text. The payer scans
15
+ it in their own banking app and confirms; since October 2025 the resulting SEPA
16
+ Instant Credit Transfer reaches the exchange in seconds. No Taler-specific software
17
+ is needed on the payer's side, and no payment service provider sits in between.
18
+
19
+ ```
20
+ Taler wallet payer's own banking app exchange bank account
21
+ reserve keypair ──QR code──► scan, confirm transfer ──SCT──► subject: <reserve pub>
22
+
23
+ coins withdrawn ◄─────────── exchange credits reserve ◄────── LibEuFin/Nexus
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ ```ts
29
+ import { encodeTalerTopupQr, parseTalerTopupQr, findReservePub } from "@eupi/taler";
30
+ import QRCode from "qrcode";
31
+
32
+ // Wallet or exchange side: build the QR for a withdrawal
33
+ const payload = encodeTalerTopupQr({
34
+ accountName: "Taler Exchange Demo",
35
+ iban: "DE71 1102 2033 0123 4567 89",
36
+ amount: 50, // omit for an open-amount QR
37
+ reservePub: "ABCDEFGHJKMNPQRSTVWXYZ0123456789ABCDEFGHJKMNPQRSTVWX",
38
+ });
39
+ await QRCode.toDataURL(payload, { errorCorrectionLevel: "M" });
40
+
41
+ // Integration side: recognise top-up QR codes
42
+ const topup = parseTalerTopupQr(scannedText);
43
+ // topup.reservePub, topup.iban, topup.amount
44
+
45
+ // Bank-watcher side: match incoming transfer subjects
46
+ const pub = findReservePub("Taler ABCDEFGH... withdrawal");
47
+ ```
48
+
49
+ Validation is inherited from [`@eupi/qr`](../qr) (IBAN check digits, amount range,
50
+ payload limits) plus Taler reserve key checks (52-character Crockford base32 in
51
+ Taler's alphabet: 0-9, A-Z without I, L, O, U). `findReservePub` tolerates
52
+ surrounding text, since banks may decorate the subject line.
53
+
54
+ ## Roadmap
55
+
56
+ A runnable end-to-end demonstration against libeufin-bank (create accounts, render
57
+ the QR, execute the scanned transfer, observe the reserve credit) is planned as the
58
+ next step, along with EN 18184 QR support offered upstream to the Taler wallets.
59
+
60
+ ## License
61
+
62
+ Apache-2.0
package/dist/index.cjs ADDED
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ EpcQrError: () => import_qr.EpcQrError,
24
+ RESERVE_PUB_RE: () => RESERVE_PUB_RE,
25
+ TalerTopupError: () => TalerTopupError,
26
+ encodeTalerTopupQr: () => encodeTalerTopupQr,
27
+ findReservePub: () => findReservePub,
28
+ isValidReservePub: () => isValidReservePub,
29
+ parseTalerTopupQr: () => parseTalerTopupQr
30
+ });
31
+ module.exports = __toCommonJS(index_exports);
32
+ var import_qr = require("@eupi/qr");
33
+ var RESERVE_PUB_RE = /^[0-9A-HJKMNP-TV-Z]{52}$/;
34
+ function isValidReservePub(input) {
35
+ return RESERVE_PUB_RE.test(input);
36
+ }
37
+ function findReservePub(subject) {
38
+ for (const token of subject.toUpperCase().split(/[^0-9A-Z]+/)) {
39
+ if (RESERVE_PUB_RE.test(token)) return token;
40
+ }
41
+ return void 0;
42
+ }
43
+ var TalerTopupError = class extends Error {
44
+ issues;
45
+ constructor(message, issues) {
46
+ super(message);
47
+ this.name = "TalerTopupError";
48
+ this.issues = issues;
49
+ }
50
+ };
51
+ function encodeTalerTopupQr(options) {
52
+ const reservePub = options.reservePub.toUpperCase();
53
+ if (!isValidReservePub(reservePub)) {
54
+ throw new TalerTopupError("invalid Taler reserve public key", [
55
+ {
56
+ field: "reservePub",
57
+ message: "must be 52 Crockford base32 characters (0-9, A-Z without I, L, O, U)"
58
+ }
59
+ ]);
60
+ }
61
+ return (0, import_qr.encodeEpcQr)({
62
+ name: options.accountName,
63
+ iban: options.iban,
64
+ text: reservePub,
65
+ ...options.bic !== void 0 ? { bic: options.bic } : {},
66
+ ...options.amount !== void 0 ? { amount: options.amount } : {},
67
+ ...options.crlf !== void 0 ? { crlf: options.crlf } : {}
68
+ });
69
+ }
70
+ function parseTalerTopupQr(payload) {
71
+ const { data } = (0, import_qr.decodeEpcQr)(payload);
72
+ const subject = data.text ?? data.reference ?? "";
73
+ const reservePub = findReservePub(subject);
74
+ if (reservePub === void 0) {
75
+ throw new TalerTopupError("no Taler reserve public key in remittance information", [
76
+ { field: "text", message: "expected a 52-character Crockford base32 reserve public key" }
77
+ ]);
78
+ }
79
+ return {
80
+ reservePub,
81
+ iban: data.iban,
82
+ accountName: data.name,
83
+ ...data.bic !== void 0 ? { bic: data.bic } : {},
84
+ ...data.amount !== void 0 ? { amount: data.amount } : {}
85
+ };
86
+ }
87
+ // Annotate the CommonJS export names for ESM import in node:
88
+ 0 && (module.exports = {
89
+ EpcQrError,
90
+ RESERVE_PUB_RE,
91
+ TalerTopupError,
92
+ encodeTalerTopupQr,
93
+ findReservePub,
94
+ isValidReservePub,
95
+ parseTalerTopupQr
96
+ });
97
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Bridge between GNU Taler and the European payment QR standards.\n *\n * A Taler wallet is funded by wiring money to the exchange's bank account\n * with the reserve public key as the transfer subject: the exchange watches\n * its account (via LibEuFin/Nexus), finds the reserve public key in the\n * subject of an incoming credit, and credits that reserve.\n *\n * This package encodes that flow as a standard EPC069-12 QR code (the\n * \"EPC QR\" / GiroCode many European banking apps scan natively): the QR\n * carries the exchange's IBAN, an optional amount, and the reserve public\n * key as remittance text. Scanning it in a regular banking app produces a\n * correctly addressed SEPA (Instant) Credit Transfer that funds the\n * reserve, with no Taler-specific software on the payer's side.\n */\n\nimport { EpcQrError, decodeEpcQr, encodeEpcQr } from \"@eupi/qr\";\n\n/**\n * Taler encodes binary data in Crockford base32 using the alphabet\n * 0-9 A-Z without I, L, O, U. A reserve public key is a 32-byte EdDSA\n * public key, which encodes to 52 characters.\n */\nexport const RESERVE_PUB_RE = /^[0-9A-HJKMNP-TV-Z]{52}$/;\n\n/** True when the input is a well-formed Taler reserve public key. */\nexport function isValidReservePub(input: string): boolean {\n return RESERVE_PUB_RE.test(input);\n}\n\n/**\n * Extracts the first well-formed reserve public key token from a transfer\n * subject. Exchanges tolerate surrounding text in the subject line, and\n * some banks prepend or append their own text; this scans token-wise.\n */\nexport function findReservePub(subject: string): string | undefined {\n for (const token of subject.toUpperCase().split(/[^0-9A-Z]+/)) {\n if (RESERVE_PUB_RE.test(token)) return token;\n }\n return undefined;\n}\n\nexport interface TalerTopupIssue {\n field: string;\n message: string;\n}\n\nexport class TalerTopupError extends Error {\n readonly issues: TalerTopupIssue[];\n constructor(message: string, issues: TalerTopupIssue[]) {\n super(message);\n this.name = \"TalerTopupError\";\n this.issues = issues;\n }\n}\n\nexport interface TalerTopupOptions {\n /** Account holder name of the exchange's bank account, up to 70 characters. */\n accountName: string;\n /** IBAN of the exchange's bank account. */\n iban: string;\n /** BIC of the exchange's bank; optional within the EEA. */\n bic?: string;\n /**\n * Amount in euro (number or numeric string). Omit for an open-amount QR\n * where the payer chooses the top-up amount in their banking app.\n */\n amount?: number | string;\n /** The Taler reserve public key to credit (52-character Crockford base32). */\n reservePub: string;\n /** Use CRLF as the payload element separator instead of LF. */\n crlf?: boolean;\n}\n\n/**\n * Builds an EPC069-12 QR payload that tops up the given Taler reserve when\n * scanned and confirmed in the payer's own banking app. Render it with any\n * QR library at error correction level M.\n *\n * @throws TalerTopupError on a malformed reserve public key; EpcQrError on\n * invalid bank data (propagated from @eupi/qr).\n */\nexport function encodeTalerTopupQr(options: TalerTopupOptions): string {\n const reservePub = options.reservePub.toUpperCase();\n if (!isValidReservePub(reservePub)) {\n throw new TalerTopupError(\"invalid Taler reserve public key\", [\n {\n field: \"reservePub\",\n message: \"must be 52 Crockford base32 characters (0-9, A-Z without I, L, O, U)\",\n },\n ]);\n }\n return encodeEpcQr({\n name: options.accountName,\n iban: options.iban,\n text: reservePub,\n ...(options.bic !== undefined ? { bic: options.bic } : {}),\n ...(options.amount !== undefined ? { amount: options.amount } : {}),\n ...(options.crlf !== undefined ? { crlf: options.crlf } : {}),\n });\n}\n\nexport interface TalerTopup {\n /** The reserve public key found in the remittance text. */\n reservePub: string;\n /** IBAN of the receiving (exchange) account. */\n iban: string;\n /** Account holder name of the receiving account. */\n accountName: string;\n bic?: string;\n /** Amount in euro as a numeric string, when the QR fixes one. */\n amount?: string;\n}\n\n/**\n * Parses an EPC069-12 payload and extracts the Taler top-up details.\n *\n * @throws EpcQrError when the payload is not a valid EPC QR;\n * TalerTopupError when it contains no reserve public key.\n */\nexport function parseTalerTopupQr(payload: string): TalerTopup {\n const { data } = decodeEpcQr(payload);\n const subject = data.text ?? data.reference ?? \"\";\n const reservePub = findReservePub(subject);\n if (reservePub === undefined) {\n throw new TalerTopupError(\"no Taler reserve public key in remittance information\", [\n { field: \"text\", message: \"expected a 52-character Crockford base32 reserve public key\" },\n ]);\n }\n return {\n reservePub,\n iban: data.iban,\n accountName: data.name,\n ...(data.bic !== undefined ? { bic: data.bic } : {}),\n ...(data.amount !== undefined ? { amount: data.amount } : {}),\n };\n}\n\nexport { EpcQrError };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgBA,gBAAqD;AAO9C,IAAM,iBAAiB;AAGvB,SAAS,kBAAkB,OAAwB;AACxD,SAAO,eAAe,KAAK,KAAK;AAClC;AAOO,SAAS,eAAe,SAAqC;AAClE,aAAW,SAAS,QAAQ,YAAY,EAAE,MAAM,YAAY,GAAG;AAC7D,QAAI,eAAe,KAAK,KAAK,EAAG,QAAO;AAAA,EACzC;AACA,SAAO;AACT;AAOO,IAAM,kBAAN,cAA8B,MAAM;AAAA,EAChC;AAAA,EACT,YAAY,SAAiB,QAA2B;AACtD,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AAAA,EAChB;AACF;AA4BO,SAAS,mBAAmB,SAAoC;AACrE,QAAM,aAAa,QAAQ,WAAW,YAAY;AAClD,MAAI,CAAC,kBAAkB,UAAU,GAAG;AAClC,UAAM,IAAI,gBAAgB,oCAAoC;AAAA,MAC5D;AAAA,QACE,OAAO;AAAA,QACP,SAAS;AAAA,MACX;AAAA,IACF,CAAC;AAAA,EACH;AACA,aAAO,uBAAY;AAAA,IACjB,MAAM,QAAQ;AAAA,IACd,MAAM,QAAQ;AAAA,IACd,MAAM;AAAA,IACN,GAAI,QAAQ,QAAQ,SAAY,EAAE,KAAK,QAAQ,IAAI,IAAI,CAAC;AAAA,IACxD,GAAI,QAAQ,WAAW,SAAY,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;AAAA,IACjE,GAAI,QAAQ,SAAS,SAAY,EAAE,MAAM,QAAQ,KAAK,IAAI,CAAC;AAAA,EAC7D,CAAC;AACH;AAoBO,SAAS,kBAAkB,SAA6B;AAC7D,QAAM,EAAE,KAAK,QAAI,uBAAY,OAAO;AACpC,QAAM,UAAU,KAAK,QAAQ,KAAK,aAAa;AAC/C,QAAM,aAAa,eAAe,OAAO;AACzC,MAAI,eAAe,QAAW;AAC5B,UAAM,IAAI,gBAAgB,yDAAyD;AAAA,MACjF,EAAE,OAAO,QAAQ,SAAS,8DAA8D;AAAA,IAC1F,CAAC;AAAA,EACH;AACA,SAAO;AAAA,IACL;AAAA,IACA,MAAM,KAAK;AAAA,IACX,aAAa,KAAK;AAAA,IAClB,GAAI,KAAK,QAAQ,SAAY,EAAE,KAAK,KAAK,IAAI,IAAI,CAAC;AAAA,IAClD,GAAI,KAAK,WAAW,SAAY,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;AAAA,EAC7D;AACF;","names":[]}
@@ -0,0 +1,86 @@
1
+ export { EpcQrError } from '@eupi/qr';
2
+
3
+ /**
4
+ * Bridge between GNU Taler and the European payment QR standards.
5
+ *
6
+ * A Taler wallet is funded by wiring money to the exchange's bank account
7
+ * with the reserve public key as the transfer subject: the exchange watches
8
+ * its account (via LibEuFin/Nexus), finds the reserve public key in the
9
+ * subject of an incoming credit, and credits that reserve.
10
+ *
11
+ * This package encodes that flow as a standard EPC069-12 QR code (the
12
+ * "EPC QR" / GiroCode many European banking apps scan natively): the QR
13
+ * carries the exchange's IBAN, an optional amount, and the reserve public
14
+ * key as remittance text. Scanning it in a regular banking app produces a
15
+ * correctly addressed SEPA (Instant) Credit Transfer that funds the
16
+ * reserve, with no Taler-specific software on the payer's side.
17
+ */
18
+
19
+ /**
20
+ * Taler encodes binary data in Crockford base32 using the alphabet
21
+ * 0-9 A-Z without I, L, O, U. A reserve public key is a 32-byte EdDSA
22
+ * public key, which encodes to 52 characters.
23
+ */
24
+ declare const RESERVE_PUB_RE: RegExp;
25
+ /** True when the input is a well-formed Taler reserve public key. */
26
+ declare function isValidReservePub(input: string): boolean;
27
+ /**
28
+ * Extracts the first well-formed reserve public key token from a transfer
29
+ * subject. Exchanges tolerate surrounding text in the subject line, and
30
+ * some banks prepend or append their own text; this scans token-wise.
31
+ */
32
+ declare function findReservePub(subject: string): string | undefined;
33
+ interface TalerTopupIssue {
34
+ field: string;
35
+ message: string;
36
+ }
37
+ declare class TalerTopupError extends Error {
38
+ readonly issues: TalerTopupIssue[];
39
+ constructor(message: string, issues: TalerTopupIssue[]);
40
+ }
41
+ interface TalerTopupOptions {
42
+ /** Account holder name of the exchange's bank account, up to 70 characters. */
43
+ accountName: string;
44
+ /** IBAN of the exchange's bank account. */
45
+ iban: string;
46
+ /** BIC of the exchange's bank; optional within the EEA. */
47
+ bic?: string;
48
+ /**
49
+ * Amount in euro (number or numeric string). Omit for an open-amount QR
50
+ * where the payer chooses the top-up amount in their banking app.
51
+ */
52
+ amount?: number | string;
53
+ /** The Taler reserve public key to credit (52-character Crockford base32). */
54
+ reservePub: string;
55
+ /** Use CRLF as the payload element separator instead of LF. */
56
+ crlf?: boolean;
57
+ }
58
+ /**
59
+ * Builds an EPC069-12 QR payload that tops up the given Taler reserve when
60
+ * scanned and confirmed in the payer's own banking app. Render it with any
61
+ * QR library at error correction level M.
62
+ *
63
+ * @throws TalerTopupError on a malformed reserve public key; EpcQrError on
64
+ * invalid bank data (propagated from @eupi/qr).
65
+ */
66
+ declare function encodeTalerTopupQr(options: TalerTopupOptions): string;
67
+ interface TalerTopup {
68
+ /** The reserve public key found in the remittance text. */
69
+ reservePub: string;
70
+ /** IBAN of the receiving (exchange) account. */
71
+ iban: string;
72
+ /** Account holder name of the receiving account. */
73
+ accountName: string;
74
+ bic?: string;
75
+ /** Amount in euro as a numeric string, when the QR fixes one. */
76
+ amount?: string;
77
+ }
78
+ /**
79
+ * Parses an EPC069-12 payload and extracts the Taler top-up details.
80
+ *
81
+ * @throws EpcQrError when the payload is not a valid EPC QR;
82
+ * TalerTopupError when it contains no reserve public key.
83
+ */
84
+ declare function parseTalerTopupQr(payload: string): TalerTopup;
85
+
86
+ export { RESERVE_PUB_RE, type TalerTopup, TalerTopupError, type TalerTopupIssue, type TalerTopupOptions, encodeTalerTopupQr, findReservePub, isValidReservePub, parseTalerTopupQr };
@@ -0,0 +1,86 @@
1
+ export { EpcQrError } from '@eupi/qr';
2
+
3
+ /**
4
+ * Bridge between GNU Taler and the European payment QR standards.
5
+ *
6
+ * A Taler wallet is funded by wiring money to the exchange's bank account
7
+ * with the reserve public key as the transfer subject: the exchange watches
8
+ * its account (via LibEuFin/Nexus), finds the reserve public key in the
9
+ * subject of an incoming credit, and credits that reserve.
10
+ *
11
+ * This package encodes that flow as a standard EPC069-12 QR code (the
12
+ * "EPC QR" / GiroCode many European banking apps scan natively): the QR
13
+ * carries the exchange's IBAN, an optional amount, and the reserve public
14
+ * key as remittance text. Scanning it in a regular banking app produces a
15
+ * correctly addressed SEPA (Instant) Credit Transfer that funds the
16
+ * reserve, with no Taler-specific software on the payer's side.
17
+ */
18
+
19
+ /**
20
+ * Taler encodes binary data in Crockford base32 using the alphabet
21
+ * 0-9 A-Z without I, L, O, U. A reserve public key is a 32-byte EdDSA
22
+ * public key, which encodes to 52 characters.
23
+ */
24
+ declare const RESERVE_PUB_RE: RegExp;
25
+ /** True when the input is a well-formed Taler reserve public key. */
26
+ declare function isValidReservePub(input: string): boolean;
27
+ /**
28
+ * Extracts the first well-formed reserve public key token from a transfer
29
+ * subject. Exchanges tolerate surrounding text in the subject line, and
30
+ * some banks prepend or append their own text; this scans token-wise.
31
+ */
32
+ declare function findReservePub(subject: string): string | undefined;
33
+ interface TalerTopupIssue {
34
+ field: string;
35
+ message: string;
36
+ }
37
+ declare class TalerTopupError extends Error {
38
+ readonly issues: TalerTopupIssue[];
39
+ constructor(message: string, issues: TalerTopupIssue[]);
40
+ }
41
+ interface TalerTopupOptions {
42
+ /** Account holder name of the exchange's bank account, up to 70 characters. */
43
+ accountName: string;
44
+ /** IBAN of the exchange's bank account. */
45
+ iban: string;
46
+ /** BIC of the exchange's bank; optional within the EEA. */
47
+ bic?: string;
48
+ /**
49
+ * Amount in euro (number or numeric string). Omit for an open-amount QR
50
+ * where the payer chooses the top-up amount in their banking app.
51
+ */
52
+ amount?: number | string;
53
+ /** The Taler reserve public key to credit (52-character Crockford base32). */
54
+ reservePub: string;
55
+ /** Use CRLF as the payload element separator instead of LF. */
56
+ crlf?: boolean;
57
+ }
58
+ /**
59
+ * Builds an EPC069-12 QR payload that tops up the given Taler reserve when
60
+ * scanned and confirmed in the payer's own banking app. Render it with any
61
+ * QR library at error correction level M.
62
+ *
63
+ * @throws TalerTopupError on a malformed reserve public key; EpcQrError on
64
+ * invalid bank data (propagated from @eupi/qr).
65
+ */
66
+ declare function encodeTalerTopupQr(options: TalerTopupOptions): string;
67
+ interface TalerTopup {
68
+ /** The reserve public key found in the remittance text. */
69
+ reservePub: string;
70
+ /** IBAN of the receiving (exchange) account. */
71
+ iban: string;
72
+ /** Account holder name of the receiving account. */
73
+ accountName: string;
74
+ bic?: string;
75
+ /** Amount in euro as a numeric string, when the QR fixes one. */
76
+ amount?: string;
77
+ }
78
+ /**
79
+ * Parses an EPC069-12 payload and extracts the Taler top-up details.
80
+ *
81
+ * @throws EpcQrError when the payload is not a valid EPC QR;
82
+ * TalerTopupError when it contains no reserve public key.
83
+ */
84
+ declare function parseTalerTopupQr(payload: string): TalerTopup;
85
+
86
+ export { RESERVE_PUB_RE, type TalerTopup, TalerTopupError, type TalerTopupIssue, type TalerTopupOptions, encodeTalerTopupQr, findReservePub, isValidReservePub, parseTalerTopupQr };
package/dist/index.js ADDED
@@ -0,0 +1,66 @@
1
+ // src/index.ts
2
+ import { EpcQrError, decodeEpcQr, encodeEpcQr } from "@eupi/qr";
3
+ var RESERVE_PUB_RE = /^[0-9A-HJKMNP-TV-Z]{52}$/;
4
+ function isValidReservePub(input) {
5
+ return RESERVE_PUB_RE.test(input);
6
+ }
7
+ function findReservePub(subject) {
8
+ for (const token of subject.toUpperCase().split(/[^0-9A-Z]+/)) {
9
+ if (RESERVE_PUB_RE.test(token)) return token;
10
+ }
11
+ return void 0;
12
+ }
13
+ var TalerTopupError = class extends Error {
14
+ issues;
15
+ constructor(message, issues) {
16
+ super(message);
17
+ this.name = "TalerTopupError";
18
+ this.issues = issues;
19
+ }
20
+ };
21
+ function encodeTalerTopupQr(options) {
22
+ const reservePub = options.reservePub.toUpperCase();
23
+ if (!isValidReservePub(reservePub)) {
24
+ throw new TalerTopupError("invalid Taler reserve public key", [
25
+ {
26
+ field: "reservePub",
27
+ message: "must be 52 Crockford base32 characters (0-9, A-Z without I, L, O, U)"
28
+ }
29
+ ]);
30
+ }
31
+ return encodeEpcQr({
32
+ name: options.accountName,
33
+ iban: options.iban,
34
+ text: reservePub,
35
+ ...options.bic !== void 0 ? { bic: options.bic } : {},
36
+ ...options.amount !== void 0 ? { amount: options.amount } : {},
37
+ ...options.crlf !== void 0 ? { crlf: options.crlf } : {}
38
+ });
39
+ }
40
+ function parseTalerTopupQr(payload) {
41
+ const { data } = decodeEpcQr(payload);
42
+ const subject = data.text ?? data.reference ?? "";
43
+ const reservePub = findReservePub(subject);
44
+ if (reservePub === void 0) {
45
+ throw new TalerTopupError("no Taler reserve public key in remittance information", [
46
+ { field: "text", message: "expected a 52-character Crockford base32 reserve public key" }
47
+ ]);
48
+ }
49
+ return {
50
+ reservePub,
51
+ iban: data.iban,
52
+ accountName: data.name,
53
+ ...data.bic !== void 0 ? { bic: data.bic } : {},
54
+ ...data.amount !== void 0 ? { amount: data.amount } : {}
55
+ };
56
+ }
57
+ export {
58
+ EpcQrError,
59
+ RESERVE_PUB_RE,
60
+ TalerTopupError,
61
+ encodeTalerTopupQr,
62
+ findReservePub,
63
+ isValidReservePub,
64
+ parseTalerTopupQr
65
+ };
66
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Bridge between GNU Taler and the European payment QR standards.\n *\n * A Taler wallet is funded by wiring money to the exchange's bank account\n * with the reserve public key as the transfer subject: the exchange watches\n * its account (via LibEuFin/Nexus), finds the reserve public key in the\n * subject of an incoming credit, and credits that reserve.\n *\n * This package encodes that flow as a standard EPC069-12 QR code (the\n * \"EPC QR\" / GiroCode many European banking apps scan natively): the QR\n * carries the exchange's IBAN, an optional amount, and the reserve public\n * key as remittance text. Scanning it in a regular banking app produces a\n * correctly addressed SEPA (Instant) Credit Transfer that funds the\n * reserve, with no Taler-specific software on the payer's side.\n */\n\nimport { EpcQrError, decodeEpcQr, encodeEpcQr } from \"@eupi/qr\";\n\n/**\n * Taler encodes binary data in Crockford base32 using the alphabet\n * 0-9 A-Z without I, L, O, U. A reserve public key is a 32-byte EdDSA\n * public key, which encodes to 52 characters.\n */\nexport const RESERVE_PUB_RE = /^[0-9A-HJKMNP-TV-Z]{52}$/;\n\n/** True when the input is a well-formed Taler reserve public key. */\nexport function isValidReservePub(input: string): boolean {\n return RESERVE_PUB_RE.test(input);\n}\n\n/**\n * Extracts the first well-formed reserve public key token from a transfer\n * subject. Exchanges tolerate surrounding text in the subject line, and\n * some banks prepend or append their own text; this scans token-wise.\n */\nexport function findReservePub(subject: string): string | undefined {\n for (const token of subject.toUpperCase().split(/[^0-9A-Z]+/)) {\n if (RESERVE_PUB_RE.test(token)) return token;\n }\n return undefined;\n}\n\nexport interface TalerTopupIssue {\n field: string;\n message: string;\n}\n\nexport class TalerTopupError extends Error {\n readonly issues: TalerTopupIssue[];\n constructor(message: string, issues: TalerTopupIssue[]) {\n super(message);\n this.name = \"TalerTopupError\";\n this.issues = issues;\n }\n}\n\nexport interface TalerTopupOptions {\n /** Account holder name of the exchange's bank account, up to 70 characters. */\n accountName: string;\n /** IBAN of the exchange's bank account. */\n iban: string;\n /** BIC of the exchange's bank; optional within the EEA. */\n bic?: string;\n /**\n * Amount in euro (number or numeric string). Omit for an open-amount QR\n * where the payer chooses the top-up amount in their banking app.\n */\n amount?: number | string;\n /** The Taler reserve public key to credit (52-character Crockford base32). */\n reservePub: string;\n /** Use CRLF as the payload element separator instead of LF. */\n crlf?: boolean;\n}\n\n/**\n * Builds an EPC069-12 QR payload that tops up the given Taler reserve when\n * scanned and confirmed in the payer's own banking app. Render it with any\n * QR library at error correction level M.\n *\n * @throws TalerTopupError on a malformed reserve public key; EpcQrError on\n * invalid bank data (propagated from @eupi/qr).\n */\nexport function encodeTalerTopupQr(options: TalerTopupOptions): string {\n const reservePub = options.reservePub.toUpperCase();\n if (!isValidReservePub(reservePub)) {\n throw new TalerTopupError(\"invalid Taler reserve public key\", [\n {\n field: \"reservePub\",\n message: \"must be 52 Crockford base32 characters (0-9, A-Z without I, L, O, U)\",\n },\n ]);\n }\n return encodeEpcQr({\n name: options.accountName,\n iban: options.iban,\n text: reservePub,\n ...(options.bic !== undefined ? { bic: options.bic } : {}),\n ...(options.amount !== undefined ? { amount: options.amount } : {}),\n ...(options.crlf !== undefined ? { crlf: options.crlf } : {}),\n });\n}\n\nexport interface TalerTopup {\n /** The reserve public key found in the remittance text. */\n reservePub: string;\n /** IBAN of the receiving (exchange) account. */\n iban: string;\n /** Account holder name of the receiving account. */\n accountName: string;\n bic?: string;\n /** Amount in euro as a numeric string, when the QR fixes one. */\n amount?: string;\n}\n\n/**\n * Parses an EPC069-12 payload and extracts the Taler top-up details.\n *\n * @throws EpcQrError when the payload is not a valid EPC QR;\n * TalerTopupError when it contains no reserve public key.\n */\nexport function parseTalerTopupQr(payload: string): TalerTopup {\n const { data } = decodeEpcQr(payload);\n const subject = data.text ?? data.reference ?? \"\";\n const reservePub = findReservePub(subject);\n if (reservePub === undefined) {\n throw new TalerTopupError(\"no Taler reserve public key in remittance information\", [\n { field: \"text\", message: \"expected a 52-character Crockford base32 reserve public key\" },\n ]);\n }\n return {\n reservePub,\n iban: data.iban,\n accountName: data.name,\n ...(data.bic !== undefined ? { bic: data.bic } : {}),\n ...(data.amount !== undefined ? { amount: data.amount } : {}),\n };\n}\n\nexport { EpcQrError };\n"],"mappings":";AAgBA,SAAS,YAAY,aAAa,mBAAmB;AAO9C,IAAM,iBAAiB;AAGvB,SAAS,kBAAkB,OAAwB;AACxD,SAAO,eAAe,KAAK,KAAK;AAClC;AAOO,SAAS,eAAe,SAAqC;AAClE,aAAW,SAAS,QAAQ,YAAY,EAAE,MAAM,YAAY,GAAG;AAC7D,QAAI,eAAe,KAAK,KAAK,EAAG,QAAO;AAAA,EACzC;AACA,SAAO;AACT;AAOO,IAAM,kBAAN,cAA8B,MAAM;AAAA,EAChC;AAAA,EACT,YAAY,SAAiB,QAA2B;AACtD,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AAAA,EAChB;AACF;AA4BO,SAAS,mBAAmB,SAAoC;AACrE,QAAM,aAAa,QAAQ,WAAW,YAAY;AAClD,MAAI,CAAC,kBAAkB,UAAU,GAAG;AAClC,UAAM,IAAI,gBAAgB,oCAAoC;AAAA,MAC5D;AAAA,QACE,OAAO;AAAA,QACP,SAAS;AAAA,MACX;AAAA,IACF,CAAC;AAAA,EACH;AACA,SAAO,YAAY;AAAA,IACjB,MAAM,QAAQ;AAAA,IACd,MAAM,QAAQ;AAAA,IACd,MAAM;AAAA,IACN,GAAI,QAAQ,QAAQ,SAAY,EAAE,KAAK,QAAQ,IAAI,IAAI,CAAC;AAAA,IACxD,GAAI,QAAQ,WAAW,SAAY,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;AAAA,IACjE,GAAI,QAAQ,SAAS,SAAY,EAAE,MAAM,QAAQ,KAAK,IAAI,CAAC;AAAA,EAC7D,CAAC;AACH;AAoBO,SAAS,kBAAkB,SAA6B;AAC7D,QAAM,EAAE,KAAK,IAAI,YAAY,OAAO;AACpC,QAAM,UAAU,KAAK,QAAQ,KAAK,aAAa;AAC/C,QAAM,aAAa,eAAe,OAAO;AACzC,MAAI,eAAe,QAAW;AAC5B,UAAM,IAAI,gBAAgB,yDAAyD;AAAA,MACjF,EAAE,OAAO,QAAQ,SAAS,8DAA8D;AAAA,IAC1F,CAAC;AAAA,EACH;AACA,SAAO;AAAA,IACL;AAAA,IACA,MAAM,KAAK;AAAA,IACX,aAAa,KAAK;AAAA,IAClB,GAAI,KAAK,QAAQ,SAAY,EAAE,KAAK,KAAK,IAAI,IAAI,CAAC;AAAA,IAClD,GAAI,KAAK,WAAW,SAAY,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;AAAA,EAC7D;AACF;","names":[]}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@eupi/taler",
3
+ "version": "0.1.0",
4
+ "description": "Top up GNU Taler reserves with standard European payment QR codes (EPC069-12 / EN 18184)",
5
+ "keywords": [
6
+ "taler",
7
+ "gnu-taler",
8
+ "sepa",
9
+ "sepa-instant",
10
+ "qr",
11
+ "girocode",
12
+ "epc-qr",
13
+ "payments",
14
+ "withdrawal",
15
+ "reserve"
16
+ ],
17
+ "license": "Apache-2.0",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/LedgerInnovation/eupi.git",
21
+ "directory": "packages/taler"
22
+ },
23
+ "type": "module",
24
+ "sideEffects": false,
25
+ "engines": {
26
+ "node": ">=18"
27
+ },
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "import": "./dist/index.js",
32
+ "require": "./dist/index.cjs"
33
+ }
34
+ },
35
+ "main": "./dist/index.cjs",
36
+ "module": "./dist/index.js",
37
+ "types": "./dist/index.d.ts",
38
+ "files": [
39
+ "dist",
40
+ "README.md"
41
+ ],
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "scripts": {
46
+ "build": "tsup src/index.ts --format esm,cjs --dts --sourcemap --clean",
47
+ "test": "vitest run",
48
+ "typecheck": "tsc --noEmit"
49
+ },
50
+ "dependencies": {
51
+ "@eupi/qr": "workspace:^"
52
+ },
53
+ "devDependencies": {
54
+ "tsup": "^8.5.0",
55
+ "typescript": "^5.8.0",
56
+ "vitest": "^3.2.0"
57
+ }
58
+ }