@p2pdotme/sdk 1.2.19 → 1.2.21
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/dist/country.cjs +108 -57
- package/dist/country.cjs.map +1 -1
- package/dist/country.d.cts +40 -8
- package/dist/country.d.ts +40 -8
- package/dist/country.mjs +105 -57
- package/dist/country.mjs.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/qr-parsers.cjs +94 -29
- package/dist/qr-parsers.cjs.map +1 -1
- package/dist/qr-parsers.d.cts +6 -3
- package/dist/qr-parsers.d.ts +6 -3
- package/dist/qr-parsers.mjs +94 -29
- package/dist/qr-parsers.mjs.map +1 -1
- package/package.json +1 -1
package/dist/qr-parsers.d.cts
CHANGED
|
@@ -71,13 +71,16 @@ type ParseResult = Result<ParsedQR, QRParserError>;
|
|
|
71
71
|
* a signed JWT whose `valor.original` field holds the amount. All other parsers
|
|
72
72
|
* (UPI, QRIS, QR Ph, MercadoPago, PagoMovil, Transfermóvil) and static PIX are synchronous and
|
|
73
73
|
* resolve immediately.
|
|
74
|
+
*
|
|
75
|
+
* When the country has `validateQr` (PEN, VEN, BOB), Scan & Pay must pass the
|
|
76
|
+
* same check as SELL upload — not a looser envelope.
|
|
74
77
|
*/
|
|
75
78
|
declare function parseQR(params: ParseQRParams): Promise<ParseResult>;
|
|
76
79
|
|
|
77
80
|
/**
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
+
* Same envelope as SELL upload: opaque `base64?merchantId=NNNN` (blob ≥ 40).
|
|
82
|
+
* Prefer `validateVenezuelanQr` from `@p2pdotme/sdk/country`; this alias stays
|
|
83
|
+
* for `@p2pdotme/sdk/qr-parsers` callers.
|
|
81
84
|
*/
|
|
82
85
|
declare function isPagoMovilQr(qrData: string): boolean;
|
|
83
86
|
|
package/dist/qr-parsers.d.ts
CHANGED
|
@@ -71,13 +71,16 @@ type ParseResult = Result<ParsedQR, QRParserError>;
|
|
|
71
71
|
* a signed JWT whose `valor.original` field holds the amount. All other parsers
|
|
72
72
|
* (UPI, QRIS, QR Ph, MercadoPago, PagoMovil, Transfermóvil) and static PIX are synchronous and
|
|
73
73
|
* resolve immediately.
|
|
74
|
+
*
|
|
75
|
+
* When the country has `validateQr` (PEN, VEN, BOB), Scan & Pay must pass the
|
|
76
|
+
* same check as SELL upload — not a looser envelope.
|
|
74
77
|
*/
|
|
75
78
|
declare function parseQR(params: ParseQRParams): Promise<ParseResult>;
|
|
76
79
|
|
|
77
80
|
/**
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
+
* Same envelope as SELL upload: opaque `base64?merchantId=NNNN` (blob ≥ 40).
|
|
82
|
+
* Prefer `validateVenezuelanQr` from `@p2pdotme/sdk/country`; this alias stays
|
|
83
|
+
* for `@p2pdotme/sdk/qr-parsers` callers.
|
|
81
84
|
*/
|
|
82
85
|
declare function isPagoMovilQr(qrData: string): boolean;
|
|
83
86
|
|
package/dist/qr-parsers.mjs
CHANGED
|
@@ -120,6 +120,39 @@ function validateVenezuelanQr(payload) {
|
|
|
120
120
|
if (!/^[A-Za-z0-9+/=]+$/.test(blob)) return false;
|
|
121
121
|
return /(?:^|[?&])merchantId=\d{3,4}(?:&|$)/.test(trimmed.substring(qIdx));
|
|
122
122
|
}
|
|
123
|
+
function payQrCandidate(paymentId) {
|
|
124
|
+
const trimmed = paymentId.trim();
|
|
125
|
+
const sep = trimmed.indexOf(PACKED_PAYMENT_ID_SEP);
|
|
126
|
+
return (sep >= 0 ? trimmed.slice(0, sep) : trimmed).trim();
|
|
127
|
+
}
|
|
128
|
+
function isPeruvianPayQr(payload) {
|
|
129
|
+
if (!payload.startsWith("0002")) return false;
|
|
130
|
+
return payload.includes("5802PE") && payload.includes("5303604");
|
|
131
|
+
}
|
|
132
|
+
function isVenezuelanPayQr(payload) {
|
|
133
|
+
if (!payload || payload.includes(PACKED_PAYMENT_ID_SEP)) return false;
|
|
134
|
+
const qIdx = payload.indexOf("?");
|
|
135
|
+
if (qIdx === -1) return false;
|
|
136
|
+
const blob = payload.slice(0, qIdx);
|
|
137
|
+
return blob.length > 0 && /^[A-Za-z0-9+/=]+$/.test(blob);
|
|
138
|
+
}
|
|
139
|
+
function isPhilippinePayQr(payload) {
|
|
140
|
+
if (!payload.startsWith("0002")) return false;
|
|
141
|
+
return payload.includes("5802PH") && payload.includes("5303608");
|
|
142
|
+
}
|
|
143
|
+
function isBolivianPayQr(payload) {
|
|
144
|
+
if (!payload || payload.includes(PACKED_PAYMENT_ID_SEP)) return false;
|
|
145
|
+
if (payload.startsWith("0002") && payload.includes("5802BO") && payload.includes("5303068")) {
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
const sep = payload.lastIndexOf("|");
|
|
149
|
+
if (sep < 40) return false;
|
|
150
|
+
const blob = payload.slice(0, sep);
|
|
151
|
+
const tag = payload.slice(sep + 1);
|
|
152
|
+
if (blob.length % 4 !== 0) return false;
|
|
153
|
+
if (!/^[A-Za-z0-9+/]+={0,2}$/.test(blob)) return false;
|
|
154
|
+
return /^[0-9A-Fa-f]{16,64}$/.test(tag);
|
|
155
|
+
}
|
|
123
156
|
|
|
124
157
|
// src/country/currencies/bob.ts
|
|
125
158
|
var BOB_COUNTRY_OPTION = {
|
|
@@ -135,7 +168,7 @@ var BOB_COUNTRY_OPTION = {
|
|
|
135
168
|
flag: "\u{1F1E7}\u{1F1F4}",
|
|
136
169
|
flagUrl: "https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72/1f1e7-1f1f4.png",
|
|
137
170
|
phoneCode: "+591",
|
|
138
|
-
telegramSupportChannel: "https://t.me/
|
|
171
|
+
telegramSupportChannel: "https://t.me/p2pme_bolivia_merchants",
|
|
139
172
|
twitterUsername: "p2pmebolivia",
|
|
140
173
|
smsCountryCodes: ["BO"],
|
|
141
174
|
precision: 2,
|
|
@@ -143,7 +176,11 @@ var BOB_COUNTRY_OPTION = {
|
|
|
143
176
|
disabled: false,
|
|
144
177
|
disabledPaymentTypes: [],
|
|
145
178
|
uploadPaymentQR: true,
|
|
146
|
-
validateQr: validateBolivianQr
|
|
179
|
+
validateQr: validateBolivianQr,
|
|
180
|
+
getPayQrPayload: (paymentId) => {
|
|
181
|
+
const candidate = payQrCandidate(paymentId);
|
|
182
|
+
return isBolivianPayQr(candidate) ? candidate : null;
|
|
183
|
+
}
|
|
147
184
|
};
|
|
148
185
|
|
|
149
186
|
// src/country/currencies/brl.ts
|
|
@@ -209,7 +246,8 @@ var CUP_COUNTRY_OPTION = {
|
|
|
209
246
|
precision: 2,
|
|
210
247
|
isAlpha: true,
|
|
211
248
|
disabled: false,
|
|
212
|
-
disabledPaymentTypes: []
|
|
249
|
+
disabledPaymentTypes: [],
|
|
250
|
+
transferWarning: "CUP_BANDEC_WARNING"
|
|
213
251
|
};
|
|
214
252
|
|
|
215
253
|
// src/country/currencies/ecu.ts
|
|
@@ -417,6 +455,10 @@ var PEN_COUNTRY_OPTION = {
|
|
|
417
455
|
disabledPaymentTypes: [],
|
|
418
456
|
uploadPaymentQR: true,
|
|
419
457
|
validateQr: validatePeruvianQr,
|
|
458
|
+
getPayQrPayload: (paymentId) => {
|
|
459
|
+
const candidate = payQrCandidate(paymentId);
|
|
460
|
+
return isPeruvianPayQr(candidate) ? candidate : null;
|
|
461
|
+
},
|
|
420
462
|
hydrateFieldsFromQr: (qr) => {
|
|
421
463
|
const phone = extractPeruvianPhoneFromQr(qr);
|
|
422
464
|
return phone ? { phone } : {};
|
|
@@ -442,7 +484,11 @@ var PHP_COUNTRY_OPTION = {
|
|
|
442
484
|
precision: 2,
|
|
443
485
|
isAlpha: true,
|
|
444
486
|
disabled: false,
|
|
445
|
-
disabledPaymentTypes: []
|
|
487
|
+
disabledPaymentTypes: [],
|
|
488
|
+
getPayQrPayload: (paymentId) => {
|
|
489
|
+
const candidate = payQrCandidate(paymentId);
|
|
490
|
+
return isPhilippinePayQr(candidate) ? candidate : null;
|
|
491
|
+
}
|
|
446
492
|
};
|
|
447
493
|
|
|
448
494
|
// src/country/currencies/usd.ts
|
|
@@ -489,7 +535,11 @@ var VEN_COUNTRY_OPTION = {
|
|
|
489
535
|
disabled: false,
|
|
490
536
|
disabledPaymentTypes: [],
|
|
491
537
|
uploadPaymentQR: true,
|
|
492
|
-
validateQr: validateVenezuelanQr
|
|
538
|
+
validateQr: validateVenezuelanQr,
|
|
539
|
+
getPayQrPayload: (paymentId) => {
|
|
540
|
+
const candidate = payQrCandidate(paymentId);
|
|
541
|
+
return isVenezuelanPayQr(candidate) ? candidate : null;
|
|
542
|
+
}
|
|
493
543
|
};
|
|
494
544
|
|
|
495
545
|
// src/country/countries.ts
|
|
@@ -510,6 +560,10 @@ var COUNTRY_OPTIONS = [
|
|
|
510
560
|
EUR_COUNTRY_OPTION,
|
|
511
561
|
USD_COUNTRY_OPTION
|
|
512
562
|
];
|
|
563
|
+
function getCountryOption(currency) {
|
|
564
|
+
if (!currency) return void 0;
|
|
565
|
+
return COUNTRY_OPTIONS.find((c) => c.currency === currency);
|
|
566
|
+
}
|
|
513
567
|
|
|
514
568
|
// src/qr-parsers/types.ts
|
|
515
569
|
import { err, ok } from "neverthrow";
|
|
@@ -1035,16 +1089,16 @@ function parseNGN(qrData, sellPrice) {
|
|
|
1035
1089
|
}
|
|
1036
1090
|
|
|
1037
1091
|
// src/qr-parsers/parsers/pen.ts
|
|
1038
|
-
var PEN_TAGS = { AMOUNT: "54"
|
|
1092
|
+
var PEN_TAGS = { AMOUNT: "54" };
|
|
1039
1093
|
function parsePeru(qrData, sellPrice) {
|
|
1040
1094
|
if (!qrData || typeof qrData !== "string" || qrData.trim().length === 0) {
|
|
1041
1095
|
return failure("INVALID_QR", "QR data is empty or invalid");
|
|
1042
1096
|
}
|
|
1043
1097
|
const trimmed = qrData.trim();
|
|
1044
|
-
|
|
1045
|
-
if (tags[PEN_TAGS.COUNTRY] !== "PE" || tags[PEN_TAGS.CURRENCY] !== "604") {
|
|
1098
|
+
if (!validatePeruvianQr(trimmed)) {
|
|
1046
1099
|
return failure("INVALID_QR", "Not a valid Peru Yape/Plin QR");
|
|
1047
1100
|
}
|
|
1101
|
+
const tags = extractTags(trimmed, [PEN_TAGS.AMOUNT]);
|
|
1048
1102
|
const result = { paymentAddress: trimmed };
|
|
1049
1103
|
const amountStr = tags[PEN_TAGS.AMOUNT];
|
|
1050
1104
|
if (amountStr) {
|
|
@@ -1086,21 +1140,14 @@ function parseQRPh(qrData, sellPrice) {
|
|
|
1086
1140
|
|
|
1087
1141
|
// src/qr-parsers/parsers/ven.ts
|
|
1088
1142
|
function isPagoMovilQr(qrData) {
|
|
1089
|
-
|
|
1090
|
-
return false;
|
|
1091
|
-
}
|
|
1092
|
-
const trimmed = qrData.trim();
|
|
1093
|
-
const qIdx = trimmed.indexOf("?");
|
|
1094
|
-
if (qIdx === -1) return false;
|
|
1095
|
-
const payload = trimmed.substring(0, qIdx);
|
|
1096
|
-
return !!payload && /^[A-Za-z0-9+/=]+$/.test(payload);
|
|
1143
|
+
return validateVenezuelanQr(qrData);
|
|
1097
1144
|
}
|
|
1098
1145
|
function parsePagoMovil(qrData, _sellPrice) {
|
|
1099
1146
|
if (!qrData || typeof qrData !== "string" || qrData.trim().length === 0) {
|
|
1100
1147
|
return failure("INVALID_QR", "QR data is empty or invalid");
|
|
1101
1148
|
}
|
|
1102
1149
|
const trimmed = qrData.trim();
|
|
1103
|
-
if (!
|
|
1150
|
+
if (!validateVenezuelanQr(trimmed)) {
|
|
1104
1151
|
return failure("INVALID_QR", "Not a valid Venezuelan QR code");
|
|
1105
1152
|
}
|
|
1106
1153
|
return success({ paymentAddress: trimmed });
|
|
@@ -1112,34 +1159,52 @@ async function parseQR(params) {
|
|
|
1112
1159
|
if (!qrData || typeof qrData !== "string" || qrData.trim().length === 0) {
|
|
1113
1160
|
return failure("INVALID_QR", "QR data is empty or invalid");
|
|
1114
1161
|
}
|
|
1162
|
+
let result;
|
|
1115
1163
|
switch (currency) {
|
|
1116
1164
|
case CURRENCY.INR:
|
|
1117
|
-
|
|
1165
|
+
result = parseUPI(qrData, sellPrice);
|
|
1166
|
+
break;
|
|
1118
1167
|
case CURRENCY.IDR:
|
|
1119
|
-
|
|
1168
|
+
result = parseQRIS(qrData, sellPrice);
|
|
1169
|
+
break;
|
|
1120
1170
|
case CURRENCY.BRL:
|
|
1121
|
-
|
|
1171
|
+
result = await parsePIX(qrData, sellPrice, { proxyUrl, orderId });
|
|
1172
|
+
break;
|
|
1122
1173
|
case CURRENCY.ARS:
|
|
1123
|
-
|
|
1174
|
+
result = parseMercadoPago(qrData, sellPrice);
|
|
1175
|
+
break;
|
|
1124
1176
|
case CURRENCY.VEN:
|
|
1125
|
-
|
|
1177
|
+
result = parsePagoMovil(qrData, sellPrice);
|
|
1178
|
+
break;
|
|
1126
1179
|
case CURRENCY.BOB:
|
|
1127
|
-
|
|
1180
|
+
result = parseBolivia(qrData, sellPrice);
|
|
1181
|
+
break;
|
|
1128
1182
|
case CURRENCY.NGN:
|
|
1129
|
-
|
|
1183
|
+
result = parseNGN(qrData, sellPrice);
|
|
1184
|
+
break;
|
|
1130
1185
|
case CURRENCY.COP:
|
|
1131
|
-
|
|
1186
|
+
result = parseCOP(qrData, sellPrice);
|
|
1187
|
+
break;
|
|
1132
1188
|
case CURRENCY.CUP:
|
|
1133
|
-
|
|
1189
|
+
result = parseTransfermovil(qrData, sellPrice);
|
|
1190
|
+
break;
|
|
1134
1191
|
case CURRENCY.ECU:
|
|
1135
|
-
|
|
1192
|
+
result = parseDeUna(qrData, sellPrice);
|
|
1193
|
+
break;
|
|
1136
1194
|
case CURRENCY.PEN:
|
|
1137
|
-
|
|
1195
|
+
result = parsePeru(qrData, sellPrice);
|
|
1196
|
+
break;
|
|
1138
1197
|
case CURRENCY.PHP:
|
|
1139
|
-
|
|
1198
|
+
result = parseQRPh(qrData, sellPrice);
|
|
1199
|
+
break;
|
|
1140
1200
|
default:
|
|
1141
1201
|
return failure("INVALID_CURRENCY", `Currency "${currency}" is not supported`);
|
|
1142
1202
|
}
|
|
1203
|
+
const validateQr = getCountryOption(currency)?.validateQr;
|
|
1204
|
+
if (result.isOk() && validateQr && !validateQr(qrData.trim())) {
|
|
1205
|
+
return failure("INVALID_QR", "QR does not match catalog validateQr");
|
|
1206
|
+
}
|
|
1207
|
+
return result;
|
|
1143
1208
|
}
|
|
1144
1209
|
export {
|
|
1145
1210
|
QRParserError,
|