Skip to content

Commit

Permalink
Merge pull request #18 from xendit/feat/payout
Browse files Browse the repository at this point in the history
Payouts endpoints
  • Loading branch information
stanleynguyen authored Dec 29, 2019
2 parents 9c16dde + 03328ef commit 9195808
Show file tree
Hide file tree
Showing 11 changed files with 292 additions and 0 deletions.
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ For PCI compliance to be maintained, tokenization of credt cards info should be
+ [Methods](#methods-3)
* [Recurring Payments Services](#recurring-payments-services)
+ [Methods](#methods-4)
* [Payout Services](#payout-services)
+ [Methods](#methods-5)
- [Contributing](#contributing)

<!-- tocstop -->
Expand Down Expand Up @@ -473,6 +475,47 @@ rp.pausePayment(data: { id: string }): Promise<object>;
rp.resumePayment(data: { id: string }): Promise<object>;
```

### Payout Services

Instanitiate Payout service using constructor that has been injected with Xendit keys

```js
const { Payout } = x;
const payoutSpecificOptions = {};
const p = new Payout(payoutSpecificOptions);
```

Example: Create a payout

```js
p.createPayout({
externalID: 'your-external-id',
amount: 100000,
}).then(({ id }) => {
console.log(`Invoice created with ID: ${id}`);
});
```

#### Methods

- Create a payout

```ts
p.createPayout(data: { externalID: string; amount: string })
```

- Get a payout

```ts
p.getPayout(data: { id: string })
```

- Void a payout

```ts
p.voidPayout(data: { id: string })
```

## Contributing

Running test suite
Expand Down
27 changes: 27 additions & 0 deletions examples/payout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const x = require('./xendit');

const { Payout } = x;
const p = new Payout({});

p.createPayout({
externalID: Date.now().toString(),
amount: 10000,
})
.then(r => {
console.log('created payout:', r); // eslint-disable-line no-console
return r;
})
.then(({ id }) => p.getPayout({ id }))
.then(r => {
console.log('retrieved payout:', r); // eslint-disable-line no-console
return r;
})
.then(({ id }) => p.voidPayout({ id }))
.then(r => {
console.log('payout voided:', r.id); // eslint-disable-line no-console
return r;
})
.catch(e => {
console.error(e); // eslint-disable-line no-console
process.exit(1);
});
1 change: 1 addition & 0 deletions integration_test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Promise.all([
require('./disbursement.test')(),
require('./invoice.test')(),
require('./va.test')(),
require('./payout.test')(),
require('./recurring.test')(),
])
.then(() => {
Expand Down
18 changes: 18 additions & 0 deletions integration_test/payout.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const x = require('./xendit.test');

const { Payout } = x;
const p = new Payout({});

module.exports = function() {
return p
.createPayout({
externalID: Date.now().toString(),
amount: 10000,
})
.then(({ id }) => p.getPayout({ id }))
.then(({ id }) => p.voidPayout({ id }))
.then(() => {
// eslint-disable-next-line no-console
console.log('Payout integration test done...');
});
};
3 changes: 3 additions & 0 deletions src/payout/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const PayoutService = require('./payout');

module.exports = { PayoutService };
11 changes: 11 additions & 0 deletions src/payout/payout.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { XenditOptions } from '../xendit_opts';

export = class Payout {
constructor({});
static _constructorWithInjectedXenditOpts: (
opts: XenditOptions,
) => typeof Payout;
createPayout(data: { externalID: string; amount: string }): Promise<object>;
getPayout(data: { id: string }): Promise<object>;
voidPayout(data: { id: string }): Promise<object>;
};
71 changes: 71 additions & 0 deletions src/payout/payout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
const { promWithJsErr, Validate, fetchWithHTTPErr, Auth } = require('../utils');

const PAYOUT_PATH = '/payouts';

function Payout(options) {
let aggOpts = options;
if (Payout._injectedOpts && Object.keys(Payout._injectedOpts).length > 0) {
aggOpts = Object.assign({}, options, Payout._injectedOpts);
}

this.opts = aggOpts;
this.API_ENDPOINT = this.opts.xenditURL + PAYOUT_PATH;
}

Payout._injectedOpts = {};
Payout._constructorWithInjectedXenditOpts = function(options) {
Payout._injectedOpts = options;
return Payout;
};

Payout.prototype.createPayout = function(data) {
return promWithJsErr((resolve, reject) => {
Validate.rejectOnMissingFields(['externalID', 'amount'], data, reject);

fetchWithHTTPErr(`${this.API_ENDPOINT}`, {
method: 'POST',
headers: {
Authorization: Auth.basicAuthHeader(this.opts.secretKey),
'Content-Type': 'application/json',
},
body: JSON.stringify({
external_id: data.externalID,
amount: data.amount,
}),
})
.then(resolve)
.catch(reject);
});
};

Payout.prototype.getPayout = function(data) {
return promWithJsErr((resolve, reject) => {
Validate.rejectOnMissingFields(['id'], data, reject);

fetchWithHTTPErr(`${this.API_ENDPOINT}/${data.id}`, {
method: 'GET',
headers: {
Authorization: Auth.basicAuthHeader(this.opts.secretKey),
},
})
.then(resolve)
.catch(reject);
});
};

Payout.prototype.voidPayout = function(data) {
return promWithJsErr((resolve, reject) => {
Validate.rejectOnMissingFields(['id'], data, reject);

fetchWithHTTPErr(`${this.API_ENDPOINT}/${data.id}/void`, {
method: 'POST',
headers: {
Authorization: Auth.basicAuthHeader(this.opts.secretKey),
},
})
.then(resolve)
.catch(reject);
});
};

module.exports = Payout;
2 changes: 2 additions & 0 deletions src/xendit.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { CardService } from './card';
import { VAService } from './va';
import { DisbursementService } from './disbursement';
import { InvoiceService } from './invoice';
import { PayoutService } from './payout';
import { RecurringPayment } from './recurring';
import { XenditOptions } from './xendit_opts';

Expand All @@ -13,5 +14,6 @@ export = class Xendit {
VirtualAcc: typeof VAService;
Disbursement: typeof DisbursementService;
Invoice: typeof InvoiceService;
Payout: typeof PayoutService;
RecurringPayment: typeof RecurringPayment;
};
2 changes: 2 additions & 0 deletions src/xendit.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const { CardService } = require('./card');
const { VAService } = require('./va');
const { DisbursementService } = require('./disbursement');
const { InvoiceService } = require('./invoice');
const { PayoutService } = require('./payout');
const { RecurringPayment } = require('./recurring');
const Errors = require('./errors');

Expand All @@ -22,6 +23,7 @@ function Xendit(options) {
this.opts,
);
this.Invoice = InvoiceService._constructorWithInjectedXenditOpts(this.opts);
this.Payout = PayoutService._constructorWithInjectedXenditOpts(this.opts);
this.RecurringPayment = RecurringPayment._constructorWithInjectedXenditOpts(
this.opts,
);
Expand Down
15 changes: 15 additions & 0 deletions test/payout/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const EXT_ID = '123';
const PAYOUT_ID = '34a2fcb6-37d8-4c11-b2dc-c46615662f23';
const AMOUNT = 10000;
const VALID_PAYOUT = {
id: PAYOUT_ID,
external_id: EXT_ID,
amount: AMOUNT,
};

module.exports = {
EXT_ID,
PAYOUT_ID,
AMOUNT,
VALID_PAYOUT,
};
99 changes: 99 additions & 0 deletions test/payout/payout.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
const chai = require('chai');
const chaiAsProm = require('chai-as-promised');
const TestConstants = require('./constants');
const { expect } = chai;
const nock = require('nock');
const { Errors } = require('../../src/xendit');
const Xendit = require('../../src/xendit');

const x = new Xendit({
publicKey: 'fake_public_key',
secretKey: 'fake_secret_key',
});

chai.use(chaiAsProm);

const { Payout } = x;
let p = new Payout({});
beforeEach(function() {
p = new Payout({});
});
before(function() {
nock(p.API_ENDPOINT)
.post('', {
external_id: TestConstants.EXT_ID,
amount: TestConstants.AMOUNT,
})
.reply(201, TestConstants.VALID_PAYOUT)
.get(`/${TestConstants.PAYOUT_ID}`)
.reply(200, TestConstants.VALID_PAYOUT)
.post(`/${TestConstants.PAYOUT_ID}/void`)
.reply(200, TestConstants.VALID_PAYOUT);
});

describe('Payout Service', () => {
describe('createPayout', () => {
it('should create a payout', done => {
expect(
p.createPayout({
externalID: TestConstants.EXT_ID,
amount: TestConstants.AMOUNT,
}),
)
.to.eventually.deep.equal(TestConstants.VALID_PAYOUT)
.and.notify(done);
});

it('should report missing required fields', done => {
expect(p.createPayout({}))
.to.eventually.be.rejected.then(e =>
Promise.all([
expect(e).to.have.property('status', 400),
expect(e).to.have.property('code', Errors.API_VALIDATION_ERROR),
]),
)
.then(() => done())
.catch(e => done(e));
});
});

describe('getPayout', () => {
it('should retrieve payout details', done => {
expect(p.getPayout({ id: TestConstants.PAYOUT_ID }))
.to.eventually.deep.equal(TestConstants.VALID_PAYOUT)
.and.notify(done);
});

it('should report missing required fields', done => {
expect(p.getPayout({}))
.to.eventually.be.rejected.then(e =>
Promise.all([
expect(e).to.have.property('status', 400),
expect(e).to.have.property('code', Errors.API_VALIDATION_ERROR),
]),
)
.then(() => done())
.catch(e => done(e));
});
});

describe('voidPayout', () => {
it('should void a payout', done => {
expect(p.voidPayout({ id: TestConstants.PAYOUT_ID }))
.to.eventually.deep.equal(TestConstants.VALID_PAYOUT)
.and.notify(done);
});

it('should report missing required fields', done => {
expect(p.voidPayout({}))
.to.eventually.be.rejected.then(e =>
Promise.all([
expect(e).to.have.property('status', 400),
expect(e).to.have.property('code', Errors.API_VALIDATION_ERROR),
]),
)
.then(() => done())
.catch(e => done(e));
});
});
});

0 comments on commit 9195808

Please sign in to comment.