Skip to content

Commit

Permalink
Merge pull request #25 from KLVTZ/feature/support-roku-verification
Browse files Browse the repository at this point in the history
Support Roku purchase verification
  • Loading branch information
Ron Korving authored Mar 9, 2017
2 parents af725f2 + 443e38f commit 420238d
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 14 deletions.
66 changes: 53 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,12 @@ written by Paul Crawford, I wanted a pure JavaScript implementation of in-app pu
I also wanted to add support for other app stores, and not just limit this to Apple. The `iap`
module is exactly that. Pull requests to add support for other platforms are very welcome!



## Installation

```sh
npm install iap
```



## Usage

Only a single method is exposed to verify purchase receipts:
Expand All @@ -24,11 +20,12 @@ var iap = require('iap');

var platform = 'apple';
var payment = {
receipt: 'receipt data', // always required
receipt: 'receipt data', // always required
productId: 'abc',
packageName: 'my.app',
secret: 'password',
subscription: true // optional, if google play subscription
subscription: true, // optional, if google play subscription
devToken: 'developer id' // required, if roku
};

iap.verifyPayment(platform, payment, function (error, response) {
Expand All @@ -39,8 +36,6 @@ iap.verifyPayment(platform, payment, function (error, response) {
The receipt you pass must conform to the requirements of the backend you are verifying with. Read
the next chapter for more information on the format.



## Supported platforms

### Apple
Expand Down Expand Up @@ -120,6 +115,48 @@ receipt sub-object.
}
```

### Roku

The receiept string represents the transaction returned from a channel or
product purchase.

A developer ID is required.

**The response**

The response passed back to your callback will also be Roku specific. The entire
parsed receipt will be in the result object:

```json
{
"receipt": {
"errorCode": null,
"errorDetails": null,
"errorMessage": "",
"status": 0,
"amount": 4.99,
"cancelled": false,
"channelId": 70391,
"channelName": "abc",
"couponCode": null,
"currency": "usd",
"expirationDate": 1488337344000,
"originalPurchaseDate": 1483153344000,
"partnerReferenceId": null,
"productId": "5KAZUPGB.0RF0",
"productName": "BASIC - US MONTHLY",
"purchaseDate": 1483153344000,
"quantity": 1,
"rokuCustomerId": "5e56c4c4d7d1504f813c630c2790e54a",
"tax": 0,
"total": 0,
"transactionId": "380e9932-ed9a-48e8-bd66-a6ec00b5efd1"
},
"transactionId": "380e9932-ed9a-48e8-bd66-a6ec00b5efd1",
"productId": "abc",
"platform": "roku"
}
```

### All Platforms

Expand All @@ -131,13 +168,10 @@ will be included:
* platform, which is always the platform you passed.



## License

MIT



## References

### Apple References
Expand All @@ -156,7 +190,7 @@ MIT
* https://bitbucket.org/gooroo175/google-play-purchase-validator/src/d88278c30df0d0dc51b852b7bcab5f40e3a30923/index.js?at=master
* https://github.com/machadogj/node-google-bigquery
* https://github.com/extrabacon/google-oauth-jwt/blob/master/lib/request-jwt.js

**API Reference**

* https://developer.android.com/google/play/billing/gp-purchase-status-api.html
Expand All @@ -168,9 +202,15 @@ MIT
* https://developers.google.com/android-publisher/api-ref/purchases/products/get
* http://developer.android.com/google/play/billing/billing_testing.html
* http://stackoverflow.com/questions/24323207/use-service-account-to-verify-google-inapppurchase

**Receipt Generation**

* http://developer.android.com/training/in-app-billing/preparing-iab-app.html
* http://developer.android.com/tools/publishing/app-signing.html
* http://developer.android.com/google/play/billing/api.html#managed

### Roku References

**API Reference**

* https://sdkdocs.roku.com/display/sdkdoc/Web+Service+API#WebServiceAPI-/listen/transaction-service.svc/validate-transaction/{devtoken}/{transactionid}
22 changes: 22 additions & 0 deletions bin/verify-roku.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env node

var argv = require('minimist')(process.argv.slice(2), { string: ['devToken', 'receipt'] });

if (argv.help) {
console.log('Usage: ./verfiy.js --devToken=\'developer-id\' --receipt=\'transaction-data\'');
process.exit(1);
}

var iap = require('../index.js');

var platform = 'roku';
var payment = argv;

iap.verifyPayment(platform, payment, function (error, result) {
if (error) {
return console.log(error);
}

console.log('Verified:');
console.log(JSON.stringify(result, null, '\t'));
});
3 changes: 2 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
var platforms = {
apple: require('./lib/apple'),
google: require('./lib/google')
google: require('./lib/google'),
roku: require('./lib/roku')
};


Expand Down
55 changes: 55 additions & 0 deletions lib/roku/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
var assert = require('assert');
var https = require('../https');

var apiUrl = {
production: 'https://apipub.roku.com/listen/transaction-service.svc/validate-transaction'
};

exports.verifyPayment = function (payment, cb) {
try {
assert.equal(typeof payment.devToken, 'string', 'Developer ID must be a string');
assert.equal(typeof payment.receipt, 'string', 'Receipt must be a string');
assert.equal(payment.receipt.match(/\w/g).length, 32, 'Receipt must contain 32 digits');
assert.equal(payment.receipt.match(/-/g).length, 4, 'Receipt must contain 4 dashes');
} catch (error) {
return process.nextTick(function () {
cb(error);
});
}

var requestUrl = apiUrl.production + '/' + payment.devToken + '/' + payment.receipt;

https.get(requestUrl, {headers: {Accept: 'application/json'}}, function (error, res, resultString) {
if (error) {
return cb(error);
}

if (res.statusCode !== 200) {
return cb(new Error('Received ' + res.statusCode + ' status code with body: ' + resultString));
}

var resultObject = null;

try {
resultObject = JSON.parse(resultString);
} catch (error) {
return cb(error);
}

if (resultObject.errorMessage) {
return cb(new Error(resultObject.errorMessage));
}

// parse non-standard date properties (i.e. /Date(1483242628000-0800)/)
// in order to extract value by milliseconds
resultObject.expirationDate = new Date(parseInt(resultObject.expirationDate.substr(6), 10)).getTime();
resultObject.originalPurchaseDate = new Date(parseInt(resultObject.originalPurchaseDate.substr(6), 10)).getTime();
resultObject.purchaseDate = new Date(parseInt(resultObject.purchaseDate.substr(6), 10)).getTime();

cb(null, {
receipt: resultObject,
transactionId: resultObject.transactionId,
productId: resultObject.productId
});
});
};

0 comments on commit 420238d

Please sign in to comment.