Compare commits

...

11 Commits

Author SHA1 Message Date
6f0952bc22 1.0.11 2020-08-25 11:57:04 +00:00
08c78bae05 fix(core): update 2020-08-25 11:57:03 +00:00
406178d6d0 1.0.10 2020-08-24 01:55:51 +00:00
d6a0a9d438 fix(core): update 2020-08-24 01:55:50 +00:00
12b6a771a2 1.0.9 2020-08-23 15:36:08 +00:00
ebf2796e0e fix(core): update 2020-08-23 15:36:07 +00:00
fe62ecd0e1 1.0.8 2020-08-23 15:35:28 +00:00
4972b135d5 fix(core): update 2020-08-23 15:35:28 +00:00
e15f0ff2b2 1.0.7 2020-08-23 13:49:33 +00:00
4bbcb9639c 1.0.6 2020-08-23 13:48:47 +00:00
306453cde4 fix(core): update 2020-08-23 13:48:46 +00:00
8 changed files with 281 additions and 85 deletions

46
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "@mojoio/paypal", "name": "@mojoio/paypal",
"version": "1.0.5", "version": "1.0.11",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -10492,6 +10492,50 @@
"resolved": "https://verdaccio.lossless.one/tslib/-/tslib-1.13.0.tgz", "resolved": "https://verdaccio.lossless.one/tslib/-/tslib-1.13.0.tgz",
"integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q=="
}, },
"tslint": {
"version": "6.1.3",
"resolved": "https://verdaccio.lossless.one/tslint/-/tslint-6.1.3.tgz",
"integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
"builtin-modules": "^1.1.1",
"chalk": "^2.3.0",
"commander": "^2.12.1",
"diff": "^4.0.1",
"glob": "^7.1.1",
"js-yaml": "^3.13.1",
"minimatch": "^3.0.4",
"mkdirp": "^0.5.3",
"resolve": "^1.3.2",
"semver": "^5.3.0",
"tslib": "^1.13.0",
"tsutils": "^2.29.0"
},
"dependencies": {
"builtin-modules": {
"version": "1.1.1",
"resolved": "https://verdaccio.lossless.one/builtin-modules/-/builtin-modules-1.1.1.tgz",
"integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
"dev": true
}
}
},
"tslint-config-prettier": {
"version": "1.18.0",
"resolved": "https://verdaccio.lossless.one/tslint-config-prettier/-/tslint-config-prettier-1.18.0.tgz",
"integrity": "sha512-xPw9PgNPLG3iKRxmK7DWr+Ea/SzrvfHtjFt5LBl61gk2UBG/DB9kCXRjv+xyIU1rUtnayLeMUVJBcMX8Z17nDg==",
"dev": true
},
"tsutils": {
"version": "2.29.0",
"resolved": "https://verdaccio.lossless.one/tsutils/-/tsutils-2.29.0.tgz",
"integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==",
"dev": true,
"requires": {
"tslib": "^1.8.1"
}
},
"tty-browserify": { "tty-browserify": {
"version": "0.0.0", "version": "0.0.0",
"resolved": "https://verdaccio.lossless.one/tty-browserify/-/tty-browserify-0.0.0.tgz", "resolved": "https://verdaccio.lossless.one/tty-browserify/-/tty-browserify-0.0.0.tgz",

View File

@ -1,7 +1,7 @@
{ {
"name": "@mojoio/paypal", "name": "@mojoio/paypal",
"private": "false", "private": false,
"version": "1.0.5", "version": "1.0.11",
"description": "mojoio PayPal API abstraction", "description": "mojoio PayPal API abstraction",
"main": "dist_ts/index.js", "main": "dist_ts/index.js",
"typings": "dist_ts/index.d.ts", "typings": "dist_ts/index.d.ts",
@ -15,7 +15,9 @@
"@gitzone/tsbuild": "^2.1.25", "@gitzone/tsbuild": "^2.1.25",
"@gitzone/tstest": "^1.0.44", "@gitzone/tstest": "^1.0.44",
"@pushrocks/qenv": "^4.0.10", "@pushrocks/qenv": "^4.0.10",
"@pushrocks/tapbundle": "^3.2.9" "@pushrocks/tapbundle": "^3.2.9",
"tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0"
}, },
"dependencies": { "dependencies": {
"@pushrocks/smartrequest": "^1.1.47", "@pushrocks/smartrequest": "^1.1.47",

View File

@ -27,7 +27,6 @@ Platform support | [![Supports Windows 10](https://badgen.net/badge/supports%20W
Use TypeScript for best in class intellisense Use TypeScript for best in class intellisense
## Contribution ## Contribution
We are always happy for code contributions. If you are not the code contributing type that is ok. Still, maintaining Open Source repositories takes considerable time and thought. If you like the quality of what we do and our modules are useful to you we would appreciate a little monthly contribution: You can [contribute one time](https://lossless.link/contribute-onetime) or [contribute monthly](https://lossless.link/contribute). :) We are always happy for code contributions. If you are not the code contributing type that is ok. Still, maintaining Open Source repositories takes considerable time and thought. If you like the quality of what we do and our modules are useful to you we would appreciate a little monthly contribution: You can [contribute one time](https://lossless.link/contribute-onetime) or [contribute monthly](https://lossless.link/contribute). :)

View File

@ -5,18 +5,22 @@ import { Qenv } from '@pushrocks/qenv';
const testQenv = new Qenv('./', './.nogit/'); const testQenv = new Qenv('./', './.nogit/');
import * as smarttime from '@pushrocks/smarttime'; import * as smarttime from '@pushrocks/smarttime';
let testPayPalInstance: paypal.PayPal; let testPayPalInstance: paypal.PayPalAccount;
tap.test('should create a valid paypal instance', async () => { tap.test('should create a valid paypal instance', async () => {
testPayPalInstance = new paypal.PayPal({ testPayPalInstance = new paypal.PayPalAccount({
clientId: testQenv.getEnvVarOnDemand('PAYPAL_CLIENT_ID'), clientId: testQenv.getEnvVarOnDemand('PAYPAL_CLIENT_ID'),
clientSecret: testQenv.getEnvVarOnDemand('PAYPAL_CLIENT_SECRET'), clientSecret: testQenv.getEnvVarOnDemand('PAYPAL_CLIENT_SECRET'),
accountOwner: 'sample corp'
}); });
expect(testPayPalInstance).to.be.instanceOf(paypal.PayPal); expect(testPayPalInstance).to.be.instanceOf(paypal.PayPalAccount);
}); });
tap.test('should get an access token', async () => { tap.test('should get an access token', async () => {
const transactions = await paypal.PayPalTransaction.getTransactionFor30days(testPayPalInstance, smarttime.ExtendedDate.fromHyphedDate('2020-05-01').getTime()); const transactions = await testPayPalInstance.getTransactionsFromTo(
smarttime.ExtendedDate.fromHyphedDate('2020-01-01').getTime(),
smarttime.ExtendedDate.fromHyphedDate('2020-08-01').getTime()
);
console.log(transactions); console.log(transactions);
}); });

View File

@ -1,2 +1,2 @@
export * from './paypal.classes.paypal'; export * from './paypal.classes.account';
export * from './paypal.classes.transaction'; export * from './paypal.classes.transaction';

View File

@ -0,0 +1,153 @@
import * as plugins from './paypal.plugins';
import { PayPalTransaction } from './paypal.classes.transaction';
export interface IPayPalOptions {
clientId: string;
clientSecret: string;
accountOwner: string;
}
export class PayPalAccount {
public apiBaseUrl: string = 'https://api.paypal.com';
public options: IPayPalOptions;
private apiToken: string;
private apiTokenExpirationTime: number;
constructor(optionsArg: IPayPalOptions) {
this.options = optionsArg;
}
public async getTransactionsFromTo(fromTimeMillisArg: number, toTimeMillisArg: number) {
let allTransactions: PayPalTransaction[] = [];
// lets note the time from one month before. We need that for accurate transactions pools.
const monthBeforeStartMillis =
fromTimeMillisArg - plugins.smarttime.getMilliSecondsFromUnits({ days: 30 });
do {
const transactions = await PayPalTransaction.getTransactionFor30days(this, fromTimeMillisArg);
allTransactions = allTransactions.concat(transactions);
fromTimeMillisArg =
fromTimeMillisArg + plugins.smarttime.getMilliSecondsFromUnits({ days: 30 });
} while (fromTimeMillisArg < toTimeMillisArg);
const invoiceIds: string[] = [];
const transactionPools: PayPalTransaction[][] = [];
// lets get all invoiceids
allTransactions.forEach((transactionArg) => {
const invoiceId = transactionArg.data.originApiObject.transaction_info.invoice_id;
if (!invoiceIds.includes(invoiceId)) {
invoiceIds.push(invoiceId);
}
});
// lets get all transactions per invoiceId
invoiceIds.forEach((invoiceIdArg) => {
const transactionPool: PayPalTransaction[] = [];
allTransactions.forEach((transactionArg) => {
if (transactionArg.data.originApiObject.transaction_info.invoice_id === invoiceIdArg) {
transactionPool.push(transactionArg);
}
});
transactionPools.push(transactionPool);
});
const previousMonthTransactions = await PayPalTransaction.getTransactionFor30days(
this,
monthBeforeStartMillis
);
transactionPools.forEach((transactionPoolArg) => {
const poolInvoiceId = transactionPoolArg[0].data.originApiObject.transaction_info.invoice_id;
previousMonthTransactions.forEach((transactionArg) => {
if (transactionArg.data.originApiObject.transaction_info.invoice_id === poolInvoiceId) {
transactionPoolArg.push(transactionArg);
}
});
});
let finalTransactions: PayPalTransaction[] = [];
// lets process all transactionPool
transactionPools.forEach((transactionPoolArg) => {
// lets detect foreign transactions
let hasForeignTransactions = false;
transactionPoolArg.forEach((transactionArg) => {
if (transactionArg.data.currency !== 'EUR') {
hasForeignTransactions = true;
}
});
if (hasForeignTransactions && transactionPoolArg.length < 4) {
console.log(
`Pool with invoiceId ${transactionPoolArg[0].data.originApiObject.transaction_info.invoice_id} is not completed yet. Omminiting ${transactionPoolArg.length} transactions as a result.`
);
return;
}
if (hasForeignTransactions && transactionPoolArg.length === 4) {
const negativeNativeTransaction = transactionPoolArg.find(transactionArg => {
return transactionArg.data.amount < 0 && transactionArg.data.currency === 'EUR';
});
const negativeForeignTransaction = transactionPoolArg.find(transactionArg => {
return transactionArg.data.amount < 0 && transactionArg.data.currency !== 'EUR';
});
const positiveNativeTransaction = transactionPoolArg.find(transactionArg => {
return transactionArg.data.amount > 0 && transactionArg.data.currency === 'EUR';
});
const positiveForeignTransaction = transactionPoolArg.find(transactionArg => {
return transactionArg.data.amount > 0 && transactionArg.data.currency !== 'EUR';
});
negativeNativeTransaction.data.name = negativeForeignTransaction.data.name;
negativeNativeTransaction.data.description = negativeForeignTransaction.data.description;
positiveNativeTransaction.data.name = this.options.accountOwner;
positiveNativeTransaction.data.description = 'account balance transfer';
transactionPoolArg = transactionPoolArg.filter(transactionArg => transactionArg.data.currency === 'EUR');
}
if (!hasForeignTransactions && transactionPoolArg.length === 2) {
const positiveNativeTransaction = transactionPoolArg.find(transactionArg => {
return transactionArg.data.amount > 0 && transactionArg.data.currency === 'EUR';
});
positiveNativeTransaction.data.name = this.options.accountOwner;
positiveNativeTransaction.data.description = 'account balance transfer';
}
// pool is ready
finalTransactions = finalTransactions.concat(transactionPoolArg);
});
return finalTransactions;
}
public async request(methodArg: 'GET' | 'POST', routeArg: string, payloadArg: any) {
if (!this.apiToken || this.apiTokenExpirationTime < Date.now()) {
const authHeader = `Basic ${plugins.smartstring.base64.encode(
`${this.options.clientId}:${this.options.clientSecret}`
)}`;
const response = await plugins.smartrequest.request(
`${this.apiBaseUrl}/v1/oauth2/token?grant_type=client_credentials`,
{
method: 'POST',
headers: {
Accept: 'application/json',
'Accept-Language': 'en_US',
Authorization: authHeader,
},
keepAlive: false,
}
);
this.apiToken = response.body.access_token;
this.apiTokenExpirationTime = Date.now() + response.body.expires_in * 1000 - 600000;
}
// we have a token
const response = await plugins.smartrequest.request(`${this.apiBaseUrl}${routeArg}`, {
method: 'GET',
headers: {
Accept: 'application/json',
'Accept-Language': 'en_US',
Authorization: `Bearer ${this.apiToken}`,
},
});
return response.body;
}
}

View File

@ -1,51 +0,0 @@
import * as plugins from './paypal.plugins';
export interface IPayPalOptions {
clientId: string;
clientSecret: string;
}
export class PayPal {
public apiBaseUrl: string = 'https://api.paypal.com'
public options: IPayPalOptions;
private apiToken: string;
private apiTokenExpirationTime: number;
constructor(optionsArg: IPayPalOptions) {
this.options = optionsArg;
}
public async request(methodArg: 'GET' | 'POST', routeArg: string, payloadArg: any) {
if (!this.apiToken || this.apiTokenExpirationTime < Date.now()) {
const authHeader = `Basic ${plugins.smartstring.base64.encode(
`${this.options.clientId}:${this.options.clientSecret}`
)}`;
const response = await plugins.smartrequest.request(
`${this.apiBaseUrl}/v1/oauth2/token?grant_type=client_credentials`,
{
method: 'POST',
headers: {
Accept: 'application/json',
'Accept-Language': 'en_US',
Authorization: authHeader,
},
keepAlive: false,
}
);
this.apiToken = response.body.access_token;
this.apiTokenExpirationTime = Date.now() + response.body.expires_in * 1000 - 600000;
}
// we have a token
const response = await plugins.smartrequest.request(`${this.apiBaseUrl}${routeArg}`, {
method: 'GET',
headers: {
Accept: 'application/json',
'Accept-Language': 'en_US',
Authorization: `Bearer ${this.apiToken}`,
},
});
return response.body;
}
}

View File

@ -1,55 +1,100 @@
import * as plugins from './paypal.plugins'; import * as plugins from './paypal.plugins';
import { PayPal } from './paypal.classes.paypal'; import { PayPalAccount } from './paypal.classes.account';
export interface IPayPalOriginTransactionApiObject { export interface IPayPalOriginTransactionApiObject {
paypal_account_id: string; transaction_info: {
transaction_id: string; paypal_account_id: string;
transaction_event_code: string; transaction_id: string;
transaction_initiation_date: string; transaction_event_code: string;
transaction_updated_date: string; transaction_initiation_date: string;
transaction_amount: { currency_code: string; value: string }; transaction_updated_date: string;
transaction_status: string; transaction_amount: { currency_code: string; value: string };
transaction_subject: string; transaction_status: string;
ending_balance: { currency_code: string; value: string }; transaction_subject: string;
available_balance: { currency_code: string; value: string }; ending_balance: { currency_code: string; value: string };
invoice_id: string; available_balance: { currency_code: string; value: string };
protection_eligibility: string; invoice_id: string;
protection_eligibility: string;
};
cart_info: {
item_details: {
item_code: string;
item_name: string;
item_quantity: string;
item_unit_price: {
currency_code: string;
value: string;
};
item_amount: {
currency_code: string;
value: string;
};
total_item_amount: {
currency_code: string;
value: string;
};
invoice_number: string;
}[];
};
payer_info: {
account_id: string;
email_address: string;
address_status: string;
payer_status: string;
payer_name: {
alternate_full_name: string;
};
country_code: string;
};
} }
export interface IPayPalTransactionOptions { export interface IPayPalTransactionOptions {
id: string; id: string;
originApiObject: IPayPalOriginTransactionApiObject originApiObject: IPayPalOriginTransactionApiObject;
amount: number; amount: number;
name: string;
description: string;
currency: 'USD' | 'EUR'; currency: 'USD' | 'EUR';
timestampIso: string; timestampIso: string;
} }
export class PayPalTransaction { export class PayPalTransaction {
public static async getTransactionFor30days( public static async getTransactionFor30days(
paypalInstanceArg: PayPal, paypalInstanceArg: PayPalAccount,
startPointMillis: number = Date.now() - plugins.smarttime.units.days(30) startPointMillis: number = Date.now() - plugins.smarttime.units.days(30)
) { ) {
const startDate = startPointMillis; const startDate = startPointMillis;
const endDate = startDate + plugins.smarttime.units.days(30); const endDate = startDate + plugins.smarttime.units.days(30);
const startDateIso = plugins.smarttime.ExtendedDate.fromMillis(startDate).toISOString(); const startDateIso = plugins.smarttime.ExtendedDate.fromMillis(startDate).toISOString();
const endDateIso = plugins.smarttime.ExtendedDate.fromMillis(endDate).toISOString(); const endDateIso = plugins.smarttime.ExtendedDate.fromMillis(endDate).toISOString();
console.log(endDateIso); console.log(`getting PayPal transactions from ${startDateIso} + ${endDateIso}`);
console.log(startDateIso);
const response = await paypalInstanceArg.request( const response = await paypalInstanceArg.request(
'GET', 'GET',
`/v1/reporting/transactions?start_date=${startDateIso}&end_date=${endDateIso}`, `/v1/reporting/transactions?start_date=${startDateIso}&end_date=${endDateIso}&fields=all`,
{} {}
); );
const returnTransactions: PayPalTransaction[] = [] const returnTransactions: PayPalTransaction[] = [];
for (const transactionDetail of response.transaction_details) { for (const transactionDetail of response.transaction_details) {
const apiObject: IPayPalOriginTransactionApiObject = transactionDetail.transaction_info; const apiObject: IPayPalOriginTransactionApiObject = transactionDetail;
const paypalTransaction = new PayPalTransaction({ const paypalTransaction = new PayPalTransaction({
originApiObject: apiObject, originApiObject: apiObject,
id: apiObject.transaction_id, id: apiObject.transaction_info.transaction_id,
amount: parseFloat(apiObject.transaction_amount.value), amount: parseFloat(apiObject.transaction_info.transaction_amount.value),
currency: apiObject.transaction_amount.currency_code as 'EUR' | 'USD', currency: apiObject.transaction_info.transaction_amount.currency_code as 'EUR' | 'USD',
timestampIso: apiObject.transaction_initiation_date timestampIso: apiObject.transaction_info.transaction_initiation_date,
name: `${apiObject.payer_info.payer_name.alternate_full_name} (${apiObject.payer_info.email_address})`,
description: `${apiObject.cart_info?.item_details?.length} items: ${apiObject.cart_info?.item_details?.map(itemArg => {
return `${itemArg.item_name}`;
}).reduce((accumulatorArg, currentValue) => {
let returnString = '';
if (accumulatorArg) {
returnString = accumulatorArg + ', ' + currentValue;
} else {
returnString = currentValue;
}
return returnString;
})}`,
}); });
returnTransactions.push(paypalTransaction); returnTransactions.push(paypalTransaction);
} }