forked from ZyntariHQ/Invoisio
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathparse-qr.ts
More file actions
142 lines (122 loc) · 4.04 KB
/
Copy pathparse-qr.ts
File metadata and controls
142 lines (122 loc) · 4.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/**
* Parses scanned QR code strings into Stellar payment data.
*
* Supports:
* 1. SEP-0007 pay URI — web+stellar:pay?destination=...&amount=...&memo=...
* 2. Raw G-address (56-char Stellar public key)
* 3. Invoisio deep-link — invoisio://pay?... (same query params as SEP-0007)
*/
export type MemoType = "text" | "id" | "hash" | "return";
export interface ParsedPayment {
destination: string;
amount?: string;
assetCode?: string;
assetIssuer?: string;
memo?: string;
memoType: MemoType;
/** Reconstructed SEP-0007 URI ready to deep-link into a Stellar wallet */
sep0007Uri: string;
}
/**
* A typed parse failure. `code` lets the UI tailor the message and recovery
* actions, while `message` is a plain-language explanation for the user.
*/
export type ParseQrErrorCode =
| "unsupported-format"
| "missing-destination"
| "invalid-destination";
export interface ParseQrError {
code: ParseQrErrorCode;
message: string;
}
const G_ADDRESS_RE = /^G[A-Z2-7]{55}$/;
function isValidGAddress(addr: string): boolean {
return G_ADDRESS_RE.test(addr);
}
function parseMemoType(raw: string | null): MemoType {
switch (raw?.toLowerCase()) {
case "text":
return "text";
case "id":
return "id";
case "hash":
return "hash";
case "return":
return "return";
default:
return "text";
}
}
function buildSep0007Uri(p: Omit<ParsedPayment, "sep0007Uri">): string {
const parts = [`destination=${encodeURIComponent(p.destination)}`];
if (p.amount) parts.push(`amount=${encodeURIComponent(p.amount)}`);
if (p.assetCode) parts.push(`asset_code=${encodeURIComponent(p.assetCode)}`);
if (p.assetIssuer)
parts.push(`asset_issuer=${encodeURIComponent(p.assetIssuer)}`);
if (p.memo) {
parts.push(`memo=${encodeURIComponent(p.memo)}`);
parts.push(`memo_type=${p.memoType}`);
}
return `web+stellar:pay?${parts.join("&")}`;
}
/**
* @returns ParsedPayment on success, or a ParseQrError describing what went
* wrong so the UI can offer a clear recovery path.
*/
export function parseQrCode(raw: string): ParsedPayment | ParseQrError {
const trimmed = raw.trim();
// 1. Raw G-address
if (isValidGAddress(trimmed)) {
const parsed: Omit<ParsedPayment, "sep0007Uri"> = {
destination: trimmed,
memoType: "text",
};
return { ...parsed, sep0007Uri: buildSep0007Uri(parsed) };
}
// 2. SEP-0007 or Invoisio deep-link
const isSep0007 = trimmed.startsWith("web+stellar:pay?");
const isInvoisio = trimmed.startsWith("invoisio://pay?");
if (!isSep0007 && !isInvoisio) {
return {
code: "unsupported-format",
message:
"This QR code isn't a Stellar payment request. Expected a Stellar address (G...) or a web+stellar: payment link.",
};
}
let queryString: string;
if (isSep0007) {
queryString = trimmed.slice("web+stellar:pay?".length);
} else {
queryString = trimmed.slice("invoisio://pay?".length);
}
// URLSearchParams works in React Native (Hermes supports it)
const params = new URLSearchParams(queryString);
const destination = params.get("destination");
if (!destination) {
return {
code: "missing-destination",
message:
"This QR code doesn't include a destination address. Ask the merchant to regenerate the payment QR code.",
};
}
if (!isValidGAddress(destination)) {
return {
code: "invalid-destination",
message: `Invalid destination address: ${destination}. It must be a valid Stellar public key that starts with "G".`,
};
}
const memoType = parseMemoType(params.get("memo_type"));
const amount = params.get("amount");
const assetCode = params.get("asset_code");
const assetIssuer = params.get("asset_issuer");
const memo = params.get("memo");
const parsed: Omit<ParsedPayment, "sep0007Uri"> = {
destination,
memoType,
...(amount !== null && { amount }),
...(assetCode !== null && { assetCode }),
...(assetIssuer !== null && { assetIssuer }),
...(memo !== null && { memo }),
};
return { ...parsed, sep0007Uri: buildSep0007Uri(parsed) };
}