# Payment Elements SDK

- [Configuration](#configuration)
- [Create payment card](#create-payment-card)
- [Capture payment](#capture-payment)
- [Pay now](#pay-now)
- [Void payment](#void-payment)
- [Types](#types)
- [Error handling](#error-handling)

## Getting started

```sh
npm install @eonx/payment-elements
yarn add @eonx/payment-elements
```

## Configuration

```ts
import { configurePaymentElements } from '@eonx/payment-elements';

configurePaymentElements({
  /**
   * Set it as true if you are working in a test environment
   **/
  test: true,
  /**
   * Change input behavior to floating label
   **/
  inputFloatLabel: true,
  /**
   * If true the expiry date will be in MM/YYYY, else MM/YY
   **/
  expiryFullYear: true,
  themes: {
    /**
     * Global settings for all themes
     */
    base: {
      fonts: [
        {
          css: 'https://fonts.googleapis.com/css2?family=Lato:wght@400;700&family=Roboto&display=swap',
        },
      ],
      styles: {
        fontFamily: 'Lato',
        fontSizeBase: '12px',
        fontSizeSmall: '15px',
        colorPrimary: '#6366f1',
        colorBorder: '#e0e0e0',
        colorDanger: '#e53935',
        colorSuccess: '#22c55e',
        colorText: '#37474f',
        colorPlaceholder: '#78909c',
        colorDivider: '#e5e7eb',
        colorInputLabel: '#455a64',
        colorInputBackground: '#fff',
        colorTextLight: 'gray',
        borderRadius: '6px',
        spacing: '10px',
        spacingLabel: '2px',
        inputHeight: '44px',
        buttonHeight: '46px',
      },
    },
    /**
     * Global settings will be inherited and overrode here
     */
    custom: {
      fonts: [
        {
          family: 'Poppins',
          weight: '400',
          display: 'swap',
          src: `url(${window.location.origin}/fonts/poppins-regular.woff2)`,
        },
      ],
      styles: {
        fontFamily: 'Poppins',
        fontSizeBase: '16px',
      },
    },
  },
});
```

You may update the configuration at the runtime.

```ts
configurePaymentElements({
  theme: 'dark',
});
```

## Create payment card

```ts
import {
  createPaymentCard,
  ApiError,
  FrameError,
  CreateCardError,
  PaymentCardTokenizeResult,
} from '@eonx/payment-elements';

const element = createPaymentCard({
  /**
   * Predefined values: 'light' or 'dark'.
   * You can pass custom theme created by `createTheme` method.
   */
  theme: 'light',
  /**
   * API key with access to the payment API.
   */
  apiKey: '-api-key-',
  /**
   * HTML container where the payment element will be mounted.
   */
  container: 'container-id',
  /**
   * Initial value for the cardholder field
   */
  cardholder: 'Joe Black',
  onSuccess(response: PaymentCardTokenizeResult) {
    // Callback when credit card created
  },
  onError(error: Error<CreateCardError | FrameError | ApiError>) {
    // Callback when any errors occurring with element
  },
  onProcess() {
    // Callback when form submitting.
  },
  onChange(event: {
    data: CardDetails;
    isValid: boolean;
    isReadyToSubmit: boolean;
  }) {
    // Form values changed
  },
  onMounted() {
    // The form is ready to display
  },
});

/**
 * Element will be automatically disposed when container will be removed from DOM.
 * Use dispose method if you need to cancel element manually.
 */
element.dispose();

/**
 * Send submit event to form
 */
element.submit();

/**
 * Pre-fill form. Partial values is allowed.
 */
element.prefill({
  cardholder: 'Test User',
  number: '5123450000000008',
  cvc: '100',
  expiryMonth: 1,
  expiryYear: 2039,
});

element.prefill({
  cardholder: 'Test User',
});

/**
 * Submit on the form will not be triggered
 */
element.disableSubmit();

/**
 * Cancel form disabling by previous method.
 */
element.enableSubmit();

/**
 * Change theme after the element has been created.
 */
element.changeTheme('dark');
```

### Types

```ts
interface CardDetails {
  cardholder: string;
  number: string;
  cvc: string;
  expiryMonth: number;
  expiryYear: number;
}
```

### Result

```ts
interface PaymentCardTokenizeResult {
  cardholder: string;
  createdAt: string;
  currency: {
    id: string;
    name: string;
    createdAt: string;
    updatedAt: string;
  };
  customer: {
    createdAt: string;
    fullName: string;
    id: string;
    updatedAt: string;
  };
  description: string;
  expiryMonth: number;
  expiryYear: number;
  hash: string;
  id: string;
  pan: string;
  paymentMethodType: PaymentMethodType;
  provider: {
    name: string;
    id: string;
    createdAt: string;
    updatedAt: string;
  };
  reference: string;
  status: 'inactive' | 'active';
  updatedAt: string;
  verified: boolean;
}
```

### Error Codes

| Code | Description                   |
| ---- | ----------------------------- |
| 10   | API key required              |
| 11   | Payment API URL not found     |
| 12   | Unhandled API error           |
| 20   | HTML container required       |
| 21   | Failed to render payment UI   |
| 22   | Incorrect UI URL              |
| 60   | Failed to create payment card |

## Capture payment

```ts
import {
  capturePayment,
  FrameError,
  ApiError,
  CapturePaymentError,
} from '@eonx/payment-elements';

const element = capturePayment({
  /**
   * API key with access to the payment API.
   */
  apiKey: '-api-key-',
  /**
   * Order intent ID
   */
  orderIntentId: '-intent-id',
  /**
   * Manage to display CVV input in the payment process.
   * Not required.
   */
  isVerificationCodeRequired: true,
  /**
   * Include verification code to capture request
   * Not required.
   */
  verificationCode: '000',
  /**
   * Attach card to order intent before capture
   * Not required.
   */
  attachCard: {
    reference: 'HP6C8X3H97',
    type: 'MastercardCredit',
    amount: '10000'
    consumerOwningEntity: 'someOwningEntity',
    paymentConfig: 'TEST'
  },
  onError(error: Error<CapturePaymentError | FrameError | ApiError>) {
    // Callback when any errors occurring with element
  },
  onSuccess(result: OrderIntent) {
    // Callback when the payment is captured
  },
  onVerify({ mount }) {
    // Callback when we need to mount the UI to handle 3DS.
    // See MountContainer type
    mount('#container');
  },
});

/**
 * Element will be automatically disposed when container will be removed from DOM.
 * Use dispose method if you need to cancel element manually.
 */
element.dispose();
```

### Error Codes

| Code | Description                                              |
| ---- | -------------------------------------------------------- |
| 10   | API key required                                         |
| 11   | Payment API URL not found                                |
| 12   | Unhandled API error                                      |
| 20   | HTML container required                                  |
| 21   | Failed to render payment UI                              |
| 40   | Failed to complete payment                               |
| 41   | Order intent ID has required for capture                 |
| 42   | Failed to retrieve order intent                          |
| 43   | Payment source required                                  |
| 44   | Multiple cards are not supported                         |
| 45   | Payment gateway has not supported                        |
| 46   | Failed to load third-party SDK                           |
| 47   | 3D Secure verification failed. Please contact your bank. |
| 48   | Unable to apply payment for your order                   |

## Pay now

```ts
import { payNow } from '@eonx/payment-elements';

const element = payNow({
  /**
   * Predefined values: 'light' or 'dark'.
   * You can pass custom theme created by `createTheme` method.
   * Non-required field.
   */
  theme: 'light',
  /**
   * API key with access to the payment API.
   */
  apiKey: '-api-key-',
  /**
   * HTML container where the payment element will be mounted.
   */
  container: 'container-id',
  /**
   * IntentId of the order to finalize the payment
   */
  orderIndentId: 'xxxxxxx-f6a3-4524-8f5f-df2a7188xxxx',
  /**
   * Payment card to capture order intent.
   * Non-required field.
   */
  card: {
    cardholder: 'Joe Black',
    number: '4242 4242 4242 4242',
    expiryMonth: 1,
    expiryYear: 2039,
  },
  /**
   * Manage to display CVV input in the payment process.
   * Non-required field.
   */
  isVerificationCodeRequired: true,
  onError(error: Error<CreateCardError | FrameError | ApiError>) {
    // Callback when any errors occurring with element
  },
  onProcess() {
    // Callback when form submitting.
  },
  onMounted() {
    // The form is ready to display
  },
});

/**
 * Element will be automatically disposed when container will be removed from DOM.
 * Use dispose method if you need to cancel element manually.
 */
element.dispose();
```

### Error Codes

| Code | Description                                              |
| ---- | -------------------------------------------------------- |
| 10   | API key required                                         |
| 11   | Payment API URL not found                                |
| 12   | Unhandled API error                                      |
| 20   | HTML container required                                  |
| 21   | Failed to render payment UI                              |
| 40   | Failed to complete payment                               |
| 41   | Order intent ID has required for capture                 |
| 42   | Failed to retrieve order intent                          |
| 43   | Payment source required                                  |
| 44   | Multiple cards are not supported                         |
| 45   | Payment gateway has not supported                        |
| 46   | Failed to load third-party SDK                           |
| 47   | 3D Secure verification failed. Please contact your bank. |
| 48   | Unable to apply payment for your order                   |

## Void payment

```ts
import {
  voidPayment,
  Error,
  VoidPaymentError,
  ApiError,
} from '@eonx/payment-elements';

try {
  const result: OrderIntent = await voidPayment({
    /**
     * API key with access to the payment API.
     */
    apiKey: '-your-key-',
    /**
     * Order intent ID
     */
    orderIntentId: '-intent-id-',
  });
} catch (e) {
  const error: Error<VoidPaymentError | ApiError> = e;
  // use error
}
```

### Error Codes

| Code | Description                           |
| ---- | ------------------------------------- |
| 10   | API key required                      |
| 11   | Payment API URL not found             |
| 12   | Unhandled API error                   |
| 50   | Order intent ID has required for void |

## Types

```ts
type MountContainer = string | HTMLElement;
```

```ts
export interface OrderIntent {
  id: string;
  status: 'pending' | 'voided' | 'captured';
  order: {
    id: string;
    generalStatus: 'completed' | 'failed' | 'processing' | 'refunded';
    createdAt: string;
    updatedAt: string;
  };
  provider: {
    id: string;
    name: string;
    createdAt: string;
    updatedAt: string;
  };
  destinations: PaymentDestination[];
  securityTokenRequired: boolean;
  sources: PaymentSource[];
  tags: string[];
  createdAt: string;
  updatedAt: string;
  expiresAt: string;
  feeType: 'provider' | 'source';
  customer?: {
    createdAt: string;
    id: string;
    updatedAt: string;
    fullName: string;
  };
  description?: string;
  dueFeeAmount?: string;
  dueFinalAmount?: string;
  externalId?: string;
  statementDescriptor?: string;
}

export interface PaymentDestination {
  id: string;
  amount: string;
  reference: string;
  type:
    | 'BankAccountReceiver'
    | 'BpayReceiver'
    | 'Ewallet'
    | 'ProviderFloat'
    | 'UnattachedReceiver';
  createdAt: string;
  updatedAt: string;
  description?: string;
  fixedFeeRate?: string;
  paymentConfig?: string;
  percentageFeeRate?: number;
  statementDescriptor?: string;
}

export interface PaymentSource {
  /**
   * Amount to come from the order source, expressed in the smallest unit of the currency of the order source,
   * e.g. 500 for AUD 5.00. The amount is exclusive of fees.
   */
  id: string;
  reference: string;
  type: PaymentMethodType;
  createdAt: string;
  updatedAt: string;
  amount: string;
  creditLimitAllowed: boolean;
  feePayer: boolean;
  gateway: string;
  consumerOwningEntity?: string;
  description?: string;
  percentageFeeRate?: number;
  paymentConfig?: string;
  paymentRate?: number;
  recurringPaymentExternalId?: string;
  statementDescriptor?: string;
}

export type PaymentMethodType =
  | 'AmericanExpressCredit'
  | 'MastercardCredit'
  | 'MastercardDebit'
  | 'MastercardPrepaid'
  | 'VisaCredit'
  | 'VisaDebit'
  | 'VisaPrepaid';
```

## Errors

```ts
interface Error<T> {
  code: T;
  message: string;
  description: string;
  violations: Record<string, string[]>;
  data: any;
  isCritical: boolean;
  responseStatus?: number;
}
```

```ts
export enum ApiError {
  KeyRequired = 10,
  UrlRequired = 11,
  UnhandledError = 12,
  KeyExpired = 13,
}
```

```ts
export enum FrameError {
  ContainerRequired = 20,
  MountFailed = 21,
}
```

```ts
export enum CapturePaymentError {
  CaptureFailed = 40,
  IntentIdRequired = 41,
  IntentFailed = 42,
  CardRequired = 43,
  MultipleCards = 44,
  UnknownGateway = 45,
  SdkFailed = 46,
  SecureFailed = 47,
  OrderFailed = 48,
}
```

```ts
export enum VoidPaymentError {
  IntentIdRequired = 50,
}
```

```ts
export enum CreateCardError {
  Failed = 60,
}
```

## Error handling

```ts
import { createPaymentCard, ApiError } from '@eonx/payment-elements';

const element = createPaymentCard({
  // ...
  onError(e: Error) {
    if (e.code === ApiError.KeyExpired) {
      // For example, reload UI and display notify when API key is expired
    } else {
      // handle error
    }
  },
});
```
