@pix.js/core
Version:
Core functions for Pix.js
397 lines (391 loc) • 14.3 kB
JavaScript
// src/dynamic.ts
import { crc16ccitt } from "crc";
// src/types.ts
var PointOfInitiationMethod = /* @__PURE__ */ ((PointOfInitiationMethod3) => {
PointOfInitiationMethod3[PointOfInitiationMethod3["OnTimeOnly"] = 12] = "OnTimeOnly";
return PointOfInitiationMethod3;
})(PointOfInitiationMethod || {});
var PixKeyType = /* @__PURE__ */ ((PixKeyType2) => {
PixKeyType2["Cpf"] = "CPF";
PixKeyType2["Cnpj"] = "CNPJ";
PixKeyType2["Email"] = "EMAIL";
PixKeyType2["Phone"] = "PHONE";
PixKeyType2["Evp"] = "EVP";
return PixKeyType2;
})(PixKeyType || {});
var PixQrCodeType = /* @__PURE__ */ ((PixQrCodeType2) => {
PixQrCodeType2["Static"] = "STATIC";
PixQrCodeType2["Dynamic"] = "DYNAMIC";
return PixQrCodeType2;
})(PixQrCodeType || {});
var ParserError = class extends Error {
constructor(message) {
super(message);
this.name = "ParserError";
}
};
// src/utils.ts
var formatText = (text, maxLength) => text.substring(0, maxLength).normalize("NFD").replace(/[\u0300-\u036f]/g, "");
var appendToBrCode = (id, value) => {
const length = value.length.toString().padStart(2, "0");
return `${id}${length}${value}`;
};
var deconstructEntries = (payload, signatureLength = 4) => {
const entries = {};
let position = 0;
while (position < payload.length - signatureLength) {
const id = payload.slice(position, position + 2);
position += 2;
const length = Number.parseInt(payload.slice(position, position + 2));
position += 2;
const value = payload.slice(position, position + length);
position += length;
entries[id] = { id, length, value };
}
return entries;
};
// src/dynamic.ts
var toDynamicPix = (payload) => {
const buildPayloadParts = () => {
const parts = [
["00", "01"],
["01", payload.pointOfInitiationMethod?.toString().padStart(2, "0") ?? ""],
[
"26",
[
appendToBrCode("00", payload.merchantAccountInfo.gui ?? "br.gov.bcb.pix"),
appendToBrCode("25", payload.merchantAccountInfo.url)
].join("")
],
["52", payload.merchantCategoryCode ?? "0000"],
["53", payload.transactionCurrency ?? "986"],
["54", payload.value?.toFixed(2) ?? ""],
["58", payload.countryCode ?? "BR"],
["59", formatText(payload.merchantName, 25)],
["60", formatText(payload.merchantCity, 15)],
["61", payload.postalCode ?? ""],
["62", appendToBrCode("05", payload.additionalData.txId)],
payload.unreservedTemplate ? [
"80",
appendToBrCode("00", payload.unreservedTemplate?.gui ?? ""),
appendToBrCode("25", payload.unreservedTemplate?.url ?? "")
] : []
];
return parts.filter(([_, value]) => value).map(([id, value]) => appendToBrCode(id, value)).join("");
};
const payloadString = `${buildPayloadParts()}6304`;
const crcResult = crc16ccitt(payloadString).toString(16).toUpperCase().padStart(4, "0");
return `${payloadString}${crcResult}`;
};
var parseDynamicPix = (payload) => {
try {
const entries = deconstructEntries(payload);
entries["26"].subEntries = deconstructEntries(entries["26"]?.value ?? "");
entries["62"].subEntries = deconstructEntries(entries["62"]?.value ?? "");
if (entries["80"]) {
entries["80"].subEntries = deconstructEntries(entries["80"]?.value ?? "");
}
return {
merchantAccountInfo: {
gui: entries["26"].subEntries?.["00"]?.value,
url: entries["26"].subEntries?.["25"]?.value
},
pointOfInitiationMethod: entries["01"]?.value ? Number.parseInt(entries["01"]?.value) : void 0,
merchantCategoryCode: entries["52"]?.value,
transactionCurrency: entries["53"]?.value,
countryCode: entries["58"]?.value,
merchantName: entries["59"]?.value,
merchantCity: entries["60"]?.value,
postalCode: entries["61"]?.value,
additionalData: {
txId: entries["62"].subEntries?.["05"]?.value
},
unreservedTemplate: entries["80"] ? {
gui: entries["80"].subEntries?.["00"]?.value,
url: entries["80"].subEntries?.["25"]?.value
} : void 0
};
} catch (error) {
throw new ParserError(`Invalid dynamic pix payload ${error}`);
}
};
var isDynamicPix = (payload) => {
try {
const entries = deconstructEntries(payload);
entries["26"].subEntries = deconstructEntries(entries["26"].value);
return !!entries["26"].subEntries["25"]?.value;
} catch (error) {
return false;
}
};
// src/key-detector.ts
var pixKeyRegexes = {
email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
phone: /^(\+)?([55]{2})?\s?\(?([0-9]{2})\)?\s?([9])([0-9]{4})-?([0-9]{4})$/,
random: /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
};
function isCPF(cpf) {
const cpfDigits = cpf.replace(/[^\d]/g, "");
if (cpfDigits.length !== 11) return false;
let sum = 0;
for (let i = 0; i < 9; i++) {
sum += Number.parseInt(cpfDigits.charAt(i)) * (10 - i);
}
let digit = 11 - sum % 11;
if (digit >= 10) digit = 0;
if (digit !== Number.parseInt(cpfDigits.charAt(9))) return false;
sum = 0;
for (let i = 0; i < 10; i++) {
sum += Number.parseInt(cpfDigits.charAt(i)) * (11 - i);
}
digit = 11 - sum % 11;
if (digit >= 10) digit = 0;
if (digit !== Number.parseInt(cpfDigits.charAt(10))) return false;
return true;
}
function isCNPJ(cnpj) {
const cnpjDigits = cnpj.replace(/[^\d]/g, "");
if (cnpjDigits.length !== 14) return false;
let size = cnpjDigits.length - 2;
let numbers = cnpjDigits.substring(0, size);
const digits = cnpjDigits.substring(size);
let sum = 0;
let pos = size - 7;
for (let i = size; i >= 1; i--) {
sum += Number.parseInt(numbers.charAt(size - i)) * pos--;
if (pos < 2) pos = 9;
}
let result = sum % 11 < 2 ? 0 : 11 - sum % 11;
if (result !== Number.parseInt(digits.charAt(0))) return false;
size = size + 1;
numbers = cnpjDigits.substring(0, size);
sum = 0;
pos = size - 7;
for (let i = size; i >= 1; i--) {
sum += Number.parseInt(numbers.charAt(size - i)) * pos--;
if (pos < 2) pos = 9;
}
result = sum % 11 < 2 ? 0 : 11 - sum % 11;
if (result !== Number.parseInt(digits.charAt(1))) return false;
return true;
}
var keyDetector = {
isCPF: (key) => isCPF(key),
isCNPJ: (key) => isCNPJ(key),
isEmail: (key) => pixKeyRegexes.email.test(key),
isPhone: (key) => pixKeyRegexes.phone.test(key),
isRandom: (key) => pixKeyRegexes.random.test(key)
};
var getPixKeyType = (key) => {
switch (true) {
case keyDetector.isCPF(key):
return "CPF" /* Cpf */;
case keyDetector.isCNPJ(key):
return "CNPJ" /* Cnpj */;
case keyDetector.isEmail(key):
return "EMAIL" /* Email */;
case keyDetector.isPhone(key):
return "PHONE" /* Phone */;
case keyDetector.isRandom(key):
return "EVP" /* Evp */;
default:
throw new Error("Invalid Pix key");
}
};
// src/schemas.ts
import { z } from "zod";
var staticMerchantAccountInfoSchema = z.object({
gui: z.string(),
key: z.string(),
merchantAdditionalInfo: z.string().optional(),
fss: z.string().optional()
}).refine((schema) => {
let totalLength = schema.gui.length + schema.key.length;
if (schema.merchantAdditionalInfo) {
totalLength += schema.merchantAdditionalInfo?.length;
}
if (schema.fss) {
totalLength += schema.fss?.length;
}
return totalLength <= 99;
}, "Combined length of gui, key, merchantAdditionalInfo and fss must not exceed 99 characters");
var dynamicMerchantAccountInfoSchema = z.object({
gui: z.string(),
url: z.string().regex(/^[^:]+\.[^:]+/),
merchantAdditionalInfo: z.string().optional(),
fss: z.string().optional()
}).refine((schema) => {
let totalLength = schema.gui.length + schema.url.length;
if (schema.merchantAdditionalInfo) {
totalLength += schema.merchantAdditionalInfo?.length;
}
if (schema.fss) {
totalLength += schema.fss?.length;
}
return totalLength <= 99;
}, "Combined length of gui, url, merchantAdditionalInfo and fss must not exceed 99 characters");
var additionalData = z.object({
txId: z.string().min(1).max(25)
});
var unreservedTemplate = z.object({
gui: z.string().optional(),
url: z.string().url().optional()
}).refine((schema) => {
return (schema.gui?.length ?? 0) + (schema.url?.length ?? 0) <= 99;
}, "Combined length of gui and url must not exceed 99 characters");
var staticPixSchema = z.object({
merchantAccountInfo: staticMerchantAccountInfoSchema,
value: z.number().positive().min(1).max(13).optional(),
merchantCategoryCode: z.string().max(4),
transactionCurrency: z.string().max(3),
countryCode: z.string().max(2),
merchantName: z.string().max(25),
merchantCity: z.string().max(15),
additionalData,
postalCode: z.string().min(1).max(99).optional(),
unreservedTemplate: unreservedTemplate.optional()
}).refine((schema) => {
const totalLength = [
schema.merchantAccountInfo.gui.length,
schema.merchantAccountInfo.key.length,
schema.merchantAccountInfo.merchantAdditionalInfo?.length ?? 0,
schema.merchantAccountInfo.fss?.length ?? 0,
schema.value ? schema.value.toString().length : 0,
schema.merchantCategoryCode.length,
schema.transactionCurrency.length,
schema.countryCode.length,
schema.merchantName.length,
schema.merchantCity.length,
schema.additionalData.txId.length,
schema.postalCode?.length ?? 0
].reduce((acc, length) => acc + length, 0);
return totalLength <= 396;
}, "The qrcode length must be less than 396 characters");
var dynamicPixSchema = z.object({
merchantAccountInfo: dynamicMerchantAccountInfoSchema,
pointOfInitiationMethod: z.nativeEnum(PointOfInitiationMethod).optional(),
value: z.number().positive().min(1).max(13).optional(),
merchantCategoryCode: z.string().max(4),
transactionCurrency: z.string().max(3),
countryCode: z.string().max(2),
merchantName: z.string().max(25),
merchantCity: z.string().max(15),
additionalData,
postalCode: z.string().min(1).max(8).optional(),
unreservedTemplate: unreservedTemplate.optional()
}).refine((schema) => {
const totalLength = [
schema.merchantAccountInfo.gui.length,
schema.merchantAccountInfo.url.length,
schema.merchantAccountInfo.merchantAdditionalInfo?.length ?? 0,
schema.merchantAccountInfo.fss?.length ?? 0,
schema.pointOfInitiationMethod?.toString().length ?? 0,
schema.value ? schema.value.toString().length : 0,
schema.merchantCategoryCode.length,
schema.transactionCurrency.length,
schema.countryCode.length,
schema.merchantName.length,
schema.merchantCity.length,
schema.additionalData.txId.length,
schema.postalCode?.length ?? 0
].reduce((acc, length) => acc + length, 0);
return totalLength <= 396;
}, "The qrcode length must be less than 396 characters");
// src/static.ts
import { crc16ccitt as crc16ccitt2 } from "crc";
var toStaticPix = (payload) => {
const buildPayloadParts = () => {
const parts = [
["00", "01"],
[
"26",
[
appendToBrCode("00", payload.merchantAccountInfo.gui ?? "br.gov.bcb.pix"),
appendToBrCode("01", payload.merchantAccountInfo.key),
payload.merchantAccountInfo.merchantAdditionalInfo ? appendToBrCode("02", payload.merchantAccountInfo.merchantAdditionalInfo) : "",
payload.merchantAccountInfo.fss ? appendToBrCode("03", payload.merchantAccountInfo.fss) : ""
].join("")
],
["52", payload.merchantCategoryCode ?? "0000"],
["53", payload.transactionCurrency ?? "986"],
["54", payload.value?.toFixed(2) ?? ""],
["58", payload.countryCode ?? "BR"],
["59", formatText(payload.merchantName, 25)],
["60", formatText(payload.merchantCity, 15)],
["61", payload.postalCode ?? ""],
["62", appendToBrCode("05", payload.additionalData.txId)],
payload.unreservedTemplate ? [
"80",
appendToBrCode("00", payload.unreservedTemplate?.gui ?? ""),
appendToBrCode("25", payload.unreservedTemplate?.url ?? "")
] : []
];
return parts.filter(([_, value]) => value).map(([id, value]) => appendToBrCode(id, value)).join("");
};
const payloadString = `${buildPayloadParts()}6304`;
const crcResult = crc16ccitt2(payloadString).toString(16).toUpperCase().padStart(4, "0");
return `${payloadString}${crcResult}`;
};
var parseStaticPix = (payload) => {
try {
const entries = deconstructEntries(payload);
entries["26"].subEntries = deconstructEntries(entries["26"]?.value ?? "");
entries["62"].subEntries = deconstructEntries(entries["62"]?.value ?? "");
if (entries["80"]) {
entries["80"].subEntries = deconstructEntries(entries["80"]?.value ?? "");
}
return {
merchantAccountInfo: {
gui: entries["26"].subEntries?.["00"]?.value,
key: entries["26"].subEntries?.["01"]?.value,
merchantAdditionalInfo: entries["26"].subEntries?.["02"]?.value,
fss: entries["26"].subEntries?.["03"]?.value
},
merchantCategoryCode: entries["52"]?.value,
transactionCurrency: entries["53"]?.value,
countryCode: entries["58"]?.value,
merchantName: entries["59"]?.value,
merchantCity: entries["60"]?.value,
postalCode: entries["61"]?.value,
additionalData: {
txId: entries["62"].subEntries?.["05"]?.value
},
value: entries["54"]?.value ? Number.parseFloat(entries["54"].value) : void 0,
unreservedTemplate: entries["80"] ? {
gui: entries["80"].subEntries?.["00"]?.value,
url: entries["80"].subEntries?.["25"]?.value
} : void 0
};
} catch (error) {
throw new ParserError(`Invalid static pix payload ${error}`);
}
};
var isStaticPix = (payload) => {
try {
const entries = deconstructEntries(payload);
entries["26"].subEntries = deconstructEntries(entries["26"].value);
return !!entries["26"].subEntries["01"]?.value;
} catch (error) {
return false;
}
};
export {
ParserError,
PixKeyType,
PixQrCodeType,
PointOfInitiationMethod,
dynamicPixSchema,
getPixKeyType,
isCNPJ,
isCPF,
isDynamicPix,
isStaticPix,
keyDetector,
parseDynamicPix,
parseStaticPix,
pixKeyRegexes,
staticPixSchema,
toDynamicPix,
toStaticPix
};
//# sourceMappingURL=index.js.map