UNPKG

ecpay-gateway

Version:
427 lines 19.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const uuid_1 = require("uuid"); const axios_1 = tslib_1.__importDefault(require("axios")); const crypto_1 = tslib_1.__importDefault(require("crypto")); const debug_1 = tslib_1.__importDefault(require("debug")); const parse_1 = tslib_1.__importDefault(require("date-fns/parse")); const format_1 = tslib_1.__importDefault(require("date-fns/format")); const getYear_1 = tslib_1.__importDefault(require("date-fns/getYear")); const getMonth_1 = tslib_1.__importDefault(require("date-fns/getMonth")); const Payment_1 = require("./errors/Payment"); const debugInvoice = debug_1.default('ECPayGateway:Invoice'); var InvoiceCarrierType; (function (InvoiceCarrierType) { // eslint-disable-next-line no-unused-vars InvoiceCarrierType[InvoiceCarrierType["PRINT"] = 0] = "PRINT"; // eslint-disable-next-line no-unused-vars InvoiceCarrierType[InvoiceCarrierType["LOVE_CODE"] = 1] = "LOVE_CODE"; // eslint-disable-next-line no-unused-vars InvoiceCarrierType[InvoiceCarrierType["GERRN_WORLD"] = 2] = "GERRN_WORLD"; // eslint-disable-next-line no-unused-vars InvoiceCarrierType[InvoiceCarrierType["NATURAL"] = 3] = "NATURAL"; // eslint-disable-next-line no-unused-vars InvoiceCarrierType[InvoiceCarrierType["MOBILE"] = 4] = "MOBILE"; })(InvoiceCarrierType = exports.InvoiceCarrierType || (exports.InvoiceCarrierType = {})); const NODE_ENV = process.env.NODE_ENV || 'development'; class Invoice { constructor({ number, prefix, randomCode, createdAt, buyerVAT, sellerVAT, items, }) { this.taxRatio = 0.05; this.hashKey = 'INVOICE_HASH_KEY'; this.hashIv = 'INVOICE_HASH_IV_'; this.number = number; this.prefix = prefix.toUpperCase(); this.randomCode = randomCode; this.createdAt = createdAt; this.buyerVAT = buyerVAT; this.sellerVAT = sellerVAT; this.items = items; } get totalTaxFreePrice() { return this.items.reduce((sum, item) => sum + Math.round(item.unitPrice / (1 + this.taxRatio)) * item.amount, 0); } get totalPrice() { return this.items.reduce((sum, item) => sum + item.unitPrice * item.amount, 0); } get yearMonthText() { const date = parse_1.default(this.createdAt, 'yyyy-MM-dd HH:mm:ss', new Date()); const year = getYear_1.default(date) - 1911; const month = getMonth_1.default(date) + 1; const endMonth = Math.ceil(month / 2) * 2; const startMonth = endMonth - 1; return `${year}年${`${startMonth}`.padStart(2, '0')}-${`${endMonth}`.padStart(2, '0')}月`; } get barcodeText() { const date = parse_1.default(this.createdAt, 'yyyy-MM-dd HH:mm:ss', new Date()); const year = getYear_1.default(date) - 1911; const month = getMonth_1.default(date) + 1; const endMonth = Math.ceil(month / 2) * 2; return `${year}${`${endMonth}`.padStart(2, '0')}${this.prefix}${this.number}${this.randomCode}`; } get invoiceNumber() { return `${this.prefix}-${this.number}`; } get firstQRCodeText() { const cipher = crypto_1.default.createCipheriv('aes-128-cbc', this.hashKey, this.hashIv); cipher.setAutoPadding(true); return `${this.prefix}${this.number}${getYear_1.default(parse_1.default(this.createdAt, 'yyyy-MM-dd HH:mm:ss', new Date())) - 1911}${format_1.default(parse_1.default(this.createdAt, 'yyyy-MM-dd HH:mm:ss', new Date()), 'MMdd')}${this.randomCode}${`${this.totalTaxFreePrice}`.padStart(8, '0')}${`${this.totalPrice}`.padStart(8, '0')}${this.buyerVAT || '00000000'}${this.sellerVAT}${[ cipher.update(`${this.prefix}${this.number}${this.randomCode}`, 'utf8', 'base64'), cipher.final('base64'), ].join('')}:**********:${this.items.length}:${Math.max(this.items.length, 2)}:1:${this.items .slice(0, 2) .map(item => `${item.name}:${item.amount}:${item.unitPrice}`) .join(':')}`.padEnd(180, ' '); } get secondQRCodeText() { const cipher = crypto_1.default.createCipheriv('aes-128-cbc', this.hashKey, this.hashIv); cipher.setAutoPadding(true); return `**${this.items .slice(2) .map(item => `${item.name}:${item.amount}:${item.unitPrice}`) .join(':')}`.padEnd(180, ' '); } } exports.Invoice = Invoice; class InvoiceGateway { constructor({ merchantId, hashKey, hashIv, development } = { merchantId: '2000132', hashKey: 'ejCk326UnaZWKisg', hashIv: 'q9jcZX8Ib9LM8wYk', development: true, }) { this.HOST = development ? 'https://einvoice-stage.ecpay.com.tw/B2CInvoice' : 'https://einvoice.ecpay.com.tw/B2CInvoice'; this.MERCHANT_ID = merchantId; this.HASH_KEY = hashKey; this.HASH_IV = hashIv; } static getCarrierTypeCode(type) { switch (type) { case InvoiceCarrierType.GERRN_WORLD: return '1'; case InvoiceCarrierType.NATURAL: return '2'; case InvoiceCarrierType.MOBILE: return '3'; case InvoiceCarrierType.LOVE_CODE: default: return ''; } } encrypt(data) { const encodedData = encodeURIComponent(JSON.stringify(data)); const cipher = crypto_1.default.createCipheriv('aes-128-cbc', this.HASH_KEY, this.HASH_IV); cipher.setAutoPadding(true); return [cipher.update(encodedData, 'utf8', 'base64'), cipher.final('base64')].join(''); } decrypt(encryptedData) { const decipher = crypto_1.default.createDecipheriv('aes-128-cbc', this.HASH_KEY, this.HASH_IV); return JSON.parse(decodeURIComponent([decipher.update(encryptedData, 'base64', 'utf8'), decipher.final('utf8')].join(''))); } getValidCarrierNumber(type, number) { switch (type) { case InvoiceCarrierType.MOBILE: if (!number || !/^\/[0-9A-Z+-.]{7}$/.test(number)) throw new Payment_1.InvalidMobileCarrierNumberType(); return this.getValidMobileCarrierNumber(number); case InvoiceCarrierType.NATURAL: if (!number || !/^[A-Z]{2}\d{14}$/.test(number)) throw new Payment_1.InvalidNaturalCarrierNumberType(); return number; case InvoiceCarrierType.GERRN_WORLD: case InvoiceCarrierType.LOVE_CODE: default: return ''; } } getValidLoveCode(loveCode) { return tslib_1.__awaiter(this, void 0, void 0, function* () { if (!loveCode) throw new Payment_1.InvalidLoveCode(); const ts = Math.floor(Date.now() / 1000); const id = uuid_1.v4(); const { data: { Data }, } = yield axios_1.default({ method: 'post', url: `${this.HOST}/CheckLoveCode`, data: { MerchantID: this.MERCHANT_ID, RqHeader: { Timestamp: ts, RqId: id, Revision: '3.0.0', }, Data: this.encrypt({ MerchantID: this.MERCHANT_ID, LoveCode: loveCode, }), }, }); const { RtnCode, IsExist } = this.decrypt(Data); switch (RtnCode) { case 1: if (IsExist === 'Y') return loveCode; throw new Payment_1.InvalidLoveCode(); case 100000100: debugInvoice('Love Code Verification System Maintenance'); throw new Payment_1.LoveCodeVerificationSystemInMaintenance(); default: debugInvoice('Love Code Verification System Error', RtnCode); throw new Payment_1.LoveCodeVerificationSystemError(); } }); } getValidMobileCarrierNumber(number) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const ts = Math.floor(Date.now() / 1000); const id = uuid_1.v4(); const { data: { Data }, } = yield axios_1.default({ method: 'post', url: `${this.HOST}/CheckBarcode`, data: { MerchantID: this.MERCHANT_ID, RqHeader: { Timestamp: ts, RqId: id, Revision: '3.0.0', }, Data: this.encrypt({ MerchantID: this.MERCHANT_ID, BarCode: number, }), }, }); const { RtnCode, IsExist } = this.decrypt(Data); switch (RtnCode) { case 1: if (IsExist === 'Y') return number; throw new Payment_1.InvalidMobileBarcode(); case 100000100: debugInvoice('Mobile Barcode Verification System Maintenance'); throw new Payment_1.MobileBarcodeVerificationSystemInMaintenance(); default: debugInvoice('Mobile Barcode Verification System Error', RtnCode); throw new Payment_1.MobileBarcodeVerificationSystemError(); } }); } sendNotification(invoiceNumber, email) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const ts = Math.floor(Date.now() / 1000); const id = uuid_1.v4(); const { data: { Data }, } = yield axios_1.default({ method: 'post', url: `${this.HOST}/InvoiceNotify`, data: { MerchantID: this.MERCHANT_ID, RqHeader: { Timestamp: ts, RqId: id, Revision: '3.0.0', }, Data: this.encrypt({ MerchantID: this.MERCHANT_ID, InvoiceNo: invoiceNumber, NotifyMail: email, Notify: 'E', InvoiceTag: 'I', Notified: 'A', }), }, }); const { RtnCode, RtnMsg, } = this.decrypt(Data); switch (RtnCode) { case 1: debugInvoice('Invoice Notification Sent!', invoiceNumber); break; default: debugInvoice(`${RtnCode} ${RtnMsg}`); throw new Payment_1.SendInvoiceNotificationFailed(); } }); } invalidAllowance({ invoiceNumber, allowanceNumber, reason }) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const timestamp = Math.floor(Date.now() / 1000); const id = uuid_1.v4(); const { data: { Data }, } = yield axios_1.default({ method: 'post', url: `${this.HOST}/AllowanceInvalid`, data: { MerchantID: this.MERCHANT_ID, RqHeader: { Timestamp: timestamp, RqID: id, Revision: '3.0.0', }, Data: this.encrypt({ MerchantID: this.MERCHANT_ID, InvoiceNo: invoiceNumber, AllowanceNo: allowanceNumber, Reason: reason, }), }, }); const { RtnCode, RtnMsg, } = this.decrypt(Data); switch (RtnCode) { case 1: break; default: debugInvoice(`${RtnCode} ${RtnMsg}`); throw new Payment_1.InvalidAllowanceFailed(); } }); } invalidInvoice({ invoiceNumber, invoiceDate, reason }) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const timestamp = Math.floor(Date.now() / 1000); const id = uuid_1.v4(); const { data: { Data }, } = yield axios_1.default({ method: 'post', url: `${this.HOST}/Invalid`, data: { MerchantID: this.MERCHANT_ID, RqHeader: { Timestamp: timestamp, RqID: id, Revision: '3.0.0', }, Data: this.encrypt({ MerchantID: this.MERCHANT_ID, InvoiceNo: invoiceNumber, InvoiceDate: invoiceDate, Reason: reason, }), }, }); const { RtnCode, RtnMsg, } = this.decrypt(Data); switch (RtnCode) { case 1: break; default: debugInvoice(`${RtnCode} ${RtnMsg}`); throw new Payment_1.InvalidInvoiceFailed(); } }); } allowanceInvoice({ invoiceNumber, invoiceDate, items, email, }) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const timestamp = Math.floor(Date.now() / 1000); const id = uuid_1.v4(); const { data: { Data }, } = yield axios_1.default({ method: 'post', url: `${this.HOST}/Allowance`, data: { MerchantID: this.MERCHANT_ID, RqHeader: { Timestamp: timestamp, RqID: id, Revision: '3.0.0', }, Data: this.encrypt(Object.assign(Object.assign({ MerchantID: this.MERCHANT_ID, InvoiceNo: invoiceNumber, InvoiceDate: invoiceDate }, (email ? { AllowanceNotify: 'E', NotifyMail: email, } : { AllowanceNotify: 'N', })), { AllowanceAmount: items.reduce((sum, item) => sum + item.unitPrice * item.amount, 0), Items: items.map((item, index) => ({ ItemSeq: index, ItemName: item.name, ItemCount: item.amount, ItemWord: item.unit || '式', ItemPrice: item.unitPrice, ItemTaxType: '1', ItemAmount: item.amount * item.unitPrice, ItemRemark: '', })) })), }, }); const { RtnCode, RtnMsg, IA_Allow_No: allowanceNumber, IA_Date: date, IA_Remain_Allowance_Amt: remainingAmount, } = this.decrypt(Data); switch (RtnCode) { case 1: return { allowanceNumber, invoiceNumber, date, remainingAmount, }; default: debugInvoice(`${RtnCode} ${RtnMsg}`); throw new Payment_1.AllowanceInvoiceFailed(); } }); } issueInvoice({ orderId, customer: { memberId, VATNumber, name, address, mobilePhone, email }, carrier: { type: carrierType, loveCode, number: carrierNumber }, items, }) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const timestamp = Math.floor(Date.now() / 1000); const id = uuid_1.v4(); const { data: { Data }, } = yield axios_1.default({ method: 'post', url: `${this.HOST}/Issue`, data: { MerchantID: this.MERCHANT_ID, RqHeader: { Timestamp: timestamp, RqID: id, Revision: '3.0.0', }, Data: this.encrypt({ MerchantID: this.MERCHANT_ID, RelateNumber: orderId, CustomerID: `${memberId}`, CustomerIdentifier: VATNumber || '', CustomerName: name, CustomerAddr: address || '', CustomerPhone: mobilePhone, CustomerEmail: email, Print: carrierType === InvoiceCarrierType.PRINT ? '1' : '0', Donation: carrierType === InvoiceCarrierType.LOVE_CODE ? '1' : '0', LoveCode: carrierType === InvoiceCarrierType.LOVE_CODE ? yield this.getValidLoveCode(loveCode) : '', CarrierType: InvoiceGateway.getCarrierTypeCode(carrierType), CarrierNum: yield this.getValidCarrierNumber(carrierType, carrierNumber), TaxType: '1', SalesAmount: items.reduce((sum, item) => sum + item.unitPrice * item.amount, 0), InvoiceRemark: '', Items: items.map((item, index) => ({ ItemSeq: index, ItemName: item.name, ItemCount: item.amount, ItemWord: item.unit || '式', ItemPrice: item.unitPrice, ItemTaxType: '1', ItemAmount: item.amount * item.unitPrice, ItemRemark: '', })), InvType: '07', vat: '1', }), }, }); const { RtnCode, RtnMsg, InvoiceNo, InvoiceDate, RandomNumber, } = this.decrypt(Data); switch (RtnCode) { case 1: if (InvoiceNo && InvoiceDate && RandomNumber) { if (NODE_ENV !== 'production') { this.sendNotification(InvoiceNo, email); } return { orderId, invoiceNumber: InvoiceNo, date: InvoiceDate, randomNumber: RandomNumber, }; } throw new Payment_1.IssueInvoiceFailed(); default: debugInvoice(`${RtnCode} ${RtnMsg}`); throw new Payment_1.IssueInvoiceFailed(); } }); } } exports.InvoiceGateway = InvoiceGateway; //# sourceMappingURL=Invoice.js.map