# CardX Plug'n Pay Library

Plug'n Pay Gateway Functions

## Installation

``` bash
npm install @cardx/plugnpay-lib
```

## Usage

``` javascript
import Gateway from "@cardx/plugnpay-lib";
```

or

``` javascript
const Gateway = require("@cardx/plugnpay-lib");
```

### Initializing the Gateway

The gateway can be initialized using one of two methods:

1. Environment variables from a `.env` or CloudFormation template
2. Passed directly to the constructor (supersedes environment variables)

#### 1. Environment Variables

``` bash
PNP_GATEWAY_ACCOUNT=
PNP_GATEWAY_KEY=
PNP_GATEWAY_KEY_NAME=
PNP_GATEWAY_PUBLISHER_PASSWORD=
PNP_GATEWAY_SERVER=test.api.paywithcardx.com
PNP_GATEWAY_SESSION=
```

``` javascript
const gateway = new Gateway();
```

#### 2. Passing Parameters to Constructor

``` javascript
const gateway = new Gateway({
  account,
  compatibility: true, // Since 1.17.0
  key,
  keyName,
  log, // optional @cardx/logger instance if you want logging
  password,
  server: "test.api.paywithcardx.com",
  session, // alternative to 'key' and 'keyName'
  timeout: 20, // optional if a different timeout is desired
});
```

### Gateway Functions

#### `async authenticateWithKey()`

Attempts to authenticate with gateway to confirm key.  Returns:

* `true` on success (status `200`)
* `false` on failure (status `403`)
* throws error on other conditions

#### `async billMemberForAmount(member:string, amount:number)`

* Calls `pnpremote.cgi` with `mode=bill_member`
* Requires `password` parameter or `PNP_GATEWAY_PUBLISHER_PASSWORD` environment variable
* Returns a formatted response (see below)
* Throws errors on:
    * Undefined `member`
    * Invalid `amount` (non-number, negative)
    * Unexpected response (400+)

#### `async getTransaction(transactionId:string)`

* Calls a get on the `transactionId` (orderId)
* Returns a formatted response (see below)
* Throws errors on:
    * Invalid `transactionId` (non-numeric, greater than 20 characters)
    * Unexpected response (400+)

#### `async voidTransaction(transactionId:string)`

* Calls a delete (void) on the `transactionId` (orderId)
* Returns a formatted response (see below)
* Throws errors on:
    * Invalid `transactionId` (non-numeric, greater than 20 characters)
    * Unexpected response (400+)

#### `async markTransactionToSettleForAmount(transactionId:string, amount:float)`

* Calls `pnpremote.cgi` with `mode=mark`
* Requires `password` parameter or `PNP_GATEWAY_PUBLISHER_PASSWORD` environment variable
* Returns a formatted response (see below)
* Throws errors on:
    * Invalid `transactionId` (non-numeric, greater than 20 characters)
    * Invalid `amount` (non-number, negative)
    * Unexpected response (400+)

#### `async proxy({ data:string, headers:object, method:string = HTTP.METHOD.GET, url:string })`

* Calls `async request()` (see below) in "proxy mode":
    * Returns all responses, not just `2XX` status codes
    * Removes the `set-cookie` header

#### `async queryTransactions(parameters:object, { retry:number })`

* Calls `pnpremote.cgi` with `mode=query_trans`
* Can retry unsuccessful attempts (default: `1`)
* Passes key-value pairs from `parameters` in request body (without validation)
* Requires `password` parameter or `PNP_GATEWAY_PUBLISHER_PASSWORD` environment variable
* Returns a formatted response (see below)
* Throws errors on unexpected responses (400+)

#### `async refundTransactionAmount(transactionId:string, amount:float, {})`

* Calls a return on the `transactionId` (orderId) for `amount`
* Third parameter is an object can include additional fields to pass to the gateway:
    * `accountCode`
    * `accountCode2`
* Returns a formatted response (see below)
* Throws errors on:
    * Invalid `transactionId` (non-numeric, greater than 20 characters)
    * Invalid `amount` (non-number, negative)
    * Unexpected response (400+)

#### `async refundTransactionPnpRemote(transactionId:string, amount:float, {})`

* Calls `pnpremote.cgi` with `mode=return`
* Requires `PNP_GATEWAY_PUBLISHER_PASSWORD` environment variable
* Returns a formatted response (see below)
* Throws errors on:
    * Invalid `transactionId` (non-numeric, greater than 20 characters)
    * Invalid `amount` (non-number, negative)
    * Unexpected response (400+)

#### `async request({ data:string, headers:object, method:string = HTTP.METHOD.GET, url:string })`

