Skip to content
This repository has been archived by the owner on Nov 27, 2019. It is now read-only.

Commit

Permalink
A working mozpay extracted from Bazuga.
Browse files Browse the repository at this point in the history
  • Loading branch information
kumar303 committed Feb 18, 2013
0 parents commit b2d405c
Show file tree
Hide file tree
Showing 5 changed files with 682 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
@@ -0,0 +1,4 @@
*.sw[po]
.DS_Store
node_modules
npm-debug.log
150 changes: 150 additions & 0 deletions README.md
@@ -0,0 +1,150 @@
# mozpay

This is a NodeJS module for processing
[navigator.mozPay()](https://wiki.mozilla.org/WebAPI/WebPayment) payments on a
server.

You'll need to obtain a secret key from a provider such as the
[Firefox Marketplace](https://marketplace.firefox.com/)
but this module should work with any compliant
[provider](https://wiki.mozilla.org/WebAPI/WebPaymentProvider).
Here is a guide to
[setting up payments](https://developer.mozilla.org/en-US/docs/Apps/Publishing/In-app_payments)
with Firefox Marketplace.

The basic payment flow goes like this:

* A user clicks a buy button on your app
* Your app signs a [JSON Web Token (JWT)](http://openid.net/specs/draft-jones-json-web-token-07.html)
describing the product, the price, and postback/chargeback URLs.
* Your app client fetches the JWT from the server and calls `navigator.mozPay()`
* Your server receives a JWT at its postback or chargeback URL
* If the postback was received and the JWT signature validates against your secret
key then you can disburse your product.

## Install

npm install mozpay

## Configuration

The module is intended to be used server side only because you can't expose
your secret key to the client. There are helpers to hook into an
[express](http://expressjs.com/) app but you could probably use other web frameworks too.

Load the module:

var pay = require('mozpay');

Configure it when your app starts up:

pay.configure({
// This is your Application Key from the Firefox Marketplace Devhub.
mozPayKey: '52ee5d47-9981-40ad-bf6e-bca957f65385',
// This is your Application Secret from the Firefox Marketplace Devhub.
mozPaySecret: 'd6338705419ea14328084e0c182603ebec4e52c1c6cbceda4d61ee125f10c0f728c4451a4637e4e960b3293df8bb6ac5',
// This is the aud (audience) in the JWT. You only need to override this if you want to use a dev server.
mozPayAudience: 'marketplace.firefox.com',
// This is an optional prefix to your postback/chargeback URLs.
// For example, a postback would be available at https://yourapp/mozpay/postback with the default prefix.
mozPayRoutePrefix: '/mozpay'
});

With an [express app object](http://expressjs.com/api.html#express), add your routes:

var express = require('express');
var pay = require('mozpay');
var app = express();

app.configure(function(){
// Make sure you turn on the body parser for POST params.
app.use(express.bodyParser());
});

pay.routes(app);

You can test your postback/chargeback URLs with something like this:

curl -X POST -d notice=JWT http://localhost:3000/mozpay/postback
curl -X POST -d notice=JWT http://localhost:3000/mozpay/chargeback

If you see a 400 Bad Request response then your app is configured to receive
real JWT requests.

You can combine the configuration and routes setup like this:

pay.routes(app, {
mozPayKey: '52ee5d47-9981-40ad-bf6e-bca957f65385',
mozPaySecret: 'd6338705419ea14328084e0c182603ebec4e52c1c6cbceda4d61ee125f10c0f728c4451a4637e4e960b3293df8bb6ac5',
// ...
});

## Events

Here's how to take action when the postback notices are
received. The ``data`` argument to these event handlers are only for valid JWT
notices that pass the signature verification.

pay.on('postback', function(data) {
console.log('product ID ' + data.request.id + ' has been purchased');
console.log('Transaction ID: ' + data.response.transactionID);
});

pay.on('chargeback', function(data) {
console.log('product ID ' + data.request.id + ' failed');
console.log('reason: ' + data.response.reason);
console.log('Transaction ID: ' + data.response.transactionID);
});

The ``data.request`` object is a copy of what you initiated the payment request
with.

## Issuing Payment Requests

When a user clicks the buy button you should fetch a JWT from the server via
Ajax. You can't cache JWTs for too long because they have a short expiration
(generally about an hour).

There is a helper to created a JWT to begin a payment.
On your server create a URL that does something like this:

var jwt = pay.request({
id: 'your-unique-product-id',
name: 'Your Product',
description: 'A little bit more about the product...',
pricePoint: 1, // Consult the Firefox Marketplace price points for details.
// This expands to a price/currency at the time of payment.
productData: 'session_id=xyz', // You can track whatever you like here.
// These must be absolute URLs like what you configured above.
postbackURL: 'http://localhost:3000/mozpay/postback',
chargebackURL: 'http://localhost:3000/mozpay/chargeback'
});

In your client-side JavaScript, you can initiate a payment with the JWT like
this:

var request = navigator.mozPay([jwtFromServer]);
request.onsuccess = function() {
console.log('navigator.mozPay() finished');
// The product has not yet been bought!
// Poll your server until a valid postback has been received.
waitForPostback();
}
request.onerror = function() {
console.log('navigator.mozPay() error: ' + this.error.name);
};

## Developers

Grab the [source](https://github.com/mozilla/mozpay-js):

git clone git://github.com/mozilla/mozpay-js.git

Install the dependencies:

cd mozpay-js
npm install

Here's how to run the tests:

npm test
189 changes: 189 additions & 0 deletions lib/mozpay.js
@@ -0,0 +1,189 @@
var events = require('events');
var util = require('util');

var jwt = require('jwt-simple');

var config;

/*
* Set up custom events. Possible events:
*
* pay.on('postback', function(notice) {
* // process a validated payment notice.
* });
*
* pay.on('chargeback', function(notice) {
* // process a validated chargeback notice.
* });
*
*/
function Pay() {}
util.inherits(Pay, events.EventEmitter);

var pay = new Pay();
module.exports = pay;


/**
* Configure global settings.
*
* @param {Object} settings
* @api public
*/
pay.configure = function(options) {
config = options;
};


/**
* Encode a JWT payment request.
*
* @param {Object} request
* @api public
*/
pay.request = function(request) {
_requireConfig();
return jwt.encode(pay.issueRequest(request), config.mozPaySecret, 'HS256');
};


/**
* Verify an incoming JWT payment notice.
*
* @param {String} encoded JWT
* @api public
*/
pay.verify = function(foreignJwt) {
return jwt.decode(foreignJwt, config.mozPaySecret);
};


/**
* Add routes to an express app object.
*
* @param {Object} express app object
* @param {Object} settings to pass into configure()
* @api public
*/
pay.routes = function(app, options) {
if (options)
pay.configure(options);
_requireConfig();

var prefix = config.mozPayRoutePrefix;
if (prefix && prefix.slice(-1) == '/') {
prefix = prefix.slice(0, -1);
}
if (!prefix)
throw new Error('config.mozPayRoutePrefix cannot be blank');

function handle(req, res, onData) {
var data;

var notice = req.param('notice');
if (!notice) {
res.send(400);
return;
}

try {
data = jwt.decode(notice, config.mozPaySecret);
} catch (er) {
console.log('Ignoring JWT: ' + er.toString());
res.send(400);
return;
}

try {
pay.validateClaims(data);
} catch (er) {
console.log('JWT claims are not valid: ' + er.toString());
res.send(400);
return;
}

if (!data.request) {
console.log('JWT request is empty: ' + data.request);
res.send(400);
return;
}
try {
var tID = data.response.transactionID;
} catch (er) {
console.log('Unexpected JWT object: ' + er.toString());
res.send(400);
return;
}
if (!tID) {
console.log('transactionID is empty: ' + tID);
res.send(400);
return;
}
res.send(tID);
onData(data);
}

app.post(prefix + '/postback', function(req, res) {
handle(req, res, function(data) {
pay.emit('postback', data);
});
});

app.post(prefix + '/chargeback', function(req, res) {
handle(req, res, function(data) {
pay.emit('chargeback', data);
});
});

};


/**
* Issue a JWT object (i.e. not encoded) for a payment request.
*
* @param {Object} request
* @api public
*/
pay.issueRequest = function(request) {
_requireConfig();
return {iss: config.mozPayKey,
aud: config.mozPayAudience,
typ: 'mozilla/payments/pay/v1',
iat: pay.now(),
exp: pay.now() + 3600, // in 1hr
request: request};
};


/*
* Validate the JWT iat/exp/nbf claims.
* */
pay.validateClaims = function(data) {
var now = pay.now();
if (!data.exp || !data.iat) {
throw new Error('JWT is missing the iat or exp claim properties');
}
if (+data.exp < now) {
throw new Error('JWT from iss ' + data.iss + ' expired: ' + data.exp + ' < ' + now);
}
if (data.nbf) {
// Honor the optional nbf (not before) timestamp.
if (+data.nbf > (now + 120)) { // pad for clock skew
throw new Error('JWT from iss ' + data.iss + ' cannot be processed: nbf=' + data.nbf + ' > ' + now);
}
}
}


/*
* Return current UTC unix timestamp.
* */
pay.now = function() {
return Math.round(Date.now() / 1000);
}


function _requireConfig() {
if (!config)
throw new Error('configure() must be called before anything else.');
}
22 changes: 22 additions & 0 deletions package.json
@@ -0,0 +1,22 @@
{
"name": "mozpay",
"version": "0.0.1",
"engines": {
"node": ">= 0.6.17",
"npm": "1.1.x"
},
"dependencies": {
"jwt-simple": "0.1.0"
},
"devDependencies": {
"express": "2.5.8",
"gently": "0.9.2",
"mocha": "1.8.1",
"should": "1.2.1",
"superagent": "0.12.4",
"underscore": "1.4.4"
},
"scripts": {
"test": "_mocha"
}
}

0 comments on commit b2d405c

Please sign in to comment.