Pw 6826/storedvalue api (#918)

* merge modifications service with checkout service and add cardDetails endpoint to checkout service

* add classic integration/payment API types and service to library

* add unit test for cardDetails endpoint

* fix unit tests for binlookup

* PW-6663: Do not run model generation on every PR

* remove inconsistent e2e/unit hybrid test for binlookup

* add storedValue API

* upgrade linter packages and fix linting issues in repo

* add storedValue API to Readme

Co-authored-by: Michael Paul <michael@michaelpaul.com.br>
This commit is contained in:
Wouter Boereboom
2022-07-12 10:44:19 +02:00
committed by GitHub
parent 1e60855be9
commit aba0c19859
18 changed files with 1241 additions and 1025 deletions

View File

@@ -23,6 +23,7 @@ The Library supports all APIs under the following services:
* [Notification Configuration API](https://docs.adyen.com/api-explorer/#/NotificationConfigurationService/v6/overview) Current supported version: **v6**
* [Local/Cloud-based Terminal API](https://docs.adyen.com/point-of-sale/terminal-api-reference): Our point-of-sale integration.
* [BIN lookup API](https://docs.adyen.com/api-explorer/#/BinLookup/v50/overview): The BIN Lookup API provides endpoints for retrieving information based on a given BIN. Current supported version: **v50**
* [Stored Value API](https://docs.adyen.com/payment-methods/gift-cards/stored-value-api): Manage both online and point-of-sale gift cards and other stored-value cards. Current supported version: **v46**
In addition it supports the following type collections:

View File

@@ -37,12 +37,12 @@
"devDependencies": {
"@types/jest": "27.5.0",
"@types/nock": "11.1.0",
"@typescript-eslint/eslint-plugin": "4.33.0",
"@typescript-eslint/parser": "4.33.0",
"@typescript-eslint/eslint-plugin": "5.17.0",
"@typescript-eslint/parser": "5.17.0",
"acorn": "^8.0.1",
"coveralls": "3.1.1",
"dotenv": "^16.0.0",
"eslint": "7.32.0",
"eslint": "8.16.0",
"jest": "^27.0.6",
"jest-ts-auto-mock": "^2.0.0",
"kind-of": "^6.0.3",

View File

@@ -45,6 +45,7 @@ export const createClient = (apiKey = process.env.ADYEN_API_KEY): Client => {
config.marketPayEndpoint = Client.MARKETPAY_ENDPOINT_TEST;
config.apiKey = apiKey;
config.paymentEndpoint = Client.PAYMENT_API_ENDPOINT_TEST;
config.storedValueEndpoint = Client.STOREDVALUE_API_ENDPOINT_TEST;
return new Client({ config });
};

View File

@@ -0,0 +1,267 @@
import nock from "nock";
import Client from "../client";
import {createClient} from "../__mocks__/base";
import StoredValue from "../services/storedValue";
import { StoredValueIssueRequest,
StoredValueIssueResponse,
StoredValueStatusChangeRequest,
StoredValueStatusChangeResponse,
StoredValueLoadRequest,
StoredValueLoadResponse,
StoredValueBalanceCheckRequest,
StoredValueBalanceCheckResponse,
StoredValueBalanceMergeRequest,
StoredValueBalanceMergeResponse,
StoredValueVoidRequest,
StoredValueVoidResponse
} from "../typings/storedValue/models";
let client: Client;
let storedValue: StoredValue;
let scope: nock.Scope;
beforeEach((): void => {
if (!nock.isActive()) {
nock.activate();
}
client = createClient();
scope = nock(`${client.config.storedValueEndpoint}/${Client.STOREDVALUE_API_VERSION}`);
storedValue = new StoredValue(client);
});
afterEach(() => {
nock.cleanAll();
});
describe("StoredValue", (): void => {
test("Should issue Givex card", async (): Promise<void> => {
scope.post("/issue")
.reply(200, {
"currentBalance": {
"currency": "EUR",
"value": 1000
},
"pspReference": "851564651069192J",
"resultCode": "Success",
"paymentMethod": {
"number": "7219627091701347",
"securityCode": "0140",
"type": "givex"
}
});
const issueRequest: StoredValueIssueRequest = {
"merchantAccount": "YOUR_MERCHANT_ACCOUNT",
"store": "YOUR_STORE_ID",
"paymentMethod": {
"type": "givex"
},
"amount": {
"currency": "EUR",
"value": 1000
},
"reference": "YOUR_REFERENCE"
};
const issueResponse: StoredValueIssueResponse = await storedValue.issue(issueRequest);
expect(issueResponse.pspReference).toEqual("851564651069192J");
});
test("Should issue virtual Fiserv card", async (): Promise<void> => {
scope.post("/issue")
.reply(200, {
"currentBalance": {
"currency": "EUR",
"value": 1000
},
"pspReference": "851564651069192J",
"resultCode": "Success",
"paymentMethod": {
"number": "7219627091701347",
"securityCode": "0140",
"type": "givex"
}
});
const issueRequest: StoredValueIssueRequest = {
"merchantAccount": "YOUR_MERCHANT_ACCOUNT",
"store": "YOUR_STORE_ID",
"paymentMethod": {
"type": "valuelink"
},
// "giftCardPromoCode": "1324",
"reference": "YOUR_REFERENCE"
};
const issueResponse: StoredValueIssueResponse = await storedValue.issue(issueRequest);
expect(issueResponse.pspReference).toEqual("851564651069192J");
});
test("Should activate card", async (): Promise<void> => {
scope.post("/changeStatus")
.reply(200, {
"currentBalance": {
"currency": "USD",
"value": 1000
},
"pspReference": "851564652149588K",
"resultCode": "Success"
});
const statusRequest: StoredValueStatusChangeRequest = {
"status": StoredValueStatusChangeRequest.StatusEnum.Active,
"amount": {
"currency": "USD",
"value": 1000
},
"merchantAccount": "YOUR_MERCHANT_ACCOUNT",
"store":"YOUR_STORE_ID",
"paymentMethod": {
"type": "svs",
"number": "6006491286999921374",
"securityCode": "1111"
},
"reference": "YOUR_REFERENCE"
};
const changeStatusResponse: StoredValueStatusChangeResponse = await storedValue.changeStatus(statusRequest);
expect(changeStatusResponse.pspReference).toEqual("851564652149588K");
});
test("Should deactivate card", async (): Promise<void> => {
scope.post("/changeStatus")
.reply(200, {
"currentBalance": {
"currency": "USD",
"value": 1000
},
"pspReference": "851564652149588K",
"resultCode": "Success"
});
const statusRequest: StoredValueStatusChangeRequest = {
"status": StoredValueStatusChangeRequest.StatusEnum.Inactive,
"merchantAccount": "YOUR_MERCHANT_ACCOUNT",
"store":"YOUR_STORE_ID",
"paymentMethod": {
"type": "givex",
},
"recurringDetailReference": "7219627091701347",
"shopperReference": "YOUR_UNIQUE_SHOPPER_ID_P3fW3k9D2tvXFu6l",
"shopperInteraction": StoredValueStatusChangeRequest.ShopperInteractionEnum.Ecommerce,
"reference": "YOUR_REFERENCE"
};
const changeStatusResponse: StoredValueStatusChangeResponse = await storedValue.changeStatus(statusRequest);
expect(changeStatusResponse.pspReference).toEqual("851564652149588K");
});
test("Should load funds to card", async (): Promise<void> => {
scope.post("/load")
.reply(200, {
"currentBalance": {
"currency": "USD",
"value": 30000
},
"pspReference": "851564654294247B",
"resultCode": "Success"
});
const loadRequest: StoredValueLoadRequest = {
"amount": {
"currency": "USD",
"value": 2000
},
"loadType": StoredValueLoadRequest.LoadTypeEnum.MerchandiseReturn,
"merchantAccount": "YOUR_MERCHANT_ACCOUNT",
"store":"YOUR_STORE_ID",
"paymentMethod": {
"type": "svs",
"number": "6006491286999921374",
"securityCode": "1111"
},
"reference": "YOUR_REFERENCE"
};
const loadResponse: StoredValueLoadResponse = await storedValue.load(loadRequest);
expect(loadResponse.pspReference).toEqual("851564654294247B");
});
test("Should check remaining balance of card", async (): Promise<void> => {
scope.post("/checkBalance")
.reply(200, {
"currentBalance": {
"currency": "EUR",
"value": 5600
},
"pspReference": "881564657480267D",
"resultCode": "Success"
});
const checkBalanceRequest: StoredValueBalanceCheckRequest = {
"merchantAccount": "YOUR_MERCHANT_ACCOUNT",
"store":"YOUR_STORE_ID",
"paymentMethod": {
"type": "svs",
"number": "603628672882001915092",
"securityCode": "5754"
},
"reference": "YOUR_REFERENCE"
};
const checkBalanceResponse: StoredValueBalanceCheckResponse = await storedValue.checkBalance(checkBalanceRequest);
expect(checkBalanceResponse.pspReference).toEqual("881564657480267D");
});
test("Should transfer full value from one card to another", async (): Promise<void> => {
scope.post("/mergeBalance")
.reply(200, {
"currentBalance": {
"currency": "EUR",
"value": 5600
},
"pspReference": "881564657480267D",
"resultCode": "Success"
});
const mergeBalanceRequest: StoredValueBalanceMergeRequest = {
"merchantAccount": "YOUR_MERCHANT_ACCOUNT",
"store":"YOUR_STORE_ID",
"sourcePaymentMethod": {
"number": "7777182708544835",
"securityCode": "2329"
},
"paymentMethod": {
"type": "valuelink",
"number": "8888182708544836",
"securityCode": "2330"
},
"reference": "YOUR_REFERENCE"
};
const mergeBalanceResponse: StoredValueBalanceMergeResponse = await storedValue.mergebalance(mergeBalanceRequest);
expect(mergeBalanceResponse.pspReference).toEqual("881564657480267D");
});
test("Should undo transaction on card", async (): Promise<void> => {
scope.post("/voidTransaction")
.reply(200, {
"currentBalance": {
"currency": "EUR",
"value": 120000
},
"pspReference": "851564673300692A",
"resultCode": "Success"
});
const voidTransactionRequest: StoredValueVoidRequest = {
"merchantAccount": "YOUR_MERCHANT_ACCOUNT",
"originalReference": "851564654294247B",
"reference": "YOUR_REFERENCE"
};
const voidTransactionResponse: StoredValueVoidResponse = await storedValue.voidTransaction(voidTransactionRequest);
expect(voidTransactionResponse.pspReference).toEqual("851564673300692A");
});
});

View File

@@ -94,9 +94,9 @@ describe("Terminal Cloud API", (): void => {
const terminalAPIRefundRequest = createTerminalAPIRefundRequest(pOITransactionId);
const id = Math.floor(Math.random() * Math.floor(10000000)).toString();
terminalAPIRefundRequest.SaleToPOIRequest.MessageHeader.ServiceID = id;
terminalAPIRefundRequest.SaleToPOIRequest.ReversalRequest!.SaleData!.SaleToAcquirerData!.currency = 'EUR';
terminalAPIRefundRequest.SaleToPOIRequest.ReversalRequest!.SaleData!.SaleToAcquirerData!.currency = "EUR";
const terminalAPIRefundResponse = await terminalCloudAPI.sync(terminalAPIRefundRequest);
expect(terminalAPIRefundResponse.SaleToPOIResponse?.ReversalResponse?.Response.Result).toBe('Success');
expect(terminalAPIRefundResponse.SaleToPOIResponse?.ReversalResponse?.Response.Result).toBe("Success");
}, 20000);
});

View File

@@ -61,6 +61,7 @@ class Client {
public static MARKETPAY_NOTIFICATION_API_VERSION = "v5";
public static MARKETPAY_NOTIFICATION_CONFIGURATION_API_VERSION = "v6";
public static PAYMENT_API_VERSION = "v68";
public static STOREDVALUE_API_VERSION = "v46";
public static LIB_NAME = "adyen-node-api-library";
public static LIB_VERSION: string = version;
public static CHECKOUT_ENDPOINT_TEST = "https://checkout-test.adyen.com/checkout";
@@ -72,6 +73,8 @@ class Client {
public static ENDPOINT_PROTOCOL = "https://";
public static PAYMENT_API_ENDPOINT_TEST = "https://pal-test.adyen.com/pal/servlet/Payment";
public static PAYMENT_API_ENDPOINT_LIVE = "https://pal-live.adyen.com/pal/servlet/Payment";
public static STOREDVALUE_API_ENDPOINT_TEST = "https://pal-test.adyen.com/pal/servlet/StoredValue";
public static STOREDVALUE_API_ENDPOINT_LIVE = "https://pal-live.adyen.com/pal/servlet/StoredValue";
private _httpClient!: ClientInterface;
@@ -112,12 +115,14 @@ class Client {
this.config.checkoutEndpoint = Client.CHECKOUT_ENDPOINT_TEST;
this.config.terminalApiCloudEndpoint = Client.TERMINAL_API_ENDPOINT_TEST;
this.config.paymentEndpoint = Client.PAYMENT_API_ENDPOINT_TEST;
this.config.storedValueEndpoint = Client.STOREDVALUE_API_ENDPOINT_TEST;
} else if (environment === "LIVE") {
this.config.endpoint = Client.ENDPOINT_LIVE;
this.config.marketPayEndpoint = Client.MARKETPAY_ENDPOINT_LIVE;
this.config.hppEndpoint = Client.HPP_LIVE;
this.config.terminalApiCloudEndpoint = Client.TERMINAL_API_ENDPOINT_LIVE;
this.config.paymentEndpoint = Client.PAYMENT_API_ENDPOINT_LIVE;
this.config.storedValueEndpoint = Client.STOREDVALUE_API_ENDPOINT_LIVE;
if (liveEndpointUrlPrefix) {
this.config.endpoint =
`${Client.ENDPOINT_PROTOCOL}${liveEndpointUrlPrefix}${Client.ENDPOINT_LIVE_SUFFIX}`;

View File

@@ -35,6 +35,7 @@ interface ConfigConstructor {
terminalApiCloudEndpoint?: string;
terminalApiLocalEndpoint?: string;
paymentEndpoint?: string;
storedValueEndpoint?: string;
}
class Config {
@@ -60,6 +61,7 @@ class Config {
public terminalApiLocalEndpoint?: string;
public paymentEndpoint?: string;
public storedValueEndpoint?: string;
public constructor(options: ConfigConstructor = {}) {
if (options.username) this.username = options.username;
@@ -80,6 +82,7 @@ class Config {
if (options.terminalApiCloudEndpoint) this.terminalApiCloudEndpoint = options.terminalApiCloudEndpoint;
if (options.terminalApiLocalEndpoint) this.terminalApiLocalEndpoint = options.terminalApiLocalEndpoint;
if (options.paymentEndpoint) this.paymentEndpoint = options.paymentEndpoint;
if (options.storedValueEndpoint) this.storedValueEndpoint = options.storedValueEndpoint;
}
public set checkoutEndpoint(checkoutEndpoint: string | undefined) {

View File

@@ -6,3 +6,4 @@ export { default as Recurring } from "./recurring";
export { default as BinLookup } from "./binLookup";
export { default as Payout } from "./payout";
export { default as Platforms } from "./platforms";
export { default as StoredValue} from "./storedValue";

View File

@@ -156,7 +156,7 @@ class Platforms extends Service {
createRequest = <T extends PlatformsTypes, U, V>(service: T) => {
return (request: U): Promise<V> => getJsonResponse<U, V>(service, request);
}
};
public get Account(): {
getAccountHolder: (request: GetAccountHolderRequest) => Promise<GetAccountHolderResponse>;

View File

@@ -39,7 +39,7 @@ class Recurring extends Service {
private readonly _listRecurringDetails: ListRecurringDetails;
private readonly _disable: Disable;
private readonly _scheduleAccountUpdater: ScheduleAccountUpdater;
private readonly _notifyShopper: NotifyShopper
private readonly _notifyShopper: NotifyShopper;
public constructor(client: Client) {
super(client);

View File

@@ -0,0 +1,14 @@
import Client from "../../../client";
import Service from "../../../service";
import Resource from "../../resource";
class ChangeStatus extends Resource {
public constructor(service: Service) {
super(
service,
`${service.client.config.storedValueEndpoint}/${Client.STOREDVALUE_API_VERSION}/changeStatus`
);
}
}
export default ChangeStatus;

View File

@@ -0,0 +1,14 @@
import Client from "../../../client";
import Service from "../../../service";
import Resource from "../../resource";
class CheckBalance extends Resource {
public constructor(service: Service) {
super(
service,
`${service.client.config.storedValueEndpoint}/${Client.STOREDVALUE_API_VERSION}/checkBalance`
);
}
}
export default CheckBalance;

View File

@@ -0,0 +1,14 @@
import Client from "../../../client";
import Service from "../../../service";
import Resource from "../../resource";
class Issue extends Resource {
public constructor(service: Service) {
super(
service,
`${service.client.config.storedValueEndpoint}/${Client.STOREDVALUE_API_VERSION}/issue`
);
}
}
export default Issue;

View File

@@ -0,0 +1,14 @@
import Client from "../../../client";
import Service from "../../../service";
import Resource from "../../resource";
class Load extends Resource {
public constructor(service: Service) {
super(
service,
`${service.client.config.storedValueEndpoint}/${Client.STOREDVALUE_API_VERSION}/load`
);
}
}
export default Load;

View File

@@ -0,0 +1,14 @@
import Client from "../../../client";
import Service from "../../../service";
import Resource from "../../resource";
class MergeBalance extends Resource {
public constructor(service: Service) {
super(
service,
`${service.client.config.storedValueEndpoint}/${Client.STOREDVALUE_API_VERSION}/mergeBalance`
);
}
}
export default MergeBalance;

View File

@@ -0,0 +1,14 @@
import Client from "../../../client";
import Service from "../../../service";
import Resource from "../../resource";
class VoidTransaction extends Resource {
public constructor(service: Service) {
super(
service,
`${service.client.config.storedValueEndpoint}/${Client.STOREDVALUE_API_VERSION}/voidTransaction`
);
}
}
export default VoidTransaction;

View File

@@ -0,0 +1,88 @@
import Client from "../client";
import getJsonResponse from "../helpers/getJsonResponse";
import Service from "../service";
import {
StoredValueBalanceCheckRequest,
StoredValueBalanceCheckResponse,
StoredValueBalanceMergeRequest,
StoredValueBalanceMergeResponse,
StoredValueIssueRequest,
StoredValueIssueResponse,
StoredValueLoadRequest,
StoredValueLoadResponse,
StoredValueStatusChangeRequest,
StoredValueStatusChangeResponse,
StoredValueVoidRequest,
StoredValueVoidResponse,
} from "../typings/storedValue/models";
import ChangeStatus from "./resource/storedValue/changeStatus";
import Issue from "./resource/storedValue/issue";
import Load from "./resource/storedValue/load";
import CheckBalance from "./resource/storedValue/checkBalance";
import MergeBalance from "./resource/storedValue/mergeBalance";
import VoidTransaction from "./resource/storedValue/voidTransaction";
class StoredValue extends Service {
private readonly _issue: Issue;
private readonly _changeStatus: ChangeStatus;
private readonly _load: Load;
private readonly _checkBalance: CheckBalance;
private readonly _mergebalance: MergeBalance;
private readonly _voidTransaction: VoidTransaction;
public constructor(client: Client) {
super(client);
this._issue = new Issue(this);
this._changeStatus = new ChangeStatus(this);
this._load = new Load(this);
this._checkBalance = new CheckBalance(this);
this._mergebalance = new MergeBalance(this);
this._voidTransaction = new VoidTransaction(this);
}
public issue(request: StoredValueIssueRequest): Promise<StoredValueIssueResponse> {
return getJsonResponse<StoredValueIssueRequest, StoredValueIssueResponse>(
this._issue,
request,
);
}
public changeStatus(request: StoredValueStatusChangeRequest): Promise<StoredValueStatusChangeResponse> {
return getJsonResponse<StoredValueStatusChangeRequest, StoredValueStatusChangeResponse>(
this._changeStatus,
request,
);
}
public load(request: StoredValueLoadRequest): Promise<StoredValueLoadResponse> {
return getJsonResponse<StoredValueLoadRequest, StoredValueLoadResponse>(
this._load,
request,
);
}
public checkBalance(request: StoredValueBalanceCheckRequest): Promise<StoredValueBalanceCheckResponse> {
return getJsonResponse<StoredValueBalanceCheckRequest, StoredValueBalanceCheckResponse>(
this._checkBalance,
request,
);
}
public mergebalance(request: StoredValueBalanceMergeRequest): Promise<StoredValueBalanceMergeResponse> {
return getJsonResponse<StoredValueBalanceMergeRequest, StoredValueBalanceMergeResponse>(
this._mergebalance,
request,
);
}
public voidTransaction(request: StoredValueVoidRequest): Promise<StoredValueVoidResponse> {
return getJsonResponse<StoredValueVoidRequest, StoredValueVoidResponse>(
this._voidTransaction,
request,
);
}
}
export default StoredValue;

1802
yarn.lock

File diff suppressed because it is too large Load Diff