telebirr-receipt 0.0.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
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.
|