Skip to content

Commit

Permalink
Merge 9dca7d4 into 10f29fa
Browse files Browse the repository at this point in the history
  • Loading branch information
ruscon committed Sep 15, 2022
2 parents 10f29fa + 9dca7d4 commit cae7467
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 34 deletions.
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@goparrot/square-connect-plus",
"description": "Extends the official Square Node.js SDK library with additional functionality",
"version": "1.4.0",
"version": "1.5.0",
"author": "Coroliov Oleg",
"license": "MIT",
"private": false,
Expand Down Expand Up @@ -79,6 +79,7 @@
"@types/chai": "^4.3.0",
"@types/chai-as-promised": "^7.1.5",
"@types/lodash.camelcase": "^4.3.6",
"@types/lodash.capitalize": "^4.2.7",
"@types/lodash.snakecase": "^4.1.6",
"@types/mocha": "^9.1.0",
"@types/node": "^16.11.24",
Expand Down Expand Up @@ -113,5 +114,8 @@
"ts-node": "^10.5.0",
"typescript": "^4.5.5",
"uuid": "^8.3.2"
},
"dependencies": {
"lodash.capitalize": "^4.2.1"
}
}
92 changes: 59 additions & 33 deletions src/client/SquareClient.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { ApiResponse, Error as SquareError } from 'square';
import {
import capitalize from 'lodash.capitalize';
import type {
ApiResponse,
Error as SquareError,
ApplePayApi,
CardsApi,
CatalogApi,
CheckoutApi,
Client,
DEFAULT_CONFIGURATION,
EmployeesApi,
GiftCardActivitiesApi,
GiftCardsApi,
Expand All @@ -22,7 +22,9 @@ import {
RefundsApi,
TransactionsApi,
} from 'square';
import { Client, DEFAULT_CONFIGURATION } from 'square';
import { v4 as uuidv4 } from 'uuid';
import type { BaseApi } from 'square/dist/api/baseApi';
import { retryableErrorCodes } from '../constants';
import { SquareApiException } from '../exception';
import type { ISquareClientConfig, ISquareClientDefaultConfig, ISquareClientMergedConfig } from '../interface';
Expand All @@ -31,6 +33,10 @@ import { NullLogger } from '../logger';
import { exponentialDelay, mergeDeepProps, sleep } from '../utils';
import { CustomerClientApi } from './CustomerClientApi';

type ApiName = {
[key in keyof Client]: Client[key] extends BaseApi ? key : never;
}[keyof Client];

export class SquareClient {
#client: Client;
readonly #mergedConfig: ISquareClientMergedConfig;
Expand All @@ -40,12 +46,15 @@ export class SquareClient {
retryDelay: exponentialDelay,
},
configuration: DEFAULT_CONFIGURATION,
logContext: {
merchantId: 'unknown',
},
};

constructor(private readonly accessToken: string, config: ISquareClientConfig = {}) {
const { logger, ...configWithoutLogger } = config;
this.#mergedConfig = mergeDeepProps(this.#defaultConfig, configWithoutLogger);
this.#mergedConfig.logger = config.logger;
this.#mergedConfig.logger = logger;
}

/**
Expand All @@ -69,25 +78,25 @@ export class SquareClient {
}

getApplePayApi(retryableMethods: string[] = []): ApplePayApi {
return this.proxy(new ApplePayApi(this.getOriginClient()), retryableMethods);
return this.proxy('applePayApi', retryableMethods);
}

getCatalogApi(
retryableMethods: string[] = ['batchRetrieveCatalogObjects', 'catalogInfo', 'listCatalog', 'retrieveCatalogObject', 'searchCatalogObjects'],
): CatalogApi {
return this.proxy(new CatalogApi(this.getOriginClient()), retryableMethods);
return this.proxy('catalogApi', retryableMethods);
}

getCheckoutApi(retryableMethods: string[] = []): CheckoutApi {
return this.proxy(new CheckoutApi(this.getOriginClient()), retryableMethods);
return this.proxy('checkoutApi', retryableMethods);
}

getCustomersApi(retryableMethods: string[] = ['listCustomers', 'retrieveCustomer', 'searchCustomers', 'deleteCustomerCard']): CustomerClientApi {
return this.proxy(new CustomerClientApi(this.getOriginClient()), retryableMethods);
return this.proxyWithInstance('customersApi', new CustomerClientApi(this.getOriginClient()), retryableMethods);
}

getEmployeesApi(retryableMethods: string[] = ['listEmployees', 'retrieveEmployee']): EmployeesApi {
return this.proxy(new EmployeesApi(this.getOriginClient()), retryableMethods);
return this.proxy('employeesApi', retryableMethods);
}

getLoyaltyApi(
Expand All @@ -99,7 +108,7 @@ export class SquareClient {
'retrieveLoyaltyProgram',
],
): LoyaltyApi {
return this.proxy(new LoyaltyApi(this.getOriginClient()), retryableMethods);
return this.proxy('loyaltyApi', retryableMethods);
}

getInventoryApi(
Expand All @@ -112,7 +121,7 @@ export class SquareClient {
'retrieveInventoryPhysicalCount',
],
): InventoryApi {
return this.proxy(new InventoryApi(this.getOriginClient()), retryableMethods);
return this.proxy('inventoryApi', retryableMethods);
}

getLaborApi(
Expand All @@ -126,31 +135,31 @@ export class SquareClient {
'searchShifts',
],
): LaborApi {
return this.proxy(new LaborApi(this.getOriginClient()), retryableMethods);
return this.proxy('laborApi', retryableMethods);
}

getLocationsApi(retryableMethods: string[] = ['listLocations']): LocationsApi {
return this.proxy(new LocationsApi(this.getOriginClient()), retryableMethods);
return this.proxy('locationsApi', retryableMethods);
}

getMerchantsApi(retryableMethods: string[] = ['retrieveMerchant', 'listMerchants']): MerchantsApi {
return this.proxy(new MerchantsApi(this.getOriginClient()), retryableMethods);
return this.proxy('merchantsApi', retryableMethods);
}

getMobileAuthorizationApi(retryableMethods: string[] = []): MobileAuthorizationApi {
return this.proxy(new MobileAuthorizationApi(this.getOriginClient()), retryableMethods);
return this.proxy('mobileAuthorizationApi', retryableMethods);
}

getOAuthApi(retryableMethods: string[] = []): OAuthApi {
return this.proxy(new OAuthApi(this.getOriginClient()), retryableMethods);
return this.proxy('oAuthApi', retryableMethods);
}

getOrdersApi(retryableMethods: string[] = ['batchRetrieveOrders', 'searchOrders', 'createOrder', 'payOrder', 'calculateOrder']): OrdersApi {
return this.proxy(new OrdersApi(this.getOriginClient()), retryableMethods);
return this.proxy('ordersApi', retryableMethods);
}

getPaymentsApi(retryableMethods: string[] = ['getPayment', 'listPayments', 'createPayment', 'cancelPayment']): PaymentsApi {
return this.proxy(new PaymentsApi(this.getOriginClient()), retryableMethods);
return this.proxy('paymentsApi', retryableMethods);
}

getGiftCardsApi(
Expand All @@ -164,27 +173,27 @@ export class SquareClient {
'retrieveGiftCard',
],
): GiftCardsApi {
return this.proxy(new GiftCardsApi(this.getOriginClient()), retryableMethods);
return this.proxy('giftCardsApi', retryableMethods);
}

getGiftCardActivitiesApi(retryableMethods: string[] = ['listGiftCardActivities', 'createGiftCardActivity']): GiftCardActivitiesApi {
return this.proxy(new GiftCardActivitiesApi(this.getOriginClient()), retryableMethods);
return this.proxy('giftCardActivitiesApi', retryableMethods);
}

getRefundsApi(retryableMethods: string[] = ['getPaymentRefund', 'listPaymentRefunds', 'refundPayment']): RefundsApi {
return this.proxy(new RefundsApi(this.getOriginClient()), retryableMethods);
return this.proxy('refundsApi', retryableMethods);
}

getTransactionsApi(retryableMethods: string[] = ['listRefunds', 'listTransactions', 'retrieveTransaction']): TransactionsApi {
return this.proxy(new TransactionsApi(this.getOriginClient()), retryableMethods);
return this.proxy('transactionsApi', retryableMethods);
}

getCardsApi(retryableMethods: string[] = ['listCards', 'retrieveCard', 'disableCard']): CardsApi {
return this.proxy(new CardsApi(this.getOriginClient()), retryableMethods);
return this.proxy('cardsApi', retryableMethods);
}

getInvoiceApi(retryableMethods: string[] = ['listInvoices', 'searchInvoices', 'getInvoice']): InvoicesApi {
return this.proxy(new InvoicesApi(this.getOriginClient()), retryableMethods);
return this.proxy('invoicesApi', retryableMethods);
}

private createOriginClient(accessToken: string, { configuration }: Partial<ISquareClientConfig>): Client {
Expand All @@ -198,16 +207,16 @@ export class SquareClient {
/**
* @throws SquareApiException
*/
private proxy<T extends object>(api: T, retryableMethods: string[]): T {
private proxyWithInstance<T extends ApiName, A extends Client[T]>(apiName: T, api: A, retryableMethods: string[]): A {
const stackError: string = new Error().stack?.slice(6) || '';

const handler: ProxyHandler<T> = {
get: (target: T, apiMethodName: string): unknown => {
const handler: ProxyHandler<A> = {
get: (target: A, apiMethodName: string): unknown => {
return async (...args: unknown[]): Promise<ApiResponse<T>> => {
const requestFn: (...arg: unknown[]) => Promise<ApiResponse<T>> = target[apiMethodName].bind(target, ...args);

try {
return await this.makeRetryable<ApiResponse<T>>(api, requestFn, apiMethodName, retryableMethods);
return await this.makeRetryable<ApiResponse<T>>(apiName, requestFn, apiMethodName, retryableMethods);
} catch (err) {
if (err instanceof Error) {
err.stack += stackError;
Expand All @@ -219,12 +228,27 @@ export class SquareClient {
},
};

return new Proxy<T>(api, handler);
return new Proxy<A>(api, handler);
}

/**
* @throws SquareApiException
*/
private proxy<T extends ApiName>(apiName: T, retryableMethods: string[]): Client[T] {
const api = this.getOriginClient()[apiName];

return this.proxyWithInstance(apiName, api, retryableMethods);
}

private async makeRetryable<T>(api, promiseFn: (...arg: unknown[]) => Promise<T>, apiMethodName: string, retryableMethods: string[]): Promise<T> {
private async makeRetryable<T>(
apiName: ApiName,
promiseFn: (...arg: unknown[]) => Promise<T>,
apiMethodName: string,
retryableMethods: string[],
): Promise<T> {
let retries: number = 0;
const { maxRetries, retryCondition = this.retryCondition } = this.#mergedConfig.retry;
const { logContext } = this.#mergedConfig;
const logger = this.getLogger();

async function retry(): Promise<T> {
Expand All @@ -238,9 +262,10 @@ export class SquareClient {

if (retryableMethods.includes(apiMethodName) && (await retryCondition(squareException, maxRetries, retries))) {
logger.info('Square api retry', {
...logContext,
retries,
maxRetries,
apiName: api.constructor.name,
apiName: capitalize(apiName),
apiMethodName,
startedAt,
finishedAt,
Expand All @@ -260,7 +285,8 @@ export class SquareClient {
const finishedAt = Date.now();
const execTime = finishedAt - startedAt;
logger.info(`Square api request: ${apiMethodName} executed in ${execTime}ms`, {
apiName: api.constructor.name,
...logContext,
apiName: capitalize(apiName),
apiMethodName,
startedAt,
finishedAt,
Expand Down
7 changes: 7 additions & 0 deletions src/interface/ISquareClientConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,15 @@ import type { Configuration } from 'square';
import type { ILogger } from '../logger';
import type { IRetriesOptions } from './IRetriesOptions';

export type LogContext = {
/** square merchant id */
merchantId?: string;
[key: string]: any;
};

export interface ISquareClientConfig {
retry?: Partial<IRetriesOptions>;
configuration?: Partial<Omit<Configuration, 'accessToken'>>;
logger?: ILogger;
logContext?: LogContext;
}
7 changes: 7 additions & 0 deletions test/unit/client/SquareClient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ describe('SquareClient (unit)', (): void => {
environment: Environment.Sandbox,
},
logger: console,
logContext: {
someKey: 'someValue',
merchantId: 'unknown',
},
};

describe('#constructor', (): void => {
Expand Down Expand Up @@ -73,6 +77,9 @@ describe('SquareClient (unit)', (): void => {
...DEFAULT_CONFIGURATION,
},
logger: undefined,
logContext: {
merchantId: 'unknown',
},
});
});

Expand Down
7 changes: 7 additions & 0 deletions test/unit/client/SquareClientFactory.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ describe('SquareClientFactory (unit)', (): void => {
environment: Environment.Sandbox,
},
logger: console,
logContext: {
someKey: 'someValue',
merchantId: 'unknown',
},
};

describe('#create', (): void => {
Expand Down Expand Up @@ -67,6 +71,9 @@ describe('SquareClientFactory (unit)', (): void => {
},
configuration: DEFAULT_CONFIGURATION,
logger: undefined,
logContext: {
merchantId: 'unknown',
},
});
});

Expand Down

0 comments on commit cae7467

Please sign in to comment.