Skip to content

Commit aa44150

Browse files
Justin PageRon Korving
authored andcommitted
Use shared secret and receipt field (#24)
* Adopt getReceiptFieldValue for apple in_app If field is not found in object root, check in_app for value * Use latest_receipt_info when available latest receipt info contains up-to-date info on a receipt's history. Especially regarding monthly accounts that auto-renew. We should provide the transaction id and product id from this field when available. By default we use the receipt in_app as a starting point. * Reference latestReceiptInfo for apple receipts Added a brief example that includes a monthly auto-renewable subscription as well as a reference to when the in_app and latestReceiptInfo may be available. * Sort latest receipt info by transaction id transaction id is iterable and thus is a better field to sort by versus date
1 parent cd5caa9 commit aa44150

File tree

2 files changed

+105
-39
lines changed

2 files changed

+105
-39
lines changed

README.md

Lines changed: 62 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -54,35 +54,74 @@ contains your In-App Purchase Shared Secret
5454
**The response**
5555

5656
The response passed back to your callback will also be Apple specific. The entire parsed receipt
57-
will be in the result object:
57+
will be in the result object. Applications that support monthly and yearly
58+
subscription access will represent auto-renewable terms in either the `in_app`
59+
or `latestReceiptInfo` property.
5860

5961
```json
6062
{
61-
"receipt": {
62-
"original_purchase_date_pst": "2014-02-24 23:19:49 America/Los_Angeles",
63-
"purchase_date_ms": "1393312789954",
64-
"unique_identifier": "78abf2209323434771637ee22f0ee8b8341f14b4",
65-
"original_transaction_id": "1000000102526370",
66-
"bvrs": "0.0.1",
67-
"transaction_id": "1000000102526671",
68-
"quantity": "1",
69-
"unique_vendor_identifier": "206FED24-2EAB-4FC6-B946-4AF61086DF21",
70-
"item_id": "820817285",
71-
"product_id": "abc",
72-
"purchase_date": "2014-02-25 07:19:49 Etc/GMT",
73-
"original_purchase_date": "2014-02-25 07:19:49 Etc/GMT",
74-
"purchase_date_pst": "2014-02-24 23:19:49 America/Los_Angeles",
75-
"bid": "test.myapp",
76-
"original_purchase_date_ms": "1393312789954"
77-
},
78-
"transactionId": "1000000102526671",
79-
"productId": "abc",
80-
"platform": "apple",
81-
"environment": "production"
63+
"receipt": {
64+
"original_purchase_date_pst": "2016-10-29 15:46:57 America/Los_Angeles",
65+
"purchase_date_ms": "1477802802000",
66+
"unique_identifier": "78abf2209323434771637ee22f0ee8b8341f14b4",
67+
"original_transaction_id": "120000257973875",
68+
"bvrs": "0.0.1",
69+
"transaction_id": "120000265421254",
70+
"quantity": "1",
71+
"unique_vendor_identifier": "206FED24-2EAB-4FC6-B946-4AF61086DF21",
72+
"item_id": "820817285",
73+
"product_id": "abc",
74+
"purchase_date": "2016-10-29 22:46:57 Etc/GMT",
75+
"original_purchase_date": "2016-10-29 22:46:57 Etc/GMT",
76+
"purchase_date_pst": "2016-10-29 15:46:57 America/Los_Angeles",
77+
"bid": "test.myapp",
78+
"original_purchase_date_ms": "1477781217000",
79+
"in_app": [
80+
{
81+
"quantity": "1",
82+
"product_id": "abc",
83+
"transaction_id": "120000265421254",
84+
"original_transaction_id": "120000257973875",
85+
"purchase_date": "2016-10-30 04:46:42 Etc/GMT",
86+
"purchase_date_ms": "1477802802000",
87+
"purchase_date_pst": "2016-10-29 21:46:42 America/Los_Angeles",
88+
"original_purchase_date": "2016-10-29 22:46:57 Etc/GMT",
89+
"original_purchase_date_ms": "1477781217000",
90+
"original_purchase_date_pst": "2016-10-29 15:46:57 America/Los_Angeles",
91+
"expires_date": "2016-11-30 05:46:42 Etc/GMT",
92+
"expires_date_ms": "1480484802000",
93+
"expires_date_pst": "2016-11-29 21:46:42 America/Los_Angeles",
94+
"web_order_line_item_id": "820817285",
95+
"is_trial_period": "false"
96+
},
97+
]
98+
},
99+
"latestReceiptInfo": [
100+
{
101+
"quantity": "1",
102+
"product_id": "abc",
103+
"transaction_id": "120000233230473",
104+
"original_transaction_id": "120000233230473",
105+
"purchase_date": "2016-06-12 22:36:58 Etc/GMT",
106+
"purchase_date_ms": "1465771018000",
107+
"purchase_date_pst": "2016-06-12 15:36:58 America/Los_Angeles",
108+
"original_purchase_date": "2016-06-12 22:36:58 Etc/GMT",
109+
"original_purchase_date_ms": "1465771018000",
110+
"original_purchase_date_pst": "2016-06-12 15:36:58 America/Los_Angeles",
111+
"expires_date": "2016-07-12 22:36:58 Etc/GMT",
112+
"expires_date_ms": "1468363018000",
113+
"expires_date_pst": "2016-07-12 15:36:58 America/Los_Angeles",
114+
"web_order_line_item_id": "120000034778618",
115+
"is_trial_period": "true"
116+
},
117+
],
118+
"transactionId": "120000233230473",
119+
"productId": "abc",
120+
"platform": "apple",
121+
"environment": "production"
82122
}
83123
```
84124

85-
86125
### Google Play
87126

88127
**The payment object**
@@ -167,7 +206,6 @@ will be included:
167206
* productId, which specifies what was purchased.
168207
* platform, which is always the platform you passed.
169208

170-
171209
## License
172210

173211
MIT

lib/apple/index.js

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,26 @@ var responses = {
1717
'21008': 'This receipt is from the production receipt, but it was sent to the test environment service for verification. Send it to the production environment service instead.'
1818
};
1919

20+
function getReceiptFieldValue(receipt, field) {
21+
if (receipt.hasOwnProperty(field)) {
22+
return receipt[field];
23+
}
24+
25+
/* jshint camelcase:false*/
26+
if (receipt.hasOwnProperty('in_app') && receipt.in_app[0] && receipt.in_app[0].hasOwnProperty(field)) {
27+
return receipt.in_app[0][field];
28+
}
29+
30+
return null;
31+
}
2032

2133
function parseResult(result) {
2234
result = JSON.parse(result);
2335

2436
var status = parseInt(result.status, 10);
2537

38+
var latestReceiptInfo = null;
39+
2640
if (status !== 0) {
2741
var msg = responses[status] || 'Unknown status code: ' + status;
2842

@@ -32,18 +46,27 @@ function parseResult(result) {
3246
throw error;
3347
}
3448

35-
var receipt = result.receipt;
49+
var productId = getReceiptFieldValue(result.receipt, 'product_id');
50+
var transactionId = getReceiptFieldValue(result.receipt, 'transaction_id');
51+
52+
/* jshint camelcase:false */
53+
if (result.hasOwnProperty('latest_receipt_info') && result.latest_receipt_info[0]) {
54+
latestReceiptInfo = result.latest_receipt_info.sort(function (a, b) {
55+
return parseInt(a.transaction_id, 10) - parseInt(b.transaction_id, 10);
56+
});
57+
58+
productId = latestReceiptInfo[latestReceiptInfo.length - 1].product_id;
59+
transactionId = latestReceiptInfo[latestReceiptInfo.length - 1].transaction_id;
60+
}
3661

3762
return {
38-
receipt: receipt,
39-
/* jshint camelcase:false */
40-
transactionId: receipt.transaction_id,
41-
productId: receipt.product_id
42-
/* jshint camelcase:true */
63+
receipt: result.receipt,
64+
latestReceiptInfo: latestReceiptInfo,
65+
productId: productId,
66+
transactionId: transactionId
4367
};
4468
}
4569

46-
4770
function verify(environmentUrl, options, cb) {
4871
https.post(environmentUrl, options, function (error, res, resultString) {
4972
if (error) {
@@ -71,7 +94,6 @@ function isBase64like(str) {
7194
return !!str.match(/^[a-zA-Z0-9\/+]+\={0,2}$/);
7295
}
7396

74-
7597
exports.verifyPayment = function (payment, cb) {
7698
var jsonData = {};
7799

@@ -100,18 +122,24 @@ exports.verifyPayment = function (payment, cb) {
100122
}
101123

102124
var receipt = result.receipt;
125+
126+
var productId = getReceiptFieldValue(receipt, 'product_id');
103127

104-
/* jshint camelcase:false */
105-
if (payment.hasOwnProperty('productId') && payment.productId !== receipt.product_id) {
106-
return cb(new Error('Wrong product ID: ' + payment.productId + ' (expected: ' + receipt.product_id + ')'));
128+
if (payment.hasOwnProperty('productId') && payment.productId !== productId) {
129+
return cb(new Error('Wrong product ID: ' + payment.productId + ' (expected: ' + productId + ')'));
107130
}
108-
/* jshint camelcase:true */
131+
132+
var receiptBundleId = getReceiptFieldValue(receipt, 'bid');
109133

110-
if (payment.hasOwnProperty('packageName') && payment.packageName !== receipt.bid) {
111-
return cb(new Error('Wrong bundle ID: ' + payment.packageName + ' (expected: ' + receipt.bid + ')'));
134+
if (receiptBundleId === null) {
135+
receiptBundleId = getReceiptFieldValue(receipt, 'bundle_id');
112136
}
113137

114-
result.environment = environment;
138+
if (payment.hasOwnProperty('packageName') && payment.packageName !== receiptBundleId) {
139+
return cb(new Error('Wrong bundle ID: ' + payment.packageName + ' (expected: ' + receiptBundleId + ')'));
140+
}
141+
142+
result.environment = environment;
115143

116144
return cb(null, result);
117145
}

0 commit comments

Comments
 (0)