paypal-express-checkout-es6
Version:
nodejs library for paypal express checkout api support
335 lines (297 loc) • 10.6 kB
JavaScript
/* @flow */
import request from 'request';
import QS from 'querystring';
import debugLib from 'debug';
export type PaypalECConfigType = {
isSandbox: bool, // true = sandbox, false = prod
CURRENCYCODE: string, // USD
USER: string, // api user name
PWD: string, // api password
SIGNATURE: string, // api signature
VERSION: string // version of api
};
// These are required parameters in every call
export type PaypalECRequestParamType = {
USER: string,
PWD: string,
SIGNATURE: string,
METHOD: string,
VERSION: string
};
export type PaypalECResultType = {
errorCode: ?string,
statusCode: number,
responseBody: string,
result: ?Object
};
export type PaypalECPaymentItemType = {
NAME: string, // item name
QTY: number, // total quantity of the items
AMT: number, // price of an item
TAXAMT: ?number, // tax amount of an item
DESC: ?string, // item description
NUMBER: ?string // item number generated by you
};
export type PaypalECPaymentType = {
AMT: number, // total $ amount of the payment
ITEMAMT: ?number, // total amount of the item
TAXAMT: ?number, // total amount of tax
SHIPPINGAMT: ?number, // total amount of shipping
HANDLINGAMT: ?number, // total amount of shiping handling
SHIPDISCAMT: ?number, // total amount of shipping discount
INSURANCEAMT: ?number, // total amount of insurance
PAYMENTACTION: string, // payment action - Sale, Order, Authorization ....
SELLERPAYPALACCOUNTID: ?string, // paypal email of seller
PAYMENTREQUESTID: ?string, // unique id generated by you for tracking payment
DESC: ?string, // generic description for the payment
items: ?Array<PaypalECPaymentItemType> // items in detail
};
// default config
const defaultConfig: PaypalECConfigType = {
isSandbox: true,
CURRENCYCODE: 'USD',
USER: '',
PWD: '',
SIGNATURE: '',
VERSION: '86'
};
// flow types for request
type RequestErrorType = {
code: string
};
type RequestHttpResponseType = {
statusCode: number
};
/**
* @class PaypalExpressCheckout
*/
export default class {
/**
* @property config
*/
config: PaypalECConfigType
/**
* @property _log
*/
_log: Function // used for logging
/**
* @method constructor
* @param { Object } config - initial config
*/
constructor(config: Object) {
this._log = debugLib('paypal-express-checkout');
this.setConfig(Object.assign({}, defaultConfig, config));
}
/**
* @method setConfig
* @param { Object } config - new config to set
*/
setConfig(config: Object) {
this.config = Object.assign({}, this.config, config);
this._log('%s mode set', this.config.isSandbox ? 'Sandbox' : 'Production');
}
/**
* @method _getAPIEndpoint
* @private
* @returns {string} paypal API endpoint from config
*/
_getAPIEndpoint(): string {
return this.config.isSandbox ? 'https://api-3t.sandbox.paypal.com/nvp' : 'https://api-3t.paypal.com/nvp';
}
/**
* @method _getRequestParams
* @private
* @param {string} method - api method
* @param {Object} params - key,value based parameter list for api
* @returns {PaypalECRequestParamType} parameter object for paypal express checkout call
*/
_getRequestParams(method: string, params: Object): PaypalECRequestParamType {
return {
USER: this.config.USER,
PWD: this.config.PWD,
VERSION: this.config.VERSION,
SIGNATURE: this.config.SIGNATURE,
METHOD: method,
...params
};
}
/**
* @method call
* @param {string} method - api method
* @param {Object} params - key,value based parameter list for api
* @returns {Promise<PaypalECResultType>} promise of api call result
*/
call(method: string, params: Object): Promise<PaypalECResultType> {
const uri: string = this._getAPIEndpoint();
const form: PaypalECRequestParamType = this._getRequestParams(method, params);
return new Promise((resolve: Function, reject: Function) => {
this._log(`POST ${uri}`);
this._log(`Form ${JSON.stringify(form, null, 2)}`);
request.post({
uri,
form
}, (err: ?RequestErrorType, httpResp: RequestHttpResponseType, responseBody: string) => {
const result: PaypalECResultType = {
errorCode: null,
responseBody,
statusCode: httpResp.statusCode,
result: QS.parse(responseBody)
};
this._log(`Response http status ${result.statusCode}`);
this._log(`Response error code ${result.errorCode}`);
this._log(`Response result ${JSON.stringify(result.result, null, 2)}`);
if (err) {
result.errorCode = err.code;
reject(result);
} else {
resolve(result);
}
});
});
}
/**
* @method getUrlFromToken
* @param {string} token - auth token created from other funcs
* @returns {string} Full paypal url to redirect user for actions
*/
getUrlFromToken(token: string): string {
const sandboxText: string = this.config.isSandbox ? '.sandbox' : '';
return `https://www${sandboxText}.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=${token}`;
}
// ==============================================
// Functions for parallel payment
// ==============================================
/**
* @method _getPaymentRequestForm
* @param {Array<PaypalECPaymentType>} paymentData - multiple payment info for parallel payment
* @returns {Object} payment form data
*/
_getPaymentRequestForm(paymentData: Array<PaypalECPaymentType> = []): Object {
const formData = {};
this._log(`${paymentData.length} parallel payments to do`);
paymentData.forEach((payment: PaypalECPaymentType, index: number) => {
formData[`PAYMENTREQUEST_${index}_CURRENCYCODE`] = this.config.CURRENCYCODE;
Object.keys(payment).forEach((key: string) => {
if (key !== 'items') {
formData[`PAYMENTREQUEST_${index}_${key}`] = payment[key];
}
});
if (payment.items) {
this._log(`${payment.items.length} items presents in payment no.${index}`);
payment.items.forEach((item: PaypalECPaymentItemType, itemIndex: number) => {
Object.keys(item).forEach((key: string) => {
formData[`L_PAYMENTREQUEST_${index}_${key}${itemIndex}`] = item[key];
});
});
}
});
return formData;
}
/**
* @method getExpressCheckoutDetails
* @param {string} token - auth token
* @returns {Promise<PaypalECResultType>} promise of api call result
*/
getExpressCheckoutDetails(token: string): Promise<PaypalECResultType> {
const params: Object = {
TOKEN: token
};
return this.call('GetExpressCheckoutDetails', params);
}
/**
* @method setExpressCheckout
* @param {Array<PaypalECPaymentType>} paymentData - multiple payment info for parallel payment
* @param {string} returnUrl - success callback url
* @param {string} cancelUrl - cancel callback url
* @param {string} additionalParams - additional parameters
* @returns {Promise<PaypalECResultType>} promise of api call result
*/
setExpressCheckout(paymentData: Array<PaypalECPaymentType>, returnUrl: string, cancelUrl: string, additionalParams: Object = {})
: Promise<PaypalECResultType> {
const params: Object = Object.assign({
VERSION: '93',
cancelUrl,
returnUrl,
...additionalParams
}, this._getPaymentRequestForm(paymentData));
return this.call('SetExpressCheckout', params);
}
/**
* @method doExpressCheckoutPayment
* @param {string} token - auth token
* @param {string} payerID - payerId returned from getExpressCheckoutDetails
* @param {Array<PaypalECPaymentType>} paymentData - multiple payment info for parallel payment
* @returns {Promise<PaypalECResultType>} promise of api call result
*/
doExpressCheckoutPayment(token: string, payerID: string, paymentData: Array<PaypalECPaymentType>)
: Promise<PaypalECResultType> {
// we only use necessary information from payment data
const tunedPaymentData = paymentData.map((payment: PaypalECPaymentType): PaypalECPaymentType => {
const {
AMT,
PAYMENTREQUESTID,
SELLERPAYPALACCOUNTID
} = payment;
const data: PaypalECPaymentType = {
AMT,
PAYMENTREQUESTID,
SELLERPAYPALACCOUNTID
};
return data;
});
const params: Object = Object.assign({
VERSION: '93',
TOKEN: token,
PAYERID: payerID
}, this._getPaymentRequestForm(tunedPaymentData));
return this.call('DoExpressCheckoutPayment', params);
}
// ==============================================
// Functions for reference payment
// ==============================================
/**
* @method getBillingAuthorizationToken
* @param {string} billingType - type of billing agreement
* @param {string} billingDesription - billing agreement description
* @param {string} returnUrl - success callback url
* @param {string} cancelUrl - cancel callback url
* @returns {Promise<PaypalECResultType>} promise of api call result
*/
getReferenceAuthToken(billingType: string, billingDesription: string, returnUrl: string, cancelUrl: string): Promise<PaypalECResultType> {
const params: Array<PaypalECPaymentType> = [{
PAYMENTACTION: 'AUTHORIZATION',
AMT: 0
}];
return this.setExpressCheckout(params, returnUrl, cancelUrl, {
L_BILLINGTYPE0: billingType,
L_BILLINGAGREEMENTDESCRIPTION0: billingDesription,
VERSION: '86'
});
}
/**
* @method createBillingAgreement
* @param {string} token - auth token created from getBillingAuthorizationToken
* @returns {Promise<PaypalECResultType>} promise of api call result
*/
createBillingAgreement(token: string): Promise<PaypalECResultType> {
const params: Object = {
TOKEN: token
};
return this.call('CreateBillingAgreement', params);
}
/**
* @method doReferenceTransaction
* @param {string} amount - money to capture from customer's account
* @param {string} paymentAction - type of payment
* @param {referenceID} - billing id created with createBillingAgreement
* @returns {Promise<PaypalECResultType>} promise of api call result
*/
doReferenceTransaction(amount: number, paymentAction: string, referenceID: string): Promise<PaypalECResultType> {
const params: Object = {
AMT: amount,
PAYMENTACTION: paymentAction,
REFERENCEID: referenceID
};
return this.call('DoReferenceTransaction', params);
}
}