Skip to content

Commit

Permalink
Merge pull request #609 from stripe/paulasjes/expose-webhook-header-g…
Browse files Browse the repository at this point in the history
…enerator

Exposing generateHeaderString as generateWebhookHeaderString
  • Loading branch information
paulasjes-stripe authored May 8, 2019
2 parents 0736f67 + 1062f11 commit 1f90bce
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 27 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
*.node*.js
node_modules
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,29 @@ const event = stripe.webhooks.constructEvent(
);
```

#### Testing Webhook signing

You can use `generateWebhookHeaderString` to mock webhook events that come from Stripe:

```js
const payload = {
id: 'evt_test_webhook',
object: 'event',
};

const payloadString = JSON.stringify(payload, null, 2);
const secret = 'whsec_test_secret';

const header = stripe.generateWebhookHeaderString({
payload: payloadString,
});

const event = stripe.webhooks.constructEvent(payloadString, header, secret);

// Do something with mocked signed event
expect(event.id).to.equal(payload.id);
```

### Writing a Plugin

If you're writing a plugin that uses the library, we'd appreciate it if you identified using `stripe.setAppInfo()`:
Expand Down
6 changes: 6 additions & 0 deletions lib/stripe.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ var APP_INFO_PROPERTIES = ['name', 'version', 'url', 'partner_id'];

var EventEmitter = require('events').EventEmitter;
var utils = require('./utils');
var testUtils = require('../testUtils');

var resourceNamespace = require('./ResourceNamespace');

Expand Down Expand Up @@ -340,6 +341,11 @@ Stripe.prototype = {
return this._enableTelemetry;
},

// Generates a header to be used for webhook mocking
generateWebhookHeaderString: function(opts) {
return testUtils.generateHeaderString.apply(testUtils, opts);
},

_prepResources: function() {
for (var name in resources) {
this[utils.pascalToCamelCase(name)] = new resources[name](this);
Expand Down
33 changes: 6 additions & 27 deletions test/Webhook.spec.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
'use strict';

var stripe = require('../testUtils').getSpyableStripe();
var testUtils = require('../testUtils');
var stripe = testUtils.getSpyableStripe();
var generateHeaderString = testUtils.generateHeaderString;
var expect = require('chai').expect;
var Buffer = require('safe-buffer').Buffer;

var EVENT_PAYLOAD = {
id: 'evt_test_webhook',
object: 'event',
};
var EVENT_PAYLOAD_STRING = JSON.stringify(EVENT_PAYLOAD, null, 2);

var SECRET = 'whsec_test_secret';
var EVENT_PAYLOAD = testUtils.EVENT_PAYLOAD;
var EVENT_PAYLOAD_STRING = testUtils.EVENT_PAYLOAD_STRING;
var SECRET = testUtils.WEBHOOK_SECRET;

describe('Webhooks', function() {
describe('.constructEvent', function() {
Expand Down Expand Up @@ -136,22 +134,3 @@ describe('Webhooks', function() {
});
});
});

function generateHeaderString(opts) {
opts = opts || {};

opts.timestamp = Math.floor(opts.timestamp) || Math.floor(Date.now() / 1000);
opts.payload = opts.payload || EVENT_PAYLOAD_STRING;
opts.secret = opts.secret || SECRET;
opts.scheme = opts.scheme || stripe.webhooks.signature.EXPECTED_SCHEME;

opts.signature = opts.signature ||
stripe.webhooks.signature._computeSignature(opts.timestamp + '.' + opts.payload, opts.secret);

var generatedHeader = [
't=' + opts.timestamp,
opts.scheme + '=' + opts.signature,
].join(',');

return generatedHeader;
}
41 changes: 41 additions & 0 deletions testUtils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ var ResourceNamespace = require('../lib/ResourceNamespace').ResourceNamespace;

var utils = module.exports = {

EVENT_PAYLOAD: {
id: 'evt_test_webhook',
object: 'event',
},

get EVENT_PAYLOAD_STRING() {
return JSON.stringify(this.EVENT_PAYLOAD, null, 2);
},

WEBHOOK_SECRET: 'whsec_test_secret',

getUserStripeKey: function() {
var key = process.env.STRIPE_TEST_API_KEY || 'tGN0bIwXnHdwOa85VABjPdSn8nWY7G7I';

Expand Down Expand Up @@ -158,4 +169,34 @@ var utils = module.exports = {
}
},

/**
* Generates a header to be used for webhook mocking
*
* @typedef {object} opts
* @property {object} stripe - Instance of Stripe to use. Defaults to a spyable instance for testing.
* @property {number} timestamp - Timestamp of the header. Defaults to Date.now()
* @property {string} payload - JSON stringified payload object, containing the 'id' and 'object' parameters
* @property {string} secret - Stripe webhook secret 'whsec_...'
* @property {string} scheme - Version of API to hit. Defaults to 'v1'.
* @property {string} signature - Computed webhook signature
*/
generateHeaderString: function(opts) {
opts = opts || {};

opts.stripe = opts.stripe || utils.getSpyableStripe();
opts.timestamp = Math.floor(opts.timestamp) || Math.floor(Date.now() / 1000);
opts.payload = opts.payload || utils.EVENT_PAYLOAD_STRING;
opts.secret = opts.secret || utils.WEBHOOK_SECRET;
opts.scheme = opts.scheme || opts.stripe.webhooks.signature.EXPECTED_SCHEME;

opts.signature = opts.signature ||
opts.stripe.webhooks.signature._computeSignature(opts.timestamp + '.' + opts.payload, opts.secret);

var generatedHeader = [
't=' + opts.timestamp,
opts.scheme + '=' + opts.signature,
].join(',');

return generatedHeader;
},
};

0 comments on commit 1f90bce

Please sign in to comment.