telebirr-receipt 0.0.1 → 1.0.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.
Potentially problematic release.
This version of telebirr-receipt might be problematic. Click here for more details.
- package/README.md +9 -52
- package/index.js +39 -39
- package/package.json +9 -7
- package/LICENSE +0 -14
package/README.md
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
|
5
5
|
`telebirr-receipt` Is a small nodejs package to parse and verify
|
6
6
|
telebirr transaction receipt. Any telebirr transaction
|
7
|
-
receipt can be found through
|
7
|
+
receipt can be found through url
|
8
8
|
`https://transactioninfo.ethiotelecom.et/receipt/{receipt_no}`
|
9
9
|
by replacing `{receipt_no}` with receipt number or id which is sent from telebirr
|
10
10
|
transaction SMS or the full receipt url is also sent via SMS.
|
@@ -22,13 +22,13 @@ npm install telebirr-receipt
|
|
22
22
|
```javascript
|
23
23
|
const {receipt, utils: {loadReceipt, parseFromHTML}} = require('telebirr-receipt')
|
24
24
|
|
25
|
-
//
|
25
|
+
// for debugging only security warning
|
26
26
|
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0
|
27
27
|
|
28
|
-
// TODO: replace `
|
29
|
-
loadReceipt({
|
30
|
-
const {verify, verifyOnly, verifyAll, equals} = receipt(parseFromHTML(htmlAsString), {to:
|
31
|
-
verify((
|
28
|
+
// TODO: replace `receipt_no`
|
29
|
+
loadReceipt({receipt_no: "ADQ..."}).then(htmlAsString => {
|
30
|
+
const {verify, verifyOnly, verifyAll, equals} = receipt(parseFromHTML(htmlAsString), {to: "Someone"})
|
31
|
+
verify((parsed_fields, pre_defined_fields) => {
|
32
32
|
// do anything with receipt
|
33
33
|
})
|
34
34
|
}).catch(reason => {
|
@@ -41,7 +41,7 @@ loadReceipt({receiptNo: 'ADQ...'}).then(htmlAsString => {
|
|
41
41
|
#### Using custom receipt parser with custom string source
|
42
42
|
|
43
43
|
```javascript
|
44
|
-
const {receipt} = require(
|
44
|
+
const {receipt} = require("telebirr-receipt");
|
45
45
|
|
46
46
|
function parse() {
|
47
47
|
const source = 'payer_name=Abebe;amount=5000;to=Kebede'
|
@@ -58,50 +58,7 @@ function parse() {
|
|
58
58
|
const {verifyAll, verify, equals} = receipt(parse(), {to: 'Kebede', amount: '5000'})
|
59
59
|
|
60
60
|
// match expected receiver's name with the name found on receipt
|
61
|
-
console.log(verify((
|
61
|
+
console.log(verify((parsed_info, my_info) => equals(parsed_info?.to, my_info?.to)))
|
62
62
|
|
63
63
|
console.log(verifyAll(['payer_name'])) // match every field but `payer_name`
|
64
|
-
```
|
65
|
-
|
66
|
-
### Use case
|
67
|
-
|
68
|
-
The main use case of this project is to overcome payment api unavailability and requirements in Ethiopia. Based on this
|
69
|
-
project you can get paid and verify your payment transactions.
|
70
|
-
|
71
|
-
1. Your customers may fill their personal or payment information
|
72
|
-
2. Your customers pay you via telebirr
|
73
|
-
3. You let your customers know how to verify their payment by entering their transaction number or link which is
|
74
|
-
received from 127 / telebirr SMS.
|
75
|
-
4. So you can verify that who send the money to you.
|
76
|
-
|
77
|
-
### IMPORTANT
|
78
|
-
|
79
|
-
The telebirr receipt website may be modified or changed at anytime, so it is important to test HTML parser every time
|
80
|
-
before letting users pay and before verifying.
|
81
|
-
You could test the parser simply by hard coding a sample transaction information manually and matching it with parsed
|
82
|
-
from url.
|
83
|
-
|
84
|
-
### Known issues
|
85
|
-
|
86
|
-
1. `loadReceipt` fails with error: `unable to verify the first certificate`, code: `UNABLE_TO_VERIFY_LEAF_SIGNATURE`.
|
87
|
-
Disabling certificate verification could fix the problem, but it is not recommended.
|
88
|
-
Let me know if there is good solution to this problem.
|
89
|
-
2. `parseFromHTML` may return different receipt field values or even empty and omit field values because of
|
90
|
-
the variability of the receipt website based on transaction such as transferring to bank and direct telebirr
|
91
|
-
transferring have different receipt format and soon. Thus, you should take a look at the return data of the
|
92
|
-
function `parseFromHTML` for different transaction destinations.
|
93
|
-
|
94
|
-
### DISCLAIMER
|
95
|
-
|
96
|
-
The open-source code provided is offered "as is" without warranty of any kind, either express or implied, including, but
|
97
|
-
not limited to, the implied warranties of merchantability and fitness for a particular purpose. The author of this code
|
98
|
-
shall not be liable for any damages arising from the use of this code, including, but not limited to, direct, indirect,
|
99
|
-
incidental, special, or consequential damages.
|
100
|
-
|
101
|
-
Users of this open-source code are solely responsible for testing and verifying the code's suitability for their
|
102
|
-
specific purposes. The author of this code makes no representations or warranties about the code's accuracy,
|
103
|
-
reliability, completeness, or timeliness. The user assumes all risk associated with the use of this code and agrees to
|
104
|
-
indemnify and hold harmless the author of this code from any and all claims, damages, or liabilities arising from the
|
105
|
-
use of this code.
|
106
|
-
|
107
|
-
By using this open-source code, the user acknowledges and agrees to these terms and conditions.
|
64
|
+
```
|
package/index.js
CHANGED
@@ -1,45 +1,45 @@
|
|
1
1
|
/**
|
2
2
|
* Initialize receipt
|
3
|
-
* @param {Object}
|
4
|
-
* @param {Object}
|
3
|
+
* @param {Object} parsed_fields Parsed receipt information
|
4
|
+
* @param {Object} pre_defined_fields Expected receipt information, used for verification
|
5
5
|
* @return {Object}
|
6
6
|
*/
|
7
|
-
function receipt(
|
7
|
+
function receipt(parsed_fields = {}, pre_defined_fields = {}) {
|
8
8
|
/**
|
9
9
|
* Compare two strings or numbers
|
10
|
-
* @param {string
|
11
|
-
* @param {string
|
12
|
-
* @return {
|
10
|
+
* @param {string} a
|
11
|
+
* @param {string} b
|
12
|
+
* @return {Boolean} `true` if a and b are both string or number and equal otherwise `false`
|
13
13
|
*/
|
14
14
|
function equals(a, b) {
|
15
15
|
// considering `undefined === undefined`
|
16
|
-
return (typeof a
|
16
|
+
return (((typeof a == 'string' && typeof b == 'string') || (typeof a == 'number' && typeof b == 'number')) && a === b)
|
17
17
|
}
|
18
18
|
|
19
19
|
/**
|
20
20
|
* Verify any parsed field with its corresponding predefined field
|
21
|
-
* @param {
|
22
|
-
* @return {
|
21
|
+
* @param {Function} callback Callback function with two parameters `parsed_fields` and `pre_defined_fields` respectively
|
22
|
+
* @return {Boolean} the return value from the callback function, should return `boolean`
|
23
23
|
*/
|
24
24
|
function verify(callback) {
|
25
|
-
return callback(
|
25
|
+
return callback(parsed_fields, pre_defined_fields)
|
26
26
|
}
|
27
27
|
|
28
28
|
/**
|
29
29
|
* Match or verify all parsed fields with their corresponding predefined fields
|
30
|
-
* `
|
30
|
+
* `parsed_fields` and `pre_defined_fields` must have the same name
|
31
31
|
* @param {[string]} doNotCompare List of field names to ignore from checking
|
32
|
-
* @return {
|
32
|
+
* @return {Boolean} `true` if all fields match otherwise `false`, if `parsed_fields` are empty returns `false`
|
33
33
|
*/
|
34
34
|
function verifyAll(doNotCompare = []) {
|
35
|
-
if (Object.keys(
|
35
|
+
if (Object.keys(parsed_fields).length <= 0) {
|
36
36
|
return false
|
37
37
|
}
|
38
38
|
|
39
|
-
for (const key in
|
40
|
-
if (Object.hasOwnProperty.call(
|
39
|
+
for (const key in parsed_fields) {
|
40
|
+
if (Object.hasOwnProperty.call(parsed_fields, key)) {
|
41
41
|
if (!doNotCompare.includes(key)) {
|
42
|
-
if (!equals(
|
42
|
+
if (!equals(parsed_fields[key], pre_defined_fields[key])) {
|
43
43
|
return false
|
44
44
|
}
|
45
45
|
}
|
@@ -51,17 +51,17 @@ function receipt(parsedFields = {}, preDefinedFields = {}) {
|
|
51
51
|
|
52
52
|
/**
|
53
53
|
* Match or verify specified list of fields with their corresponding predefined fields
|
54
|
-
* `
|
55
|
-
* @param {[string]}
|
56
|
-
* @return {
|
54
|
+
* `parsed_fields` and `pre_defined_fields` must have the same name
|
55
|
+
* @param {[string]} field_names List of field names to check
|
56
|
+
* @return {Boolean} `true` if all fields match otherwise `false`, if empty `field_names` passed returns `false`
|
57
57
|
*/
|
58
|
-
function verifyOnly(
|
59
|
-
if (
|
58
|
+
function verifyOnly(field_names = []) {
|
59
|
+
if (field_names.length <= 0) {
|
60
60
|
return false
|
61
61
|
}
|
62
62
|
|
63
|
-
|
64
|
-
if (!equals(
|
63
|
+
field_names.forEach(key => {
|
64
|
+
if (!equals(field_names[key], pre_defined_fields[key])) {
|
65
65
|
return false
|
66
66
|
}
|
67
67
|
})
|
@@ -75,7 +75,7 @@ function receipt(parsedFields = {}, preDefinedFields = {}) {
|
|
75
75
|
/**
|
76
76
|
* Parse fields from HTML string
|
77
77
|
* @param {string} str HTML string
|
78
|
-
* @return {
|
78
|
+
* @return {Object} `parsed_fields` as an object with field names `payer_name`, `payer_phone`, `payer_acc_type`, `credited_party_name`, `credited_party_acc_no`, `transaction_status`, `bank_acc_no`, `receipt_no`, `date`, `settled_amount`, `discount_amount`, `vat_amount`, `total_amount`, `amount_in_word`, `payment_mode`, `payment_reason`, `payment_channel`
|
79
79
|
*/
|
80
80
|
function parseFromHTML(str) {
|
81
81
|
const htmlparser = require('htmlparser2')
|
@@ -90,21 +90,21 @@ function parseFromHTML(str) {
|
|
90
90
|
return ''
|
91
91
|
}
|
92
92
|
|
93
|
-
const names = [
|
94
|
-
const keys = ['payer_name', 'payer_phone', 'payer_acc_type', 'credited_party_name',
|
93
|
+
const names = ["የከፋይስም/payername", "የከፋይቴሌብርቁ./payertelebirrno.", "የከፋይአካውንትአይነት/payeraccounttype", "የገንዘብተቀባይስም/creditedpartyname", "የገንዘብተቀባይቴሌብርቁ./creditedpartyaccountno", "የክፍያውሁኔታ/transactionstatus", "የባንክአካውንትቁጥር/bankaccountnumber", "የክፍያቁጥር/receiptno.", "የክፍያቀን/paymentdate", "የተከፈለውመጠን/settledamount", "ቅናሽ/discountamount", "15%ቫት/vat", "ጠቅላላየተክፈለ/totalamountpaid", "የገንዘቡልክበፊደል/totalamountinword", "የክፍያዘዴ/paymentmode", "የክፍያምክንያት/paymentreason", "የክፍያመንገድ/paymentchannel"]
|
94
|
+
const keys = ['payer_name', 'payer_phone', 'payer_acc_type', 'credited_party_name', "credited_party_acc_no", "transaction_status", "bank_acc_no", "receipt_no", "date", "settled_amount", "discount_amount", "vat_amount", "total_amount", "amount_in_word", "payment_mode", "payment_reason", "payment_channel"]
|
95
95
|
|
96
96
|
td.forEach((value, index) => {
|
97
97
|
let s = htmlparser.DomUtils.textContent(value).replace(/[\n\r\s\t]/ig, '').trim().toLowerCase();
|
98
98
|
|
99
99
|
const indexOf = names.indexOf(s)
|
100
100
|
if (indexOf >= 0) {
|
101
|
-
let nextValue = getNextValue(index + (indexOf > 6 && indexOf <= 9 ? 2 : 0));
|
101
|
+
let nextValue = getNextValue(index + ((indexOf > 6 && indexOf <= 9) ? 2 : 0));
|
102
102
|
let key = keys[indexOf];
|
103
103
|
|
104
|
-
if (key ===
|
104
|
+
if (key === "bank_acc_no") {
|
105
105
|
fields['to'] = nextValue.replace(/^[0-9]+/g, '').trim()
|
106
106
|
fields[key] = nextValue.replace(/[A-Z]/ig, '').trim()
|
107
|
-
} else if (key.endsWith(
|
107
|
+
} else if (key.endsWith("amount")) {
|
108
108
|
fields[key] = parseFloat(nextValue.replace(/birr/ig, '').trim())
|
109
109
|
} else {
|
110
110
|
fields[key] = nextValue
|
@@ -117,32 +117,32 @@ function parseFromHTML(str) {
|
|
117
117
|
|
118
118
|
/**
|
119
119
|
* Load receipt HTML string from receipt number or any url
|
120
|
-
* @param {string}
|
121
|
-
* @param {string}
|
120
|
+
* @param {string?} receipt_no Receipt number received from telebirr SMS, high priority
|
121
|
+
* @param {string?} full_url Or full receipt url, less prior
|
122
122
|
* @return {Promise<String>} HTML string
|
123
123
|
*/
|
124
|
-
function loadReceipt({
|
124
|
+
function loadReceipt({receipt_no, full_url}) {
|
125
125
|
return new Promise((resolve, reject) => {
|
126
|
-
const url =
|
126
|
+
const url = receipt_no ? `https://transactioninfo.ethiotelecom.et/receipt/${receipt_no}` : full_url
|
127
127
|
|
128
|
-
const https = require(
|
128
|
+
const https = require("https");
|
129
129
|
const options = new URL(url)
|
130
130
|
|
131
|
-
let data =
|
131
|
+
let data = ""
|
132
132
|
const req = https.request(options, (res) => {
|
133
|
-
res.on(
|
133
|
+
res.on("data", (chunk) => {
|
134
134
|
data += chunk
|
135
135
|
})
|
136
136
|
|
137
|
-
res.on(
|
137
|
+
res.on("error", (error) => {
|
138
138
|
reject(error)
|
139
139
|
})
|
140
140
|
|
141
|
-
res.on(
|
141
|
+
res.on("end", () => {
|
142
142
|
resolve(data)
|
143
143
|
})
|
144
144
|
})
|
145
|
-
req.on(
|
145
|
+
req.on("error", (error) => {
|
146
146
|
reject(error)
|
147
147
|
})
|
148
148
|
req.end()
|
package/package.json
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "telebirr-receipt",
|
3
3
|
"description": "Telebirr transaction receipt parser and verifier",
|
4
|
-
"version": "0.0
|
5
|
-
"author": "Yonathan Tesfaye <yt040448@gmail.com>",
|
4
|
+
"version": "1.0.0",
|
6
5
|
"main": "index.js",
|
7
6
|
"scripts": {
|
8
7
|
"start": "node index.js",
|
@@ -16,16 +15,19 @@
|
|
16
15
|
"parse",
|
17
16
|
"transaction"
|
18
17
|
],
|
19
|
-
"
|
18
|
+
"author": {
|
19
|
+
"name": "Yonathan Tesfaye",
|
20
|
+
"email": "yt040448@gmail.com"
|
21
|
+
},
|
22
|
+
"license": "Apache",
|
20
23
|
"repository": {
|
21
|
-
"
|
22
|
-
"url": "git://github.com/TheRealYT/telebirr-receipt.git"
|
24
|
+
"url": "https://github.com/TheRealYT/telebirr-receipt"
|
23
25
|
},
|
24
26
|
"files": [
|
25
27
|
"README.md",
|
26
|
-
"
|
28
|
+
"index.js",
|
27
29
|
"package.json",
|
28
|
-
"
|
30
|
+
"package-lock.json"
|
29
31
|
],
|
30
32
|
"dependencies": {
|
31
33
|
"htmlparser2": "^9.0.0"
|
package/LICENSE
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
Copyright 2023 Yonathan Tesfaye
|
2
|
-
Email: yt040448@gmail.com
|
3
|
-
|
4
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
-
you may not use this file except in compliance with the License.
|
6
|
-
You may obtain a copy of the License at
|
7
|
-
|
8
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
9
|
-
|
10
|
-
Unless required by applicable law or agreed to in writing, software
|
11
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
-
See the License for the specific language governing permissions and
|
14
|
-
limitations under the License.
|