<p align="center" style="text-align: center; padding-top: 15px">
    <a href="https://www.comgate.cz/en">
        <img src="https://static.comgate.cz/assets/images/logo/cg-horr.svg" width="200px" alt="Comgate logo"/>
    </a>
</p>
<p align="center" style="text-align: center; margin: 0; padding: 5px 0 15px;">
    <a href="https://www.comgate.cz/en"><b>Website</b></a> &nbsp;•&nbsp;
    <a href="https://apidoc.comgate.cz/en/"><b>Api Docs</b></a> &nbsp;•&nbsp;
    <a href="https://apidoc.comgate.cz/en/checkout/uvod-checkout"><b>Checkout Docs</b></a> &nbsp;•&nbsp;
    <a href="https://help.comgate.cz/v1/en"><b>Help</b></a>
</p>

# @comgate/checkout-js

![NPM version](https://img.shields.io/npm/v/@comgate/checkout-js.svg?style=flat) [![npm downloads](https://img.shields.io/npm/dm/@comgate/checkout-js.svg?style=flat-square)](https://npm-stat.com/charts.html?package=@comgate/checkout)

`@comgate/checkout-js` is a JavaScript library for easily implement [Comgate payment](https://www.comgate.cz/en) methods directly into the online store cart. It allows you to create a clean, non-disruptive design, including the payment processing directly in the online store.

The library is designed to be used in modern web applications and is compatible with most popular frontend frameworks and build tools.

### Difference between _checkout-js_ and _checkout_

<hr>

`@comgate/checkout-js` is loading wrapper and TypeScript types for [`@comgate/checkout`](https://www.npmjs.com/package/@comgate/checkout).

> **The [`@comgate/checkout`](https://www.npmjs.com/package/@comgate/checkout) package is currently deprecated, and all its functionality has been moved to this package.**

## Comgate Checkout SDK documentation

[Comprehensive API documentation](https://apidoc.comgate.cz/en/) can be found on our website. It is used for custom implementation of online store connection to API. It contains CURL, PHP, JAVA, Python and C# examples.

**[The complete documentation for Comgate Checkout](https://apidoc.comgate.cz/en/checkout/uvod-checkout)** is available in our API documentation, in the '_Checkout SDK_' section.

## Why use @comgate/checkout-js?

In the standard case, you have to insert the script into the page, wait for it to load, catch any errors, and then start using the available functions.

Whereas `@comgate/checkout-js` is a simple and secure way to asynchronously load the Comgate Checkout SDK. It provides a promise-based API for creating an instance of Comgate Checkout.

> 💡 If you still want to use direct script loading method, the example code that also handles potential script loading errors is available below in the section [Using a CDN](#example-2---safe--full-featured-solution).

## Installation

To get started, install `@comgate/checkout-js` with npm.

```sh
npm install @comgate/checkout-js
```

## Usage

Import the `loadComgateCheckout` function for asynchronously loading the Comgate Checkout. Then it is possible to create an instance of Comgate Checkout by [general documentation for this library](https://apidoc.comgate.cz/en/checkout/tutorial#krok-7-vytvo%C5%99en%C3%AD-instance-comgate-checkout-sdk).

### `preloadComgateCheckout(options)`

<hr>

Preloades the Comgate Checkout SDK script and returns a Promise that resolves when the script is successfully preloaded. Otherwise, it will reject the Promise with an error detail.

-   Accepts an object with options
-   Returns a Promise that resolves when the Comgate Checkout SDK script is preloaded

> ⚠️ Preloading the Comgate Checkout SDK does not execute the script. It only loads the script into the browser cache. For executing the script, use the [`loadComgateCheckout`](#user-content-loadcomgatecheckoutoptions) function.

#### Promises

```javascript
import { preloadComgateCheckout, MODULE_APPLEPAY, MODULE_GOOGLEPAY, /*TPreloadComgateCheckoutConfig, TPreloadComgateCheckoutResult*/ } from '@comgate/checkout-js';

const config /*: TPreloadComgateCheckoutConfig */ = {
    checkoutId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
    modules:  [MODULE_APPLEPAY, MODULE_GOOGLEPAY],
    version: '@2', // fix only major version, not specific version
    // callback after preload, the payload is same as return
    // onPreload: (payload /*: TPreloadComgateCheckoutResult */) => {}  value
};

preloadComgateCheckout(config)
    .then((res /*: TPreloadComgateCheckoutResult */) => {
        if (res) {
            // Comgate Checkout SDK preloaded
        }
    });
    // never reject promise - only resolve with boolean (success)
```

#### Passing options

The `preloadComgateCheckout` function takes an object as a configuration. The following options are available:

| Parameter    | Type               | Required | Default     | Description                                                                                                 |
|--------------|--------------------|----------|-------------|-------------------------------------------------------------------------------------------------------------|
| `checkoutId` | `string`           | yes      |             | Comgate Checkout ID as UUID<br>_Format: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`_                             |
| `modules`    | `TModuleType[]`    | no       | `[]`        | Preloade modules source. The core module is always preloaded.                                              |
| `version`    | `TCheckoutVersion` | no       | `"@2"`      | Comgate Checkout SDK version                                                                                |
| `timeout`    | `number`           | no       | `10000`     | Maximum time in miliseconds for waiting to script preload<br>min: 500; max: 30000                          |
| `onPreload` | `Function`         | no       | `undefined` | Alternative to resolved promise as a return value. Type `(payload: TPreloadComgateCheckoutResult) => void` |

#### Return `Promise<TPreloadComgateCheckoutResult>`

The `preloadComgateCheckout` function always returns a Promise<TPreloadComgateCheckoutResult> that resolves preloading status.
Promise value is type of `TPreloadComgateCheckoutResult` which is an object with the following properties:

| Parameter    | Type                                       | Required | Description                                                                      |
|--------------|--------------------------------------------|----------|----------------------------------------------------------------------------------|
| `checkoutId` | `string`                                   | yes      | The value of the Comgate Checkout ID from the `preloadComgateCheckout` options. |
| `success`    | `boolean`                                  | yes      | Preload result status.                                                          |
| `preloaded` | `TModuleType[]`                            | yes      | Successfully loaded modules                                                      |
| `error`      | `{ [key in TModuleTypeExtended]?: Error }` | no       | Error details for each module.                                                   |

> ℹ️ TModuleTypeExtended is a union of TModuleType ('applepay', 'googlepay', 'core') and 'loader' string.

### TPreloadComgateCheckoutResult examples

#### Success
```json
{
    "checkoutId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "preloaded": ["core", "applepay", "googlepay"],
    "success": true
}
```
#### Error example
```json
{
    "checkoutId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "preloaded": [],
    "success": false,
    "error": {
        "core": {},
        "applepay": {},
        "googlepay": {}
    }
}
```

### `loadComgateCheckout(options)`

<hr>

Loads the Comgate Checkout SDK asynchronously and returns a Promise that resolves when the Comgate Checkout SDK is successfully loaded. Otherwise, it will reject the Promise with an error.

-   Accepts an object with options
-   Returns a Promise that resolves when the Comgate Checkout SDK is loaded

#### Promises

```javascript
import loadComgateCheckout from '@comgate/checkout-js';

const checkoutId = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';

loadComgateCheckout({ checkoutId /*, version, async, defer*/ })
    .then((res /*: TComgateCheckoutLoadResult*/) => {
        // Comgate Checkout SDK loaded
    })
    .catch((error) => {
        console.error('Comgate Checkout SDK loading error', error);
    });
```

#### Async/Await snippet

<details>
  <summary>Show snippet (click)</summary>

```javascript
import loadComgateCheckout from '@comgate/checkout-js';

const checkoutId = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';

let comgateCheckout;

try {
    comgateCheckout = await loadComgateCheckout({ checkoutId /*, version: '@1', async: true, defer: false*/ });
} catch (error) {
    console.error('Comgate Checkout SDK loading error', error);
}

if (comgateCheckout) {
    // Comgate Checkout SDK loaded
}
```

</details>

#### Passing options

The `loadComgateCheckout` function takes an object as a configuration. The following options are available:

| Parameter    | Type      | Required | Default | Description                                                                     |
| ------------ | --------- | -------- | ------- | ------------------------------------------------------------------------------- |
| `checkoutId` | `string`  | yes      |         | Comgate Checkout ID as UUID<br>_Format: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`_ |
| `version`    | `string`  | no       | `"@1"`  | Comgate Checkout SDK version                                                    |
| `async`      | `boolean` | no       | `true`  | Load script asynchronously                                                      |
| `defer`      | `boolean` | no       | `false` | Load script deferred                                                            |
| `timeout`    | `number`  | no       | `10000` | Maximum time in miliseconds for waiting to script load<br>min: 500; max: 30000  |

#### Return object

The `loadComgateCheckout` function returns a Promise that resolves to an object with the following properties:

| Parameter                   | Type       | Required | Description                                                                                                                                                                                                               |
| --------------------------- | ---------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `checkoutId`                | `string`   | yes      | The value of the Comgate Checkout ID from the `loadComgateCheckout` options.                                                                                                                                              |
| `ComgateCheckout`           | `Function` | yes      | Function to create Comgate Checkout instance. Same function as `window.COMGATE_SDK()`. [More information docs.](https://apidoc.comgate.cz/en/checkout/tutorial/#krok-7-vytvo%C5%99en%C3%AD-instance-comgate-checkout-sdk) |
| `ComgateCheckoutMethodList` | `Function` | yes      | Create method list component instance without Comgate Checkout. Same function as `window.COMGATE_SDK_METHOD_LIST()`                                                                                                       |
| `ComgateCheckoutVersion`    | `Function` | yes      | Get Comgate Checkout version. Same function as `window.COMGATE_SDK_VERSION()`                                                                                                                                             |

<br>

### `getLoaderVersion()`

<hr>

> ⚠️ Returns the `@comgate/checkout-js` version, not the Comgate Checkout SDK version.

Returns the version of the Comgate Checkout SDK loader.

-   Returns an object with the version

```javascript
import { getLoaderVersion } from '@comgate/checkout-js';

const { version, revision } = getLoaderVersion();

console.log(`Version is ${version}#${revision}`);
```

#### Return object

The `getLoaderVersion` function returns an object with the following properties:

| Parameter  | Type     | Required | Description                       |
| ---------- | -------- | -------- | --------------------------------- |
| `version`  | `string` | yes      | Version of `@comgate/checkout-js` |
| `revision` | `string` | no       | Optional revision identifier.     |

<br>

### `checkoutVersions`

<hr>

Constant with available version shortcuts of the Comgate Checkout SDK.

```javascript
import { checkoutVersions } from '@comgate/checkout-js';

console.log(`Available versions: `, checkoutVersions);
// Available versions:  [ '@1' ]
```

#### Variable structure

The constant `checkoutVersions` is an array of strings that indicate the available versions of the Comgate Checkout SDK.

<br>

## Full example - import module

This snippet shows how to load the Comgate Checkout SDK and create an instance of Comgate Checkout.

```javascript
import loadComgateCheckout from '@comgate/checkout-js';

const checkoutId = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
let checkoutInstanceRef;

loadComgateCheckout({ checkoutId /*, version, async, defer */ })
    .then((res /*: TComgateCheckoutLoadResult*/) => {
        // create Comgate Checkout instance
        res.ComgateCheckout({
            checkoutId: res.checkoutId,
            locale: 'en', // (optional, default: cs) UI language
            debug: true, // (optional, default: false) enable debug mode
            transactionId: 'XXXX-XXXX-XXXX', // (req) Transaction ID
            // ...
        })
            .then((checkoutInstance) => {
                checkoutInstanceRef = checkoutInstance;
                // instance ready
                // TODO DIY
            })
            .catch((error) => {
                // error during instance creation
                // TODO DIY
            });
    })
    .catch((error) => {
        console.error('Comgate Checkout SDK loading error', error);
    });
```

## Using a CDN

The Comgate Checkout SDK is also available on a CDN. You can use it by adding the following script tag to your HTML file.

### Example 1 - simple solution

This example is a simple solution that loads the script and waits for the `ComgateCheckoutReady` event.

> **⚠️ This solution does not handle potential script loading errors.** We recommend using the [safe & full-featured solution](#example-2---safe--full-featured-solution) in production environments.

```html
<!-- Loading Comgate Checkout SDK version 1.x.y -->
<script src="https://static.comgate.cz/checkout/@1" onerror="onError()" async></script>

<script>
    /**
     * Check if the script is loaded and executed (ready to use)
     */
    document.addEventListener('ComgateCheckoutReady', function () {
        // ==================================================
        // TODO DIY - your code here
        // ==================================================
        // window.COMGATE_SDK({
        //     checkoutId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
        //     transaction: 'XXXX-XXXX-XXXX',
        //     // ...
        // }).then((checkoutInstance) => {
        //     // TODO DIY
        // }).catch((error) => {
        //     // TODO DIY
        // });
    });

    /**
     * Error handler
     * @param message
     */
    function onError(message = 'Script not loaded') {
        console.error('Comgate Checkout SDK: Script loading error');
    }
</script>
```

### Example 2 - safe & full-featured solution

This example is a more complex solution that handles potential script loading errors. It also includes a timeout for the maximum time between the script load.

**✅ We recommend using this solution in production environments.**

```html
<!-- Loading Comgate Checkout SDK version 1.x.y in -->
<script src="https://static.comgate.cz/checkout/@1" onload="onLoad()" onerror="onError()" async></script>

<script>
    // error already emitted
    let isError = false;
    // success already emitted
    let isSuccess = false;
    // timeout ID for script execution
    let loadTimeoutId = undefined;
    // timeout ID for max script loading
    let maxTimeoutId = undefined;
    //maximum time between script load and emit ComgateCheckoutReady
    let maxExecutionTimeout = 500; // for defer use longer time
    // timeout for max script loading from page load
    // to emit ComgateCheckoutReady event [in ms]
    let maxLoadTimeout = 10000; // [in ms]

    /**
     * Check if the script is loaded and executed (ready to use)
     */
    document.addEventListener('ComgateCheckoutReady', function () {
        if (!validCheckout()) {
            onError('The script is loaded, the event is emitted ' + 'but SDK is not mapped to the window.');
            return;
        }

        // loaded successfully
        checkoutReady();
    });

    /**
     * Validate if the Comgate Checkout SDK is loaded and ready to use.
     */
    function onLoad() {
        if (isError || isSuccess) {
            return;
        }

        loadTimeoutId = window.setTimeout(() => {
            // Check for correct and successful loading
            if (!validCheckout()) {
                // invalid
                clearTimeout(maxTimeoutId);
                onError('Script is loaded but not executed correctly.');
                return;
            }

            // loaded successfully
            clearTimeout(maxTimeoutId);
            checkoutReady();
        }, maxExecutionTimeout);
    }

    /**
     * Check if the script is loaded and executed correctly
     */
    function validCheckout() {
        return window?.COMGATE_SDK && window?.COMGATE_SDK_VERSION;
    }

    /**
     * Error handler
     * @param message
     */
    function onError(message = 'Script not loaded') {
        // run only first error when is not still success
        if (!isError && !isSuccess) {
            isError = true;
            console.error('Comgate Checkout SDK: ' + message);
        }
    }

    /**
     * Maximum loading timeout after which an error occurs
     */
    maxTimeoutId = window.setTimeout(() => {
        clearTimeout(loadTimeoutId);

        if (!validCheckout()) {
            // invalid
            clearTimeout(maxTimeoutId);
            onError('Script loading exceeded maximum time: ' + maxLoadTimeout + 'ms.');
            return;
        }

        // loaded successfully - unlikely, but just in case.
        checkoutReady();
    }, maxLoadTimeout);

    /**
     * Checkout SDK is ready to use
     */
    function checkoutReady() {
        // run only the first ready one, if there was no error yet
        if (!isSuccess && !isError) {
            isSuccess = true;
            yourFunction();
            return;
        }
        onError('Unexpected ready event.');
    }

    /**
     * Configure and run Comgate Checkout SDK
     */
    function yourFunction() {
        // ==================================================
        // TODO DIY - your code here
        // ==================================================
        // window.COMGATE_SDK({
        //     checkoutId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
        //     transaction: 'XXXX-XXXX-XXXX',
        //     // ...
        // }).then((checkoutInstance) => {
        //     // TODO DIY
        // }).catch((error) => {
        //     // TODO DIY
        // });
    }
</script>
```

## License

Copyright © Since 2024, [Comgate a. s.](https://www.comgate.cz/en) Released under the [EULA](https://www.unpkg.com/@comgate/checkout-js/LICENSE).
