@tamtamchik/app-store-receipt-parser 2.0.0 → 2.1.1

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/README.md CHANGED
@@ -1,6 +1,5 @@
1
1
  # Apple Receipt Parser
2
2
 
3
- [![Buy Me A Coffee][ico-coffee]][link-coffee]
4
3
  [![Latest Version on NPM][ico-version]][link-npm]
5
4
  [![Scrutinizer build][ico-scrutinizer-build]][link-scrutinizer]
6
5
  [![Scrutinizer quality][ico-scrutinizer-quality]][link-scrutinizer]
@@ -10,11 +9,13 @@
10
9
 
11
10
  A lightweight TypeScript library for extracting transaction IDs from Apple's ASN.1 encoded Unified Receipts.
12
11
 
13
- > **Warning!** This library is not a full-fledged receipt parser.
14
- > It only extracts some information from the Apple's ASN.1 encoded Unified Receipts.
15
- > It does not work with the old style transactions receipts.
12
+ > [!IMPORTANT]
13
+ > This library is not a full-fledged receipt parser.
14
+ > It only extracts some information from Apple's ASN.1 encoded Unified Receipts.
15
+ > It does not work with the old-style transaction receipts.
16
16
 
17
- > **Documentation for the version 1.x of the library can be found [here](https://github.com/tamtamchik/app-store-receipt-parser/tree/1.x/README.md).**
17
+ > [!NOTE]
18
+ > Documentation for the version 1.x of the library can be found [here](https://github.com/tamtamchik/app-store-receipt-parser/tree/1.x/README.md).
18
19
 
19
20
  ## Installation
20
21
 
@@ -41,6 +42,7 @@ const data = parseReceipt(receiptString);
41
42
 
42
43
  console.log(data);
43
44
  // {
45
+ // ENVIRONMENT: 'ProductionSandbox',
44
46
  // APP_VERSION: '1',
45
47
  // ORIGINAL_APP_VERSION: '1.0',
46
48
  // OPAQUE_VALUE: 'c4dd4054b0b61a07beb585f6a842e048',
@@ -76,10 +78,11 @@ console.log(data);
76
78
  ## Special Thanks
77
79
 
78
80
  - [@Jurajzovinec](https://github.com/Jurajzovinec) for his superb contribution to the project.
81
+ - [@fechy](https://github.com/fechy) for bringing environment variable support to the lib.
79
82
 
80
83
  ## Contributing
81
84
 
82
- Pull requests are always welcome. If you have bigger changes in mind, please open an issue first to discuss your ideas.
85
+ Pull requests are always welcome. If you have bigger changes, please open an issue first to discuss your ideas.
83
86
 
84
87
  ## License
85
88
 
@@ -87,7 +90,10 @@ Apple Receipt Parser is [MIT licensed](./LICENSE).
87
90
 
88
91
  ## Third-Party Licenses
89
92
 
90
- This project uses `ASN1.js`, which is licensed under the BSD-3-Clause License. The license text can be found in [LICENSE](./LICENSE).
93
+ This project uses `ASN1.js`, licensed under the BSD-3-Clause License. The license text can be found in [LICENSE](./LICENSE).
94
+
95
+ ---
96
+ [![Buy Me A Coffee][ico-coffee]][link-coffee]
91
97
 
92
98
  [ico-coffee]: https://img.shields.io/badge/Buy%20Me%20A-Coffee-%236F4E37.svg?style=flat-square
93
99
  [ico-version]: https://img.shields.io/npm/v/@tamtamchik/app-store-receipt-parser.svg?style=flat-square
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -25,10 +35,11 @@ __export(src_exports, {
25
35
  module.exports = __toCommonJS(src_exports);
26
36
 
27
37
  // src/parser.ts
28
- var import_asn1js2 = require("asn1js");
38
+ var ASN1 = __toESM(require("asn1js"));
29
39
 
30
40
  // src/mappings.ts
31
41
  var RECEIPT_FIELDS_MAP = /* @__PURE__ */ new Map([
42
+ [0, "ENVIRONMENT"],
32
43
  [2, "BUNDLE_ID"],
33
44
  [3, "APP_VERSION"],
34
45
  [4, "OPAQUE_VALUE"],
@@ -108,12 +119,10 @@ function verifyFieldSchema(sequence) {
108
119
  return fieldVerification;
109
120
  }
110
121
 
111
- // src/utils.ts
112
- var uniqueArrayValues = (array) => Array.from(new Set(array));
113
-
114
122
  // src/parser.ts
123
+ var uniqueArrayValues = (array) => Array.from(new Set(array));
115
124
  function isReceiptFieldKey(value) {
116
- return Boolean(value && typeof value === "number" && RECEIPT_FIELDS_MAP.has(value));
125
+ return Boolean(typeof value === "number" && RECEIPT_FIELDS_MAP.has(value));
117
126
  }
118
127
  function isParsedReceiptContentComplete(data) {
119
128
  for (const fieldKey of RECEIPT_FIELDS_MAP.values()) {
@@ -125,7 +134,7 @@ function isParsedReceiptContentComplete(data) {
125
134
  }
126
135
  function extractFieldValue(field) {
127
136
  const [fieldValue] = field.valueBlock.value;
128
- if (fieldValue instanceof import_asn1js2.IA5String || fieldValue instanceof import_asn1js2.Utf8String) {
137
+ if (fieldValue instanceof ASN1.IA5String || fieldValue instanceof ASN1.Utf8String) {
129
138
  return fieldValue.valueBlock.value;
130
139
  }
131
140
  return field.toJSON().valueBlock.valueHex;
@@ -152,7 +161,7 @@ function processField(parsed, fieldKey, fieldValue) {
152
161
  }
153
162
  function parseOctetStringContent(parsed, content) {
154
163
  const [contentSet] = content.valueBlock.value;
155
- const contentSetSequences = contentSet.valueBlock.value.filter((v) => v instanceof import_asn1js2.Sequence);
164
+ const contentSetSequences = contentSet.valueBlock.value.filter((v) => v instanceof ASN1.Sequence);
156
165
  for (const sequence of contentSetSequences) {
157
166
  const verifiedSequence = verifyFieldSchema(sequence);
158
167
  if (verifiedSequence) {
@@ -170,6 +179,7 @@ function parseReceipt(receipt) {
170
179
  const rootSchemaVerification = verifyReceiptSchema(receipt);
171
180
  const content = rootSchemaVerification.result[CONTENT_ID];
172
181
  const parsed = {
182
+ ENVIRONMENT: "Production",
173
183
  IN_APP_ORIGINAL_TRANSACTION_IDS: [],
174
184
  IN_APP_TRANSACTION_IDS: []
175
185
  };
package/dist/index.mjs CHANGED
@@ -1,8 +1,9 @@
1
1
  // src/parser.ts
2
- import { IA5String, Sequence as Sequence2, Utf8String } from "asn1js";
2
+ import * as ASN1 from "asn1js";
3
3
 
4
4
  // src/mappings.ts
5
5
  var RECEIPT_FIELDS_MAP = /* @__PURE__ */ new Map([
6
+ [0, "ENVIRONMENT"],
6
7
  [2, "BUNDLE_ID"],
7
8
  [3, "APP_VERSION"],
8
9
  [4, "OPAQUE_VALUE"],
@@ -82,12 +83,10 @@ function verifyFieldSchema(sequence) {
82
83
  return fieldVerification;
83
84
  }
84
85
 
85
- // src/utils.ts
86
- var uniqueArrayValues = (array) => Array.from(new Set(array));
87
-
88
86
  // src/parser.ts
87
+ var uniqueArrayValues = (array) => Array.from(new Set(array));
89
88
  function isReceiptFieldKey(value) {
90
- return Boolean(value && typeof value === "number" && RECEIPT_FIELDS_MAP.has(value));
89
+ return Boolean(typeof value === "number" && RECEIPT_FIELDS_MAP.has(value));
91
90
  }
92
91
  function isParsedReceiptContentComplete(data) {
93
92
  for (const fieldKey of RECEIPT_FIELDS_MAP.values()) {
@@ -99,7 +98,7 @@ function isParsedReceiptContentComplete(data) {
99
98
  }
100
99
  function extractFieldValue(field) {
101
100
  const [fieldValue] = field.valueBlock.value;
102
- if (fieldValue instanceof IA5String || fieldValue instanceof Utf8String) {
101
+ if (fieldValue instanceof ASN1.IA5String || fieldValue instanceof ASN1.Utf8String) {
103
102
  return fieldValue.valueBlock.value;
104
103
  }
105
104
  return field.toJSON().valueBlock.valueHex;
@@ -126,7 +125,7 @@ function processField(parsed, fieldKey, fieldValue) {
126
125
  }
127
126
  function parseOctetStringContent(parsed, content) {
128
127
  const [contentSet] = content.valueBlock.value;
129
- const contentSetSequences = contentSet.valueBlock.value.filter((v) => v instanceof Sequence2);
128
+ const contentSetSequences = contentSet.valueBlock.value.filter((v) => v instanceof ASN1.Sequence);
130
129
  for (const sequence of contentSetSequences) {
131
130
  const verifiedSequence = verifyFieldSchema(sequence);
132
131
  if (verifiedSequence) {
@@ -144,6 +143,7 @@ function parseReceipt(receipt) {
144
143
  const rootSchemaVerification = verifyReceiptSchema(receipt);
145
144
  const content = rootSchemaVerification.result[CONTENT_ID];
146
145
  const parsed = {
146
+ ENVIRONMENT: "Production",
147
147
  IN_APP_ORIGINAL_TRANSACTION_IDS: [],
148
148
  IN_APP_TRANSACTION_IDS: []
149
149
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tamtamchik/app-store-receipt-parser",
3
- "version": "2.0.0",
3
+ "version": "2.1.1",
4
4
  "description": "A lightweight TypeScript library for extracting transaction IDs from Apple's ASN.1 encoded receipts.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -34,15 +34,15 @@
34
34
  "asn1js": "3.0.5"
35
35
  },
36
36
  "devDependencies": {
37
- "@types/jest": "29.5.11",
38
- "@types/node": "20.10.6",
39
- "@typescript-eslint/eslint-plugin": "6.17.0",
40
- "@typescript-eslint/parser": "6.17.0",
41
- "eslint": "8.56.0",
37
+ "@types/jest": "29.5.12",
38
+ "@types/node": "^20.12.8",
39
+ "@typescript-eslint/eslint-plugin": "^7.8.0",
40
+ "@typescript-eslint/parser": "^7.8.0",
41
+ "eslint": "8.57.0",
42
42
  "jest": "29.7.0",
43
- "ts-jest": "29.1.1",
44
- "tsup": "8.0.1",
45
- "typescript": "5.3.3"
43
+ "ts-jest": "29.1.2",
44
+ "tsup": "8.0.2",
45
+ "typescript": "5.4.5"
46
46
  },
47
47
  "scripts": {
48
48
  "build": "tsup src/index.ts --format cjs,esm --clean",
package/src/mappings.ts CHANGED
@@ -1,22 +1,24 @@
1
1
  export type ReceiptFieldsKeyValues =
2
- | 2
3
- | 3
4
- | 4
5
- | 5
6
- | 12
7
- | 18
8
- | 19
9
- | 1701
10
- | 1702
11
- | 1703
12
- | 1704
13
- | 1705
14
- | 1706
15
- | 1708
16
- | 1711
17
- | 1712
2
+ | 0 // Environment
3
+ | 2 // Bundle ID
4
+ | 3 // App version
5
+ | 4 // Opaque value
6
+ | 5 // SHA-1 hash
7
+ | 12 // Receipt creation date
8
+ | 18 // Original purchase date
9
+ | 19 // Original app version
10
+ | 1701 // In-app quantity
11
+ | 1702 // In-app product ID
12
+ | 1703 // In-app transaction ID
13
+ | 1704 // In-app purchase date
14
+ | 1705 // In-app original transaction ID
15
+ | 1706 // In-app original purchase date
16
+ | 1708 // In-app expires date
17
+ | 1711 // In-app web order line item ID
18
+ | 1712 // In-app cancellation date
18
19
 
19
20
  export type ReceiptFieldsKeyNames =
21
+ | 'ENVIRONMENT'
20
22
  | 'BUNDLE_ID'
21
23
  | 'APP_VERSION'
22
24
  | 'OPAQUE_VALUE'
@@ -39,6 +41,7 @@ export type ReceiptFieldsKeyNames =
39
41
  * @see https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html
40
42
  */
41
43
  export const RECEIPT_FIELDS_MAP: ReadonlyMap<ReceiptFieldsKeyValues, ReceiptFieldsKeyNames> = new Map([
44
+ [0, 'ENVIRONMENT'],
42
45
  [2, 'BUNDLE_ID'],
43
46
  [3, 'APP_VERSION'],
44
47
  [4, 'OPAQUE_VALUE'],
@@ -55,5 +58,4 @@ export const RECEIPT_FIELDS_MAP: ReadonlyMap<ReceiptFieldsKeyValues, ReceiptFiel
55
58
  [1708, 'IN_APP_EXPIRES_DATE'],
56
59
  [1711, 'IN_APP_WEB_ORDER_LINE_ITEM_ID'],
57
60
  [1712, 'IN_APP_CANCELLATION_DATE'],
58
- ])
59
-
61
+ ])
package/src/parser.ts CHANGED
@@ -1,17 +1,21 @@
1
- import { IA5String, Integer, OctetString, Sequence, Set, Utf8String } from 'asn1js'
1
+ import * as ASN1 from 'asn1js'
2
2
 
3
3
  import { RECEIPT_FIELDS_MAP, ReceiptFieldsKeyNames, ReceiptFieldsKeyValues } from './mappings'
4
4
  import { CONTENT_ID, FIELD_TYPE_ID, FIELD_VALUE_ID, IN_APP } from './constants'
5
5
  import { verifyFieldSchema, verifyReceiptSchema } from './verifications'
6
- import { uniqueArrayValues } from './utils'
6
+
7
+ export type Environment = 'Production' | 'ProductionSandbox' | string
8
+
9
+ const uniqueArrayValues = (array: string[]) => Array.from(new Set(array))
7
10
 
8
11
  export type ParsedReceipt = Partial<Record<ReceiptFieldsKeyNames, string>> & {
12
+ ENVIRONMENT: Environment
9
13
  IN_APP_ORIGINAL_TRANSACTION_IDS: string[]
10
14
  IN_APP_TRANSACTION_IDS: string[]
11
15
  }
12
16
 
13
17
  function isReceiptFieldKey (value: unknown): value is ReceiptFieldsKeyValues {
14
- return Boolean(value && typeof value === 'number' && RECEIPT_FIELDS_MAP.has(value as ReceiptFieldsKeyValues))
18
+ return Boolean(typeof value === 'number' && RECEIPT_FIELDS_MAP.has(value as ReceiptFieldsKeyValues))
15
19
  }
16
20
 
17
21
  function isParsedReceiptContentComplete (data: ParsedReceipt): data is ParsedReceipt {
@@ -24,10 +28,10 @@ function isParsedReceiptContentComplete (data: ParsedReceipt): data is ParsedRec
24
28
  return true
25
29
  }
26
30
 
27
- function extractFieldValue (field: OctetString): string {
31
+ function extractFieldValue (field: ASN1.OctetString): string {
28
32
  const [fieldValue] = field.valueBlock.value
29
33
 
30
- if (fieldValue instanceof IA5String || fieldValue instanceof Utf8String) {
34
+ if (fieldValue instanceof ASN1.IA5String || fieldValue instanceof ASN1.Utf8String) {
31
35
  return fieldValue.valueBlock.value
32
36
  }
33
37
 
@@ -46,7 +50,7 @@ function appendField (parsed: ParsedReceipt, name: ReceiptFieldsKeyNames, value:
46
50
  parsed[name] = value
47
51
  }
48
52
 
49
- function processField (parsed: ParsedReceipt, fieldKey: number, fieldValue: OctetString) {
53
+ function processField (parsed: ParsedReceipt, fieldKey: number, fieldValue: ASN1.OctetString) {
50
54
  if (fieldKey === IN_APP) {
51
55
  parseOctetStringContent(parsed, fieldValue)
52
56
  return
@@ -60,16 +64,17 @@ function processField (parsed: ParsedReceipt, fieldKey: number, fieldValue: Octe
60
64
  appendField(parsed, name, extractFieldValue(fieldValue))
61
65
  }
62
66
 
63
- function parseOctetStringContent (parsed: ParsedReceipt, content: OctetString) {
64
- const [contentSet] = content.valueBlock.value as Set[]
65
- const contentSetSequences = contentSet.valueBlock.value.filter(v => v instanceof Sequence) as Sequence[]
67
+ function parseOctetStringContent (parsed: ParsedReceipt, content: ASN1.OctetString) {
68
+ const [contentSet] = content.valueBlock.value as ASN1.Set[]
69
+ const contentSetSequences = contentSet.valueBlock.value
70
+ .filter(v => v instanceof ASN1.Sequence) as ASN1.Sequence[]
66
71
 
67
72
  for (const sequence of contentSetSequences) {
68
73
  const verifiedSequence = verifyFieldSchema(sequence)
69
74
  if (verifiedSequence) {
70
75
  // We are confident to use "as" assertion because Integer type is guaranteed by positive verification above
71
- const fieldKey = (verifiedSequence.result[FIELD_TYPE_ID] as Integer).valueBlock.valueDec
72
- const fieldValueOctetString = verifiedSequence.result[FIELD_VALUE_ID] as OctetString
76
+ const fieldKey = (verifiedSequence.result[FIELD_TYPE_ID] as ASN1.Integer).valueBlock.valueDec
77
+ const fieldValueOctetString = verifiedSequence.result[FIELD_VALUE_ID] as ASN1.OctetString
73
78
 
74
79
  processField(parsed, fieldKey, fieldValueOctetString)
75
80
  }
@@ -84,8 +89,9 @@ function postprocessParsedReceipt (parsed: ParsedReceipt) {
84
89
  export function parseReceipt (receipt: string): ParsedReceipt {
85
90
  const rootSchemaVerification = verifyReceiptSchema(receipt)
86
91
 
87
- const content = rootSchemaVerification.result[CONTENT_ID] as OctetString
92
+ const content = rootSchemaVerification.result[CONTENT_ID] as ASN1.OctetString
88
93
  const parsed: ParsedReceipt = {
94
+ ENVIRONMENT: 'Production',
89
95
  IN_APP_ORIGINAL_TRANSACTION_IDS: [],
90
96
  IN_APP_TRANSACTION_IDS: [],
91
97
  }
package/src/utils.ts CHANGED
@@ -1 +1 @@
1
- export const uniqueArrayValues = (array: string[]) => Array.from(new Set(array))
1
+
@@ -38,7 +38,7 @@ const fieldSchema = new Sequence({
38
38
  value: [
39
39
  new Integer({ name: FIELD_TYPE_ID }),
40
40
  new Integer(),
41
- new OctetString({ name: FIELD_VALUE_ID })
41
+ new OctetString({ name: FIELD_VALUE_ID }),
42
42
  ],
43
43
  })
44
44