* Makes a request from the Gateway
* Returns raw results without additional parsing
* Implements `timeout` constructor argument or `process.env.PNP_GATEWAY_TIMEOUT` value (in seconds)
* Special parameter `headers:Gateway.Headers.Rest` will automatically set REST headers
* Special urls `url:Gateway.Url.PnpRemote` or `url:Gateway.Url.Transaction` will set the URLs to the respective endpoints

#### `static isValidTransactionId(transactionId:string)`

* Returns true if the `transactionId` (orderId) is valid:
    * String
    * Numeric
    * 20 characters or less

#### `async createTransaction({billingInfo: {}, token, expirationMonth, expirationYear, cvv, amount})`

* Creates a transaction with the (optional) billing information, card token (implying card must be tokenized first), expiration, cvv, and amount to charge (before surcharge).
* Other more esoteric optional parameters include: `currency`, `ipAddress`, `flags`, `accountCode`, `accountCode2`, `customerAccountCode`, and `invoiceAccountCode`
* Parameter Object:

``` javascript
  {
    billingInfo: {
    name = "", // if any billing fields are left out, they will default to empty strings (except for country)
    address = "",
    city = "",
    state = "",
    zip = "",
    country = "US",
    phone = "",
    email = ""
  } = {
    name: "", // billing information does not need to be passed at all - defaults to empty strings
    address: "",
    city: "",
    state: "",
    zip: "",
    country: "US",
    phone: "",
    email: ""
  },
  token = null,
  expirationMonth = "",
  expirationYear = "",
  cvv = "",
  amount = null,
  currency = CURRENCY.USD,
  ipAddress = null,
  flags = [],
  accountCode = undefined,
  accountCode2 = undefined,
  customerAccountCode = undefined,
  invoiceAccountCode = undefined
}
```

#### `async getMembers(fieldList:string, statusType:string, membersFilter:array)`

* Returns PnP Members list with fieldList information, statusType information, and membersFilter information.
* Parameters:

``` javascript
  fieldList = "", // space delimited string of PnP fields to return (1)
  statusType = "" // "active", "expired", or "all" (2)
  membersFilter = [] // array of username strings (3)
```

* (1) PnP documentation doesn't mention this field, but pass "" and it only includes these fields: password, enddate, purchaseid, username
* (2) PnP documentation says this defaults to "active" when empty, but that doesn't select "ACTIVE", so "all" is recommended
* (3) This is optional. Without it we use the PnP list_members mode. With it we use the PnP query_member mode.

#### `async updateMember(username:string, memberDetails:object)`

* Calls `pnpremote.cgi` with `mode=update_member`
* Passes key-value pairs from `memberDetails` in request body (without validation)
* Requires `password` parameter or `PNP_GATEWAY_PUBLISHER_PASSWORD` environment variable
* Returns a formatted response (see below)
* Throws errors on unexpected responses (400+)

### Return Types

#### Formatted Response

* Contains the most-used keys at the top level
* Converts `baseAmount`, `feeAmount`, and `amount` to floats
* Stores the raw response in the `_RAW` key
    * For `pnpremote.cgi` this will be a parsed object of the original URL-encoded response

### Helpers

#### `restHeaders()`

Returns headers for a REST gateway connection,
mainly for internal use.  E.g.,

``` javascript
{
  "X-Gateway-Account": ACCEPT,
  "X-Gateway-Api-Key": KEY,
  "X-Gateway-Api-Key-Name": KEY_NAME,
  "Accept": "application/json",
  "Content-Type": "application/json"
}
```

#### `requests`

Array of requests made to the gateway.
Each object in the array should include the following:

* `function`: gateway library function making the call
* `request`: axios request object
* `response`: axios response object or `null`
* `error`: axios error or `null`

### Errors

#### ArgumentError

An `ArgumentError` will be thrown if the Gateway object receives bad parameters.

#### WAF Errors

If the gateway response is blocked
by the web application firewall (WAF)
an error will be thrown.
This error will be a standard `Error` instance
with the addition of a `request` object
that contains some helpful fields:

* `isError`: `true`
* `isWafError`: `true`
* `supportId`: the "support ID" referenced in the error message
* `_RAW`: the raw content (usually HTML) from the response

## Constants

``` javascript
import { GATEWAY } from "@cardx/plugnpay-lib";
```

`FLAG` - for REST transactions

* `CUSTOMER_INITIATED`
* `MERCHANT_INITIATED`
* `MULTICURRENCY`
* `INITIAL_RECURRING`
* `RECURRING`

`HEADER` - for REST transactions

* `ACCOUNT`
* `KEY`
* `KEY_NAME`
* `REMOTE_PASSWORD`

`MODE` - for REST transactions

* `AUTHORIZATION`
* `REFUND`

`OPERATION` - to match `GATEWAY.QUERY.OPERATION`

