@tamtamchik/app-store-receipt-parser 2.2.3 → 2.3.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.
- package/CHANGELOG.md +115 -0
- package/README.md +19 -2
- package/dist/index.js +18 -10
- package/dist/index.mjs +18 -10
- package/package.json +3 -2
- package/src/ReceiptParser.ts +27 -10
- package/src/index.ts +1 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 2.3.0 - 2026-05-27
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Added `IN_APP_RECEIPTS` to parsed receipts, exposing each supported in-app purchase receipt as a structured object.
|
|
8
|
+
- Exported the `InAppReceipt` type from the package entrypoint.
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Updated package metadata and documentation to describe selected receipt field parsing beyond transaction ID extraction.
|
|
13
|
+
- Updated the package version to `2.3.0`.
|
|
14
|
+
|
|
15
|
+
### Maintenance
|
|
16
|
+
|
|
17
|
+
- Updated dependency lockfile entries already merged into `main`.
|
|
18
|
+
- Updated transitive development dependency resolution for `brace-expansion` to clear npm audit findings.
|
|
19
|
+
- Pinned GitHub Actions workflow actions by commit SHA and constrained Dependabot update cadence.
|
|
20
|
+
|
|
21
|
+
## 2.2.3 - 2026-04-15
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- Added a security policy.
|
|
26
|
+
- Added GitHub Actions CI for linting, building, and testing.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- Migrated tests from Jest to the Node.js test runner.
|
|
31
|
+
- Split linting into `lint` and `lint:fix` scripts.
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- Upgraded `asn1js` from `3.0.6` to `3.0.7`.
|
|
36
|
+
|
|
37
|
+
### Maintenance
|
|
38
|
+
|
|
39
|
+
- Updated development dependencies and GitHub Actions versions.
|
|
40
|
+
- Consolidated ESLint dependencies.
|
|
41
|
+
|
|
42
|
+
## 2.2.2 - 2025-05-02
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- Upgraded `asn1js` from `3.0.5` to `3.0.6`.
|
|
47
|
+
|
|
48
|
+
### Maintenance
|
|
49
|
+
|
|
50
|
+
- Updated dependencies.
|
|
51
|
+
|
|
52
|
+
## 2.2.1 - 2024-09-25
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
|
|
56
|
+
- Fixed exported TypeScript types.
|
|
57
|
+
|
|
58
|
+
## 2.2.0 - 2024-09-25
|
|
59
|
+
|
|
60
|
+
### Changed
|
|
61
|
+
|
|
62
|
+
- Refactored parser and verifier internals into classes.
|
|
63
|
+
- Updated TypeScript and ESLint configuration.
|
|
64
|
+
|
|
65
|
+
### Maintenance
|
|
66
|
+
|
|
67
|
+
- Updated dependencies.
|
|
68
|
+
|
|
69
|
+
## 2.1.2 - 2024-05-20
|
|
70
|
+
|
|
71
|
+
### Maintenance
|
|
72
|
+
|
|
73
|
+
- Updated the Scrutinizer Node.js version.
|
|
74
|
+
|
|
75
|
+
## 2.1.1 - 2024-05-06
|
|
76
|
+
|
|
77
|
+
### Maintenance
|
|
78
|
+
|
|
79
|
+
- Updated dependencies.
|
|
80
|
+
|
|
81
|
+
## 2.1.0 - 2024-04-11
|
|
82
|
+
|
|
83
|
+
### Added
|
|
84
|
+
|
|
85
|
+
- Added mapping for the `ENVIRONMENT` receipt field.
|
|
86
|
+
- Added `Production` as the default parsed environment.
|
|
87
|
+
|
|
88
|
+
### Fixed
|
|
89
|
+
|
|
90
|
+
- Fixed parsed receipt completeness checks for the `ENVIRONMENT` field.
|
|
91
|
+
|
|
92
|
+
### Maintenance
|
|
93
|
+
|
|
94
|
+
- Updated dependencies and documentation.
|
|
95
|
+
|
|
96
|
+
## 2.0.0 - 2024-01-10
|
|
97
|
+
|
|
98
|
+
### Changed
|
|
99
|
+
|
|
100
|
+
- Refactored the parser implementation for the `2.x` release line.
|
|
101
|
+
- Updated documentation and project structure.
|
|
102
|
+
|
|
103
|
+
## 1.0.1 - 2023-11-14
|
|
104
|
+
|
|
105
|
+
### Maintenance
|
|
106
|
+
|
|
107
|
+
- Simplified tests.
|
|
108
|
+
- Moved CI from CircleCI to Scrutinizer.
|
|
109
|
+
|
|
110
|
+
## 1.0.0 - 2023-08-19
|
|
111
|
+
|
|
112
|
+
### Added
|
|
113
|
+
|
|
114
|
+
- Added the initial receipt parsing functionality.
|
|
115
|
+
- Added tests, publishing configuration, and README documentation.
|
package/README.md
CHANGED
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
[![Software License][ico-license]](./LICENSE)
|
|
8
8
|
[![Total Downloads][ico-downloads]][link-downloads]
|
|
9
9
|
|
|
10
|
-
A lightweight TypeScript library for extracting
|
|
10
|
+
A lightweight TypeScript library for extracting selected fields from Apple's ASN.1 encoded Unified Receipts.
|
|
11
11
|
|
|
12
12
|
> [!IMPORTANT]
|
|
13
13
|
> This library is not a full-fledged receipt parser.
|
|
14
|
-
> It
|
|
14
|
+
> It extracts supported fields from Apple's ASN.1 encoded Unified Receipts, including in-app purchase receipts.
|
|
15
15
|
> It does not work with the old-style transaction receipts.
|
|
16
16
|
|
|
17
17
|
> [!NOTE]
|
|
@@ -33,6 +33,9 @@ yarn add @tamtamchik/app-store-receipt-parser
|
|
|
33
33
|
|
|
34
34
|
## Usage
|
|
35
35
|
|
|
36
|
+
The result includes selected top-level receipt fields, aggregate in-app transaction IDs,
|
|
37
|
+
and structured in-app receipt data in `IN_APP_RECEIPTS`.
|
|
38
|
+
|
|
36
39
|
```typescript
|
|
37
40
|
import { parseReceipt } from '@tamtamchik/app-store-receipt-parser';
|
|
38
41
|
|
|
@@ -65,6 +68,20 @@ console.log(data);
|
|
|
65
68
|
// '1000000166967484',
|
|
66
69
|
// '1000000166967782'
|
|
67
70
|
// ],
|
|
71
|
+
// IN_APP_RECEIPTS: [
|
|
72
|
+
// // ...
|
|
73
|
+
// {
|
|
74
|
+
// IN_APP_EXPIRES_DATE: '2015-08-10T07:19:32Z',
|
|
75
|
+
// IN_APP_CANCELLATION_DATE: '',
|
|
76
|
+
// IN_APP_QUANTITY: '020101',
|
|
77
|
+
// IN_APP_WEB_ORDER_LINE_ITEM_ID: '0207038d7ea69472c9',
|
|
78
|
+
// IN_APP_PRODUCT_ID: 'monthly',
|
|
79
|
+
// IN_APP_TRANSACTION_ID: '1000000166967782',
|
|
80
|
+
// IN_APP_ORIGINAL_TRANSACTION_ID: '1000000166965150',
|
|
81
|
+
// IN_APP_PURCHASE_DATE: '2015-08-10T07:14:32Z',
|
|
82
|
+
// IN_APP_ORIGINAL_PURCHASE_DATE: '2015-08-10T07:12:34Z'
|
|
83
|
+
// }
|
|
84
|
+
// ],
|
|
68
85
|
// IN_APP_ORIGINAL_TRANSACTION_ID: '1000000166965150',
|
|
69
86
|
// IN_APP_ORIGINAL_TRANSACTION_IDS: [
|
|
70
87
|
// '1000000166865231',
|
package/dist/index.js
CHANGED
|
@@ -145,38 +145,43 @@ var ReceiptParser = class {
|
|
|
145
145
|
createInitialParsedReceipt() {
|
|
146
146
|
return {
|
|
147
147
|
ENVIRONMENT: "Production",
|
|
148
|
+
IN_APP_RECEIPTS: [],
|
|
148
149
|
IN_APP_ORIGINAL_TRANSACTION_IDS: [],
|
|
149
150
|
IN_APP_TRANSACTION_IDS: []
|
|
150
151
|
};
|
|
151
152
|
}
|
|
152
|
-
parseReceiptContent(content) {
|
|
153
|
+
parseReceiptContent(content, inAppReceipt) {
|
|
153
154
|
const sequences = this.extractSequencesFromContent(content);
|
|
154
|
-
sequences.forEach(this.processSequence
|
|
155
|
+
sequences.forEach((sequence) => this.processSequence(sequence, inAppReceipt));
|
|
155
156
|
}
|
|
156
157
|
extractSequencesFromContent(content) {
|
|
157
158
|
const [contentSet] = content.valueBlock.value;
|
|
158
159
|
return contentSet.valueBlock.value.filter((v) => v instanceof ASN1.Sequence);
|
|
159
160
|
}
|
|
160
|
-
processSequence(sequence) {
|
|
161
|
+
processSequence(sequence, inAppReceipt) {
|
|
161
162
|
const verifiedSequence = this.receiptVerifier.verifyFieldSchema(sequence);
|
|
162
163
|
if (verifiedSequence) {
|
|
163
|
-
this.handleVerifiedSequence(verifiedSequence);
|
|
164
|
+
this.handleVerifiedSequence(verifiedSequence, inAppReceipt);
|
|
164
165
|
}
|
|
165
166
|
}
|
|
166
|
-
handleVerifiedSequence(verifiedSequence) {
|
|
167
|
+
handleVerifiedSequence(verifiedSequence, inAppReceipt) {
|
|
167
168
|
const fieldKey = verifiedSequence.result[FIELD_TYPE_ID].valueBlock.valueDec;
|
|
168
169
|
const fieldValue = verifiedSequence.result[FIELD_VALUE_ID];
|
|
169
|
-
const handler = this.getFieldHandler(fieldKey);
|
|
170
|
+
const handler = this.getFieldHandler(fieldKey, inAppReceipt);
|
|
170
171
|
handler(fieldValue);
|
|
171
172
|
}
|
|
172
|
-
getFieldHandler(fieldKey) {
|
|
173
|
+
getFieldHandler(fieldKey, inAppReceipt) {
|
|
173
174
|
if (fieldKey === IN_APP) {
|
|
174
|
-
return
|
|
175
|
+
return (fieldValue) => {
|
|
176
|
+
const parsedInAppReceipt = {};
|
|
177
|
+
this.parseReceiptContent(fieldValue, parsedInAppReceipt);
|
|
178
|
+
this.parsed.IN_APP_RECEIPTS.push(parsedInAppReceipt);
|
|
179
|
+
};
|
|
175
180
|
}
|
|
176
181
|
if (this.isValidReceiptFieldKey(fieldKey)) {
|
|
177
182
|
const name = RECEIPT_FIELDS_MAP.get(fieldKey);
|
|
178
183
|
return (fieldValue) => {
|
|
179
|
-
this.addFieldToReceipt(name, this.extractStringValue(fieldValue));
|
|
184
|
+
this.addFieldToReceipt(name, this.extractStringValue(fieldValue), inAppReceipt);
|
|
180
185
|
};
|
|
181
186
|
}
|
|
182
187
|
return () => {
|
|
@@ -192,9 +197,12 @@ var ReceiptParser = class {
|
|
|
192
197
|
}
|
|
193
198
|
return field.toJSON().valueBlock.valueHex;
|
|
194
199
|
}
|
|
195
|
-
addFieldToReceipt(name, value) {
|
|
200
|
+
addFieldToReceipt(name, value, inAppReceipt) {
|
|
196
201
|
this.addToArrayFieldIfApplicable(name, value);
|
|
197
202
|
this.parsed[name] = value;
|
|
203
|
+
if (inAppReceipt) {
|
|
204
|
+
inAppReceipt[name] = value;
|
|
205
|
+
}
|
|
198
206
|
}
|
|
199
207
|
addToArrayFieldIfApplicable(name, value) {
|
|
200
208
|
const arrayFields = {
|
package/dist/index.mjs
CHANGED
|
@@ -109,38 +109,43 @@ var ReceiptParser = class {
|
|
|
109
109
|
createInitialParsedReceipt() {
|
|
110
110
|
return {
|
|
111
111
|
ENVIRONMENT: "Production",
|
|
112
|
+
IN_APP_RECEIPTS: [],
|
|
112
113
|
IN_APP_ORIGINAL_TRANSACTION_IDS: [],
|
|
113
114
|
IN_APP_TRANSACTION_IDS: []
|
|
114
115
|
};
|
|
115
116
|
}
|
|
116
|
-
parseReceiptContent(content) {
|
|
117
|
+
parseReceiptContent(content, inAppReceipt) {
|
|
117
118
|
const sequences = this.extractSequencesFromContent(content);
|
|
118
|
-
sequences.forEach(this.processSequence
|
|
119
|
+
sequences.forEach((sequence) => this.processSequence(sequence, inAppReceipt));
|
|
119
120
|
}
|
|
120
121
|
extractSequencesFromContent(content) {
|
|
121
122
|
const [contentSet] = content.valueBlock.value;
|
|
122
123
|
return contentSet.valueBlock.value.filter((v) => v instanceof ASN1.Sequence);
|
|
123
124
|
}
|
|
124
|
-
processSequence(sequence) {
|
|
125
|
+
processSequence(sequence, inAppReceipt) {
|
|
125
126
|
const verifiedSequence = this.receiptVerifier.verifyFieldSchema(sequence);
|
|
126
127
|
if (verifiedSequence) {
|
|
127
|
-
this.handleVerifiedSequence(verifiedSequence);
|
|
128
|
+
this.handleVerifiedSequence(verifiedSequence, inAppReceipt);
|
|
128
129
|
}
|
|
129
130
|
}
|
|
130
|
-
handleVerifiedSequence(verifiedSequence) {
|
|
131
|
+
handleVerifiedSequence(verifiedSequence, inAppReceipt) {
|
|
131
132
|
const fieldKey = verifiedSequence.result[FIELD_TYPE_ID].valueBlock.valueDec;
|
|
132
133
|
const fieldValue = verifiedSequence.result[FIELD_VALUE_ID];
|
|
133
|
-
const handler = this.getFieldHandler(fieldKey);
|
|
134
|
+
const handler = this.getFieldHandler(fieldKey, inAppReceipt);
|
|
134
135
|
handler(fieldValue);
|
|
135
136
|
}
|
|
136
|
-
getFieldHandler(fieldKey) {
|
|
137
|
+
getFieldHandler(fieldKey, inAppReceipt) {
|
|
137
138
|
if (fieldKey === IN_APP) {
|
|
138
|
-
return
|
|
139
|
+
return (fieldValue) => {
|
|
140
|
+
const parsedInAppReceipt = {};
|
|
141
|
+
this.parseReceiptContent(fieldValue, parsedInAppReceipt);
|
|
142
|
+
this.parsed.IN_APP_RECEIPTS.push(parsedInAppReceipt);
|
|
143
|
+
};
|
|
139
144
|
}
|
|
140
145
|
if (this.isValidReceiptFieldKey(fieldKey)) {
|
|
141
146
|
const name = RECEIPT_FIELDS_MAP.get(fieldKey);
|
|
142
147
|
return (fieldValue) => {
|
|
143
|
-
this.addFieldToReceipt(name, this.extractStringValue(fieldValue));
|
|
148
|
+
this.addFieldToReceipt(name, this.extractStringValue(fieldValue), inAppReceipt);
|
|
144
149
|
};
|
|
145
150
|
}
|
|
146
151
|
return () => {
|
|
@@ -156,9 +161,12 @@ var ReceiptParser = class {
|
|
|
156
161
|
}
|
|
157
162
|
return field.toJSON().valueBlock.valueHex;
|
|
158
163
|
}
|
|
159
|
-
addFieldToReceipt(name, value) {
|
|
164
|
+
addFieldToReceipt(name, value, inAppReceipt) {
|
|
160
165
|
this.addToArrayFieldIfApplicable(name, value);
|
|
161
166
|
this.parsed[name] = value;
|
|
167
|
+
if (inAppReceipt) {
|
|
168
|
+
inAppReceipt[name] = value;
|
|
169
|
+
}
|
|
162
170
|
}
|
|
163
171
|
addToArrayFieldIfApplicable(name, value) {
|
|
164
172
|
const arrayFields = {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tamtamchik/app-store-receipt-parser",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "A lightweight TypeScript library for extracting
|
|
3
|
+
"version": "2.3.0",
|
|
4
|
+
"description": "A lightweight TypeScript library for extracting selected fields from Apple's ASN.1 encoded receipts.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
7
7
|
"types": "./src/index.ts",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"receipt",
|
|
21
21
|
"parser",
|
|
22
22
|
"ASN.1",
|
|
23
|
+
"in-app",
|
|
23
24
|
"transaction",
|
|
24
25
|
"typescript"
|
|
25
26
|
],
|
package/src/ReceiptParser.ts
CHANGED
|
@@ -13,8 +13,11 @@ import { ReceiptVerifier } from './ReceiptVerifier'
|
|
|
13
13
|
|
|
14
14
|
export type Environment = 'Production' | 'ProductionSandbox' | string
|
|
15
15
|
|
|
16
|
+
export type InAppReceipt = Partial<Record<ReceiptFieldsKeyNames, string>>
|
|
17
|
+
|
|
16
18
|
export type ParsedReceipt = Partial<Record<ReceiptFieldsKeyNames, string>> & {
|
|
17
19
|
ENVIRONMENT: Environment
|
|
20
|
+
IN_APP_RECEIPTS: InAppReceipt[]
|
|
18
21
|
IN_APP_ORIGINAL_TRANSACTION_IDS: string[]
|
|
19
22
|
IN_APP_TRANSACTION_IDS: string[]
|
|
20
23
|
}
|
|
@@ -46,14 +49,15 @@ class ReceiptParser {
|
|
|
46
49
|
private createInitialParsedReceipt(): ParsedReceipt {
|
|
47
50
|
return {
|
|
48
51
|
ENVIRONMENT: 'Production',
|
|
52
|
+
IN_APP_RECEIPTS: [],
|
|
49
53
|
IN_APP_ORIGINAL_TRANSACTION_IDS: [],
|
|
50
54
|
IN_APP_TRANSACTION_IDS: [],
|
|
51
55
|
}
|
|
52
56
|
}
|
|
53
57
|
|
|
54
|
-
private parseReceiptContent(content: ASN1.OctetString): void {
|
|
58
|
+
private parseReceiptContent(content: ASN1.OctetString, inAppReceipt?: InAppReceipt): void {
|
|
55
59
|
const sequences = this.extractSequencesFromContent(content)
|
|
56
|
-
sequences.forEach(this.processSequence
|
|
60
|
+
sequences.forEach(sequence => this.processSequence(sequence, inAppReceipt))
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
private extractSequencesFromContent(content: ASN1.OctetString): ASN1.Sequence[] {
|
|
@@ -62,29 +66,39 @@ class ReceiptParser {
|
|
|
62
66
|
.filter(v => v instanceof ASN1.Sequence) as ASN1.Sequence[]
|
|
63
67
|
}
|
|
64
68
|
|
|
65
|
-
private processSequence(sequence: ASN1.Sequence): void {
|
|
69
|
+
private processSequence(sequence: ASN1.Sequence, inAppReceipt?: InAppReceipt): void {
|
|
66
70
|
const verifiedSequence = this.receiptVerifier.verifyFieldSchema(sequence)
|
|
67
71
|
if (verifiedSequence) {
|
|
68
|
-
this.handleVerifiedSequence(verifiedSequence)
|
|
72
|
+
this.handleVerifiedSequence(verifiedSequence, inAppReceipt)
|
|
69
73
|
}
|
|
70
74
|
}
|
|
71
75
|
|
|
72
|
-
private handleVerifiedSequence(
|
|
76
|
+
private handleVerifiedSequence(
|
|
77
|
+
verifiedSequence: ASN1.CompareSchemaSuccess,
|
|
78
|
+
inAppReceipt?: InAppReceipt,
|
|
79
|
+
): void {
|
|
73
80
|
const fieldKey = (verifiedSequence.result[FIELD_TYPE_ID] as ASN1.Integer).valueBlock.valueDec
|
|
74
81
|
const fieldValue = verifiedSequence.result[FIELD_VALUE_ID] as ASN1.OctetString
|
|
75
82
|
|
|
76
|
-
const handler = this.getFieldHandler(fieldKey)
|
|
83
|
+
const handler = this.getFieldHandler(fieldKey, inAppReceipt)
|
|
77
84
|
handler(fieldValue)
|
|
78
85
|
}
|
|
79
86
|
|
|
80
|
-
private getFieldHandler(
|
|
87
|
+
private getFieldHandler(
|
|
88
|
+
fieldKey: number,
|
|
89
|
+
inAppReceipt?: InAppReceipt,
|
|
90
|
+
): (fieldValue: ASN1.OctetString) => void {
|
|
81
91
|
if (fieldKey === IN_APP) {
|
|
82
|
-
return
|
|
92
|
+
return (fieldValue: ASN1.OctetString) => {
|
|
93
|
+
const parsedInAppReceipt: InAppReceipt = {}
|
|
94
|
+
this.parseReceiptContent(fieldValue, parsedInAppReceipt)
|
|
95
|
+
this.parsed.IN_APP_RECEIPTS.push(parsedInAppReceipt)
|
|
96
|
+
}
|
|
83
97
|
}
|
|
84
98
|
if (this.isValidReceiptFieldKey(fieldKey)) {
|
|
85
99
|
const name = RECEIPT_FIELDS_MAP.get(fieldKey)!
|
|
86
100
|
return (fieldValue: ASN1.OctetString) => {
|
|
87
|
-
this.addFieldToReceipt(name, this.extractStringValue(fieldValue))
|
|
101
|
+
this.addFieldToReceipt(name, this.extractStringValue(fieldValue), inAppReceipt)
|
|
88
102
|
}
|
|
89
103
|
}
|
|
90
104
|
return () => {}
|
|
@@ -104,9 +118,12 @@ class ReceiptParser {
|
|
|
104
118
|
return field.toJSON().valueBlock.valueHex
|
|
105
119
|
}
|
|
106
120
|
|
|
107
|
-
private addFieldToReceipt(name: ReceiptFieldsKeyNames, value: string): void {
|
|
121
|
+
private addFieldToReceipt(name: ReceiptFieldsKeyNames, value: string, inAppReceipt?: InAppReceipt): void {
|
|
108
122
|
this.addToArrayFieldIfApplicable(name, value)
|
|
109
123
|
this.parsed[name] = value
|
|
124
|
+
if (inAppReceipt) {
|
|
125
|
+
inAppReceipt[name] = value
|
|
126
|
+
}
|
|
110
127
|
}
|
|
111
128
|
|
|
112
129
|
private addToArrayFieldIfApplicable(name: ReceiptFieldsKeyNames, value: string): void {
|
package/src/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type { ParsedReceipt } from './ReceiptParser'
|
|
1
|
+
export type { InAppReceipt, ParsedReceipt } from './ReceiptParser'
|
|
2
2
|
export { parseReceipt } from './ReceiptParser'
|