@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 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 transaction IDs from Apple's ASN.1 encoded Unified Receipts.
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 only extracts some information from Apple's ASN.1 encoded Unified Receipts.
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.bind(this));
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 this.parseReceiptContent.bind(this);
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.bind(this));
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 this.parseReceiptContent.bind(this);
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.2.3",
4
- "description": "A lightweight TypeScript library for extracting transaction IDs from Apple's ASN.1 encoded receipts.",
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
  ],
@@ -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.bind(this))
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(verifiedSequence: ASN1.CompareSchemaSuccess): void {
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(fieldKey: number): (fieldValue: ASN1.OctetString) => void {
87
+ private getFieldHandler(
88
+ fieldKey: number,
89
+ inAppReceipt?: InAppReceipt,
90
+ ): (fieldValue: ASN1.OctetString) => void {
81
91
  if (fieldKey === IN_APP) {
82
- return this.parseReceiptContent.bind(this)
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'