* `AUTHORIZATION`
* `POSTAUTH`
* `REFUND`
* `VOID`

`QUERY` - for use with `queryTransactions()`

* `ACCOUNT_CODE`
* `ACCOUNT_CODE_2`
* `ACCOUNT_CODE_3`
* `ENDDATE`
* `OPERATION`
* `ORDER_ID`
* `RESULT`
* `STARTDATE`

`STATUS`

* `BAD_CARD`
* `ERROR`
* `PENDING`
* `PROBLEM`
* `SUCCESS`

## Other Utilities

### `authenticateFromHeaders(headers:object)`

Constructs a Gateway object and runs authenticateWithKey using `headers`

* Returns `true` on success
* Throws Jaypie `ForbiddenError` on failure

### `DestinationStateMachine` class

``` javascript
import { DestinationStateMachine } from "@cardx/plugnpay-lib";
```

or

``` javascript
const { DestinationStateMachine } = require("@cardx/plugnpay-lib");
```

#### Usage

``` javascript
const stateMachine = new DestinationStateMachine({data, method:string, url:string});
stateMachine.is(DestinationStateMachine.RestPostAuthorization);
```

> _TODO: this needs a lot of work for a better explanation_

* Don't compare `stateMachine.state === DestinationStateMachine.Indeterminate`, use `stateMachine.is(DestinationStateMachine.Indeterminate)`
* `DestinationStateMachine.Default` is the starting and sometimes ending point of an evaluation
* `DestinationStateMachine.Indeterminate` means the request was ambiguous

### `GatewayRequestValidator` class

Validates that a lambda event representing a gateway request
has specific properties (see below)

#### Usage

``` javascript
// import { GatewayRequestValidator } from "@cardx/plugnpay-lib";
const { GatewayRequestValidator } = require("@cardx/plugnpay-lib");

```

#### Validation Functions

##### Functional Requests

| Function                        | Description |
| ------------------------------- | ----------- |
| `tokenOnlyAuthorizationRequest` | Transaction is a `tokenOnly` `authorization` |

##### Functional Components

| Function               | Description |
| ---------------------- | ----------- |
| `authorization`        | `singleTransaction` matching `DestinationStateMachine.RestPostAuthorization` with a `transaction.payment.card` object|
| `cardNumberNotAllowed` | `singleTransaction` with no `transaction.payment.card.number` field |
| `credit`               | `singleTransaction` matching `DestinationStateMachine.RestPostCredit` |
| `tokenOnly`            | `cardNumberNotAllowed` and `tokenRequired` |
| `tokenRequired`        | `singleTransaction` with `transaction.payment.card.token` field |
| `refund`               | `singleTransaction` matching `DestinationStateMachine.RestPostRefund` |
| `remoteCgi`            | `postRequest` with a `bodyQueryString` containing proper account, password, and mode fields |
| `singleTransaction`    | `bodyJson` with one and only one transaction |

##### Building Blocks

| Function                | Description |
| ----------------------- | ----------- |
| `authenticationHeaders` | Gateway account, keyName, and key fields all present in headers |
| `bodyJson`              | `bodyRequired` that is parsable as JSON |
| `bodyQueryString`       | `postRequest` with a `bodyRequired` that contains a valid query string |
| `bodyRequired`          | `event` object contains non-empty body |
| `getRequest`            | `event.httpMethod` is `GET` |
| `postRequest`           | `event.httpMethod` is `POST`|

### `getSingleTransactionFromBody(body:object)`

Returns the first (presumably only) transaction in the body object.

* Does not check if body object is well-formed
* Will not parse body if a string is passed
* Does not check if multiple transactions are present
* Does not inspect error object

``` javascript
const transaction = getSingleTransactionFromBody(body);
```

## Changelog

* 1.7.0: Adds (broken) request() and proxy() methods
* 1.8.0: Adds DestinationStateMachine
* 1.8.1: Fixes bug in request() and proxy() methods preventing headers from being sent
* 1.9.0: Adds void()
* 1.10.0: Adds logging
* 1.11.0: Supports timeout in all functions; begins refactor
* 1.12.0: Adds (broken) queryTransactions()
* 1.12.2: Fixes bug in queryTransactions()
* 1.12.3: Supports `accountCode3` in `createTransaction`
* 1.13.0: Exports `GATEWAY` constants
* 1.14.0: Adds `updateMember`
* 1.14.1: Adds `retry` option to `queryTransactions`
* 1.15.0: Adds `GatewayRequestValidator` class and `getSingleTransactionFromBody` function
* 1.16.0: Adds `authenticateFromHeaders` function
* 1.17.0: Adds Plug'n Pay "compatibility layer" to REST requests

## License

© CardX. All rights reserved.
