Мерчант системы

A Merchant Integration Documentation

This document outlines the structure and key components required to integrate a merchant plugin into our system.

File Structure

Your merchant should be organized in the following directory structure under the pluginsExternal/merchants directory. The directory is imported into the system as a plugin and should contain the following files:

pluginsExternal/merchants/MERCHANT_NAME/
  • configure.js: This is where you define the basic setup for your merchant plugin, such as API keys, API Secret, and other necessary configuration options.

  • icon.png: A graphical representation of the merchant. This icon is used throughout the system to visually identify your merchant plugin.

  • index.js: The core implementation file for the merchant plugin. This file contains the logic and functionality required to interact with the merchant's API and our system.

  • templates/: A directory containing HTML and JavaScript templates for the merchant's admin and client interfaces.

    • admin.html: A template file that defines the configuration interface seen by administrators when they set up the merchant in the system.

    • admin.js: A script that handles the data binding and interaction logic for the admin configuration page.

    • client.html: A template file that defines the interface seen by clients or end-users when interacting with the merchant's services.

    • client.js: A script that manages the data interaction for the client-side template.

Templates

The templates/ directory contains HTML and JavaScript files that define how the merchant's configuration and client-facing interfaces look and behave. This section provides details on the two main templates: the admin configuration page and the client page.

Admin Config Page (admin.html)

The admin configuration page is a crucial part of the merchant plugin. This page allows system administrators to set up and manage the merchant's integration by providing the necessary configuration options such as API keys, supported currencies, and debug options.

Structure Explanation:

<div>
  <!-- Display Merchant Name --> 
  <p> Merchant Name</p> 

  <!-- Display Supported Currencies -->
  <h3>Support currency:</h3>
  <li>
    <ul>CARDUAH</ul>
    <ul>BTC</ul>
  </li>
  <hr>

  <!-- Display Configuration Options -->
  <h2>Config</h2>

  <!-- API Key Configuration -->
  <label> API Key: </label>
  <br>
  <input :placeholder="this.config.apiKey" v-model="apiKey">
  <button @click="saveConfig('apiKey', apiKey)">Save</button>
  <br>

  <!-- Debug Log Configuration -->
  <label>Debug log?</label>
  <br>
  <select v-model="debug">
    <option value="1">Yes</option>
    <option value="0">No</option>
  </select>
  <button @click="saveConfig('debug', debug)">Save</button>
  <br>
  <hr>
  <br>

  <!-- Display Public IP and Webhook URL -->
  <h3>Public IP:</h3>
  <span>{{ this.publicIpV4 }}</span>
  <br>

  <hr>
  <h3>Webhook URL:</h3>
  <span>{{this.schema}}://{{this.domain}}{{this.server_path}}merchant/{{this.merchantKey}}/url_status/</span>
</div>
  • Merchant Name: Displays the name of the merchant being configured.

  • Supported Currencies: Lists the currencies that this merchant supports, which can help administrators quickly understand what options are available.

  • Config: Display the configuration options for the merchant where administrators can input the API key, API secret, and other necessary parameters

    • API Key:: Input field for the API key.

    • Secret Key:: Input field for the API secret.

    • Debug log?: Select field for enabling debug logging.

    • saveConfig: Function to save the configuration options, such as API key, API secret, and debug log.

  • Public IP and Webhook URL: Displays the public IP address and webhook URL relevant to the merchant, which is useful for debugging and setup verification.

Admin Config Export (admin.js)

The admin.js file is a Vue.js component that manages the admin configuration page. It handles data binding, user input, and communication with the backend to save the configuration settings provided by the administrator.

Key Components:

const fs = require("fs");
module.exports = {
  // Load the admin template to the Vue instance
  template: fs.readFileSync(__dirname + "/admin.html", "utf8"), 

  props: {
    schema: { type: "String" },
    domain: { type: "String" },
    server_path: { type: "String" },
    publicIpV4: { type: "String" },
    merchantKey: { type: "String" },
    config: { type: "Object" },
  },

  data() {
    return {
      loadStatus: true,
      debug: "0",
      apiKey: "",
    };
  },

  created() {
    console.log('merchant created!');
    this.debug = this.config.debug;
    this.apiKey = this.config.apiKey;
  },

  mounted() {
    console.log('merchant mounted!');
  },

  methods: {
    saveConfig(key, value) {
      console.log('Save config:', key, value);
      this.$rest.api('admin/merchant-and-payout/set-config-merchant', {
        merchantKey: this.merchantKey,
        key,
        value
      })
      .then(res => {
        console.log('admin/merchant-and-payout/set-config-merchant', res);
        if (res.success)
          return alert('Success');
        alert('Error');
      })
      .catch(err => {
        console.error('admin/merchant-and-payout/set-config-merchant', err);
        alert('Error');
      })
    },
  }
};
  • Template Loading: The template property loads the admin.html file into the Vue instance, allowing for seamless integration of the HTML template with the Vue component.

  • Props: These are properties passed from the backend to the Vue instance, providing necessary context such as the server's domain, public IP, and merchant key.

  • Data: Defines reactive data properties like debug and apiKey, which are used to bind user inputs in the admin template.

  • Lifecycle Methods:

    • created(): Initializes the component when it is first created, typically setting up default values or fetching initial data.

    • mounted(): Called when the component is inserted into the DOM, useful for any post-render logic.

  • Methods:

    • saveConfig(key, value): A method to save the merchant's configuration settings back to the server, ensuring that any changes made by the administrator are persisted.

Client Config Export (client.html)

The client page is used to display the merchant data to the client. It will display information like the Address, QR code, Account Owner & Number, Redirect URL, Form, etc. THis is the page where the client will interact with the merchant.

Structure Explanation:

<div>
  <!-- Check for Errors and display an error message -->
  <div v-if="!payData || !payData.merchantData || payData.hasError" class="form-manual-must text-center">
    <p class="title">{{ t("errorGetPayment", "Error create payment! Please contact our support team.") }}</p>
    <p>{{ payData.error }}</p>
  </div>

  <!-- Place the merchant data here for client to interact -->

</div>
  • Error Handling: The template checks for any errors in the payment data and displays an error message if necessary.

  • Merchant Data: This is where the merchant-specific data is displayed to the client, allowing them to interact with the merchant's services.

  • Payment Data: The payData object contains the payment information required by the merchant to process the payment, such as the payment amount, address, and payment method.

  • Localization: The t() function is used to translate text strings based on the client's language preference, ensuring a seamless user experience.

  • Order Information: The order object contains details about the current order, such as the currency, amount, and route information.

QR Code

Display a QR code for the client to scan and make a payment, along with the payment amount and address.

<div class="form-manual-must text-center" v-else-if="payData.merchantData.walletAddr">
  <p class="title">{{ t("toCompleteOrderTransfer", "To complete the order, you need to transfer funds:") }}</p>
  <ol>
    <li>
      <div v-if="payData.merchantData.showQR && payData.merchantData.walletAddr" class="merchant-qrcode">
        <qrcode-vue
          :value="payData.merchantData.walletAddr"
          :size="qrcodeSize"
          :background="qrcodeBackground"
          :foreground="qrcodeForeground"
          level="H"
          render-as="svg"
        />
      </div>
    </li>
    <li>
      <div>
        <p>{{ t("transfer", "Transfer:") }}</p>
        <div class="form-manual-must_txt">
          {{ Number(payData.merchantData.amount) }} {{ order.route.from.name }} {{ order.route.from.symbol }}
        </div>
      </div>
    </li>
    <li>
      <div style="align-items: flex-start;flex-direction: column !important;">
        <p>{{ t("toAddress", "To address:") }}</p>
        <div class="form-manual-must_txt" style="font-size: 0.83rem;">
          {{ payData.merchantData.walletAddr }}
        </div>
      </div>
    </li>
  </ol>
</div>

Account Information

Display the merchant's account owner and number for the client to make a bank transfer.

<div class="form-manual-must text-center" v-else-if="payData.merchantData.accountOwner">
  <p class="title">{{ t("toCompleteOrderTransfer", "To complete the order, you need to transfer funds:") }}</p>
  <ol>
    <li>
      <div>
        <p>{{ t("transfer", "Transfer:") }}</p>
        <div class="form-manual-must_txt">
          {{ Number(payData.merchantData.amount) }} {{ order.route.from.name }} {{ order.route.from.symbol }}
        </div>
      </div>
    </li>
    <li>
      <div>
        <p>{{ t("toAccount", "To account:") }}</p>
        <div class="form-manual-must_txt">
          {{ payData.merchantData.accountOwner }}
        </div>
      </div>
    </li>
    <li>
      <div>
        <p>{{ t("accountNumber", "Account number:") }}</p>
        <div class="form-manual-must_txt">
          {{ payData.merchantData.accountNumber }}
        </div>
      </div>
    </li>
  </ol>
</div>


#### Redirection URL
The URL to which the client will be redirected after completing the payment process.

```html
<div class="form-manual-must text-center" v-else-if="payData.merchantData.redirectUrl">
  <p class="title">{{ t("toCompleteOrderTransfer", "To complete the order, you need to transfer funds:") }}</p>
  <form name="payment" method="post" action="https://example.com/payment/form">
    <input v-for="(val,name) in payData.merchantData" :name="name" :value="val" type="hidden">
    <button class="btn btn-submit merchant-pay-btn" type="submit" ref="paybtn" v-if="!openPS">
      <span>{{ t("makePayment", "Make a payment") }}</span>
    </button>
    <button style="display: none" type="submit" ref="paybtn" v-else></button>
  </form>
</div>
  • Form: A form that submits the payment data to the merchant's payment processing endpoint, allowing the client to complete the payment process.

  • Button: A button that triggers the form submission when clicked, initiating the payment process.

  • Hidden Fields: Hidden input fields containing the payment data required by the merchant to process the payment.

  • Redirect URL: The URL to which the client will be redirected after completing the payment process.

  • If the redirect URL is a simple get request to the merchant site, you can use the following code:

  <div class="form-manual-must text-center" v-else-if="payData.merchantData.redirectUrl">
  <p class="title">{{ t("toCompleteOrderTransfer", "To complete the order, you need to transfer funds:") }}</p>
  <form name="payment" method="get" action="https://example.com/payment/form">
    <button class="btn btn-submit merchant-pay-btn" type="submit" ref="paybtn" v-if="!openPS">
      <span>{{ t("makePayment", "Make a payment") }}</span>
    </button>
    <button style="display: none" type="submit" ref="paybtn" v-else></button>
  </form>
</div>

Manual Form Input

If the merchant requires manual input from the client, you can display a form with the necessary fields.

<div class="merchant-form" v-else>
  <label>{{t("enter", "Enter")}} {{t("number", "number")}} (#):</label>
  <input
    v-model="ev_number"
    :placeholder="'E-Voucher Number (#) '"
    type="text"
  />

  <label>{{t("enter", "Enter")}} Activation {{t("code", "code")}} :</label>

  <input
    v-model="ev_code"
    :placeholder="'E-Voucher Activation Code '"
    type="text"
  />

  <button class="btn btn-submit merchant-pay-btn" type="button" ref="paybtn" @click="uploadCode">
    <span>{{ t("pay", "Pay") }}</span>
  </button>
</div>
  • Form Fields: Input fields for the client to enter the required information, such as an E-Voucher number and activation code.

  • Labels: Descriptive labels for each input field, providing context for the client.

  • Button: A button that triggers the client script method when clicked, allowing the client to submit the form data for payment processing.

  • uploadCode(): A client script method that handles the form submission logic, processing the client's input and initiating the payment process.

Client Config Export (client.js)

The client.js file is a Vue.js component that manages the client page. It handles data binding, user interaction, and communication with the backend to process the payment and update the order status.

The client script is responsible for handling the local state of the client page, such as loading status, payment data, and localization.

Key Components:

const fs = require("fs");

module.exports = {
  template: fs.readFileSync(__dirname + '/client.html', {encoding: 'utf8'}),
  props: {
    order: {
      type: "Object"
    },
    payData: {
      type: "Object"
    },
    qrcodeSize: {
      type: "Number",
      default: 150
    },
    qrcodeBackground: {
      type: "String",
      default: "#ffffff"
    },
    qrcodeForeground: {
      type: "String",
      default: "#000000"
    }
  },
  data() {
    return {
      loadStatus: true,
      openPS: true
    }
  },
  created() {
    if (navigator && navigator.userAgent && navigator.userAgent.match(/(iPod|iPhone|iPad)/i))
      this.openPS = false;
    else
      this.openPS = !localStorage.getItem('redirect-' + this.order.uid);
  },
  mounted() {
    localStorage.setItem('redirect-' + this.order.uid, 't');
    if (this.$refs && this.$refs.paybtn && this.openPS)
      this.$refs.paybtn.click();
    this.loadStatus = false;
  },
  methods: {
    t(k, d) {
      const t = this.$t("ui.modules." + k);
      if (t !== "ui.modules." + k)
        return t;
      return d
    },
  }
};
  • Template Loading: The template property loads the client.html file into the Vue instance, allowing for seamless integration of the HTML template with the Vue component.

  • Props: These are properties passed from the backend to the Vue instance, providing necessary context such as the order details, payment data, and QR code settings.

  • Data: Defines reactive data properties like loadStatus and openPS, which are used to manage the loading state and payment processing logic.

    • loadStatus: A boolean flag indicating whether the client page is still loading or has finished rendering.

    • openPS: A boolean flag that controls whether the payment processing page should be opened automatically based on localStorage and user agent.

  • Lifecycle Methods:

    • created(): Initializes the component when it is first created, setting up the payment processing logic based on the client's device.

    • mounted(): Called when the component is inserted into the DOM, useful for any post-render logic.

  • Methods:

    • t(k, d): A method to translate text strings based on the client's language preference, ensuring a localized user experience.

In the client.js file, you can define additional methods to handle client-side logic, such as form submission, payment processing, and order status updates. Just add the methods to the methods object in the Vue component definition.

methods: {
  t(k, d) {
    const t = this.$t("ui.modules." + k);
    if (t !== "ui.modules." + k)
      return t;
    return d
  },
  uploadCode() {
    // Handle form submission logic here and error handling
  },
}

Configuration

The configure.js file is where you define the basic setup for your merchant plugin, such as Title, Name, and required configuration options. This file is used to configure the merchant plugin and set up the necessary parameters for integration.

Structure Explanation:

module.exports.type = 'merchant';
module.exports.title = 'MERCHANT TITLE';
module.exports.name = 'MERCHANT_NAME';
module.exports.required_npm = [];

module.exports.allow_XML = [];

module.exports.default_config = {
  debug: '0',
  // Add more default configuration options here
  apiKey: '',
};
module.exports.required_config = {
  debug: 1,
  // Add more required configuration options here
  apiKey: 2
};
  • Type: The type of plugin, in this case, a merchant plugin.

  • Title: The title of the merchant plugin, which is displayed in the system's interface.

  • Name: The name of the merchant plugin, used to identify the plugin in the system.

  • Required NPM Packages: Any required npm packages that need to be installed for the merchant plugin to function correctly.

  • Allow XML: An array of XML endpoints that the merchant plugin can interact with, if applicable.

  • Default Configuration: The default configuration settings for the merchant plugin, such as debug mode and API key.

  • Required Configuration: The required configuration settings that control weather the values are displayed in the admin configuration page or not.

    • Debug: A configuration option to enable or disable debug mode, useful for troubleshooting and testing.

    • API Key: A configuration option to set the API key required for the merchant plugin to interact with the merchant's API.

    • 1: Indicates that the value is displayed as value in the admin configuration page.

    • 2: Indicates that the value is displayed as a password in the admin configuration page.

Icon

The icon.png file should be a PNG image with a minimum size of 150x150 pixels. This icon will represent your merchant plugin in the system's user interface, making it easily recognizable to administrators.

Implementation

The index.js file contains the core logic of your merchant plugin. Below are the key methods that should be implemented:

Constructor

The constructor is responsible for initializing the merchant plugin with the necessary configurations and options.

Parameters:

  • API: An object containing the system's API end for interacting with the backend locally.

  • config: An object containing the merchant's configuration settings, such as the API key and debug mode.

const id_key = __dirname.split('/').slice(-1)[0];
const config = require('../../../modules/config/merchant.config.js')(id_key);
const i18n = require('../../../modules/i18n');
const crypto = require('crypto');
const axios = require('axios');
const BigNumber = require('bignumber.js');
const checkPaymentModule = require('../../../modules/CheckPaymentModuleName');

class MerchantName {
  constructor(API, config) {
    this.API = API;
    this.config = config;
  }
}
  • id_key: The unique identifier for the merchant plugin, derived from the directory name.

  • config: A configuration object that loads the merchant's configuration settings from the system.

  • i18n: A localization function used to translate text strings based on the client's language preference.

  • crypto: A cryptographic module used for generating secure hashes and encryption.

  • axios: A popular HTTP client for making API requests.

  • BigNumber: A library for handling arbitrary-precision arithmetic, useful for dealing with financial calculations.

  • checkPaymentModule: A module that checks the payment status of a transaction, ensuring that payments are processed correctly.

Get Fields

The class MerchantName should also define any necessary form fields to collect data from the client such as thier email, phone number, full name, etc. Although, most of the time, the fields methods return an empty array unless the merchant requires additional information from the client.

async getFields(lang, xml) {
  const fields = [];

  fileds.push({
    _id: "name_card_holder", // unique id for the field
    name: i18n(lang, "en")._t(`merchant/field/full_name`) || 'Name Surname card holder',
    placeholder: "NAME SURNAME",
    regexp: "^([A-z-'`]{2,50}[_, ])[A-z-'`]{2,50}$",
    regexp_error: i18n(lang, "en")._t(`merchant/field/error/full_name`) || 'Name Surname',
    required: true,
  });

  return fields;
}

Parameters:

  • lang: The language code for the client's preferred language, used for localization.

  • xml: An XML of the currency that the merchant supports, if applicable.

  • The getFields method should return an array of field objects, each representing a form field that the client needs to fill out. Each field object should contain the following properties:

    • _id: A unique identifier for the field.

    • name: The display name of the field, used as a label in the client interface.

    • placeholder: A placeholder text for the field, providing guidance to the client on what to enter.

    • regexp: A regular expression pattern to validate the field's input, ensuring it meets the required format.

    • regexp_error: An error message to display if the client's input does not match the regular expression pattern.

    • required: A boolean flag indicating whether the field is required for the client to proceed.

  • The i18n function is used to translate text strings based on the client's language preference, ensuring a localized user experience.

API Query

The class MerchantName should also define a method to query the merchant's API for payment processing or order status updates or any other necessary data.

async api_query(method_name, data) {
  const API_HOST = 'https://api.example.com/merchant';
  const API_KEY = this.config.apiKey || await config.get('apiKey');

  const isDebug = this.config.debug || await config.get('debug');
  let method = method_name.split(':');
  let typeMethod = 'POST';

  if (method.length === 2) {
    typeMethod = method[0];
    method = method[1];
  } else {
    method = method[0];
  }
  const unixTimeRequest = Date.now();

  const optReq = {
    url: API_HOST + method,
    method: typeMethod,
    headers: {
      "Content-Type": "application/json",
      Authorization: "Bearer " + API_KEY,
    },
    data,
  };

  if (isDebugMode)
      console.time("{Merchant} -> MerchantName -> api: " + API_HOST +method_name +" - nonce: " +unixTimeRequest + " took: ");
  return axios(optReq)
      .then((r) => r.data)
      .then((body) => {
        if (isDebugMode)
          console.timeEnd("{Merchant} -> MerchantName -> api: " +typeMethod +":" +API_HOST +method +" - nonce: " +unixTimeRequest +" took: ");

        if (!body)
          return Promise.reject({ message: "Error response is empty -> MerchantName",code: 5001 });
        
        return body.data || body;
      })
      .catch((error) => {
        console.error("{Merchant} -> MerchantName -> error :", error.data ? error.data.message : error.message);
        
        if (error && error.message && error.code && !error.response)
          return Promise.reject({ message: error.message, code: error.code });
       
        const resultError = error.response || error;
        if (
          resultError &&
          resultError.data &&
          resultError.data.message &&
          resultError.status
        )
          return Promise.reject({
            message: resultError.data.message,
            statusCode: resultError.status,
            code: 5007,
          });
        return Promise.reject({
          message: `Unknown error: ${error.message} - ${data.request}`,
          code: 5008,
        });
      });
}

# Example of how to use the api_query method
const data = {
  amount: 100,
  currency: 'USD',
  card_number: '1234567890123456',
  card_expiry: '12/24',
  card_cvv: '123',
};
const response = await this.api_query('POST:/payment', data);

Parameters:

  • method_name: The name of the API method to call, including the HTTP method (e.g., POST:/payment).

  • data: The data to send in the API request, such as payment details or order information.

  • The api_query method constructs an API request to the merchant's endpoint using the provided method name and data.

  • The method extracts the API key and debug mode settings from the merchant's configuration.

  • The method sends an HTTP request to the merchant's API endpoint, handling success and error responses accordingly.

  • The method returns the response data from the API, ensuring that the necessary data is processed and available for further use.

  • The method also includes error handling logic to manage various types of API request failures and provide descriptive error messages.

Process Payment

The class MerchantName should define a method to process a payment request from the client, including validating the payment details and interacting with the merchant's API.

The create_payment method should handle the payment processing logic, including validating the payment details, generating a payment request, and interacting with the merchant's API to process the payment.

The method should return a payment object containing the payment error status, order ID, and merchant specific data.

async create_payment(order) {
  return (async () => {
    if (order.status !== "waitPayment" && order.status !== "inProgress") {
      return {};
    }
    let merchantData = await this.API.call(
    "server/exchanger/order/merchantData/get",
    {},
    { orderId: String(order._id) }
    )
      .then((r) => r.data)
      .then((r) => r.data)
      .catch((err) => console.error("Paylink ->api->merchantData/get", err));
    if (!merchantData) {
      const currencyProvider = this.getCurrencyByXML(order.route.from.xml);
      if (!currencyProvider)
        return {
          error: "Error currency provider order. Currency Not Found!",
        };
      const web_domain = await config.get("domain");
      const schema = await config.get("schema");

      merchantData = await this.api_query("POST:/create_payment", {
        amount: order.amount,
        currency: currencyProvider,
        order_id: order._id,
        callback_url: `${schema}://${web_domain}${server_path}merchant/${this.id_key}/url_status`,
        return_url: schema +"://" +web_domain +"/" +order.lang +"/" +order.TYPE +"/" +order.uid +"/" +order.secret +"/",
      });

      if (merchantData && merchantData.error) {
        return { error: merchantData.error };
      }

      if (merchantData && merchantData.url) {
        merchantData = {
          orderId: `id_${order.uid}`,
          redirectUrl: merchantData.url,
          currency: currencyProvider,
        };

        await this.API
        .call( "server/exchanger/order/merchantData/upsert", {}, {
          orderId: String(order._id),
          data: merchantData,
          merchant: id_key,
        }, "server")
        .then((r) => r.data)
        .catch((err) => console.error("Paylink->api->upsert", err));
      }
    }

    return {
      hasError: !merchantData,
      merchantData: merchantData,
      orderId: order.uid,
    };
  })();
}

Parameters:

  • order: The order object containing the payment details, such as the amount, currency, and order ID.

    • order.status: The status of the order, indicating whether the payment is pending or in progress.

    • order._id: The unique identifier for the order, used to track the payment request.

    • order.uid: The order UID, which is typically a unique identifier for the payment transaction.

    • order.amount: The payment amount in the order's currency.

    • order.route: The currency route for the payment, including the source and destination currencies.

    • order.lang: The language code for the client's preferred language.

  • The create_payment method first checks the order's status to determine if the payment processing is required.

  • The method retrieves the merchant-specific data for the order, such as the payment URL and currency provider.

  • If the merchant data is not available, the method constructs a payment request and interacts with the merchant's API to process the payment.

  • The method handles errors and returns a payment object containing the payment status, order ID, and merchant-specific data.

  • The method also updates the order's merchant data with the payment details, ensuring that the payment status is tracked correctly.

  • The method returns the payment object, which includes the payment error status, order ID, and merchant-specific data.

  • It is also recommended to include an order._id field in the merchant data to link the payment details to the order, so the system can track the payment status correctly.

URL Status

The class MerchantName should define a method to handle the URL status endpoint(Webhook), which receives updates from the merchant's API regarding the payment status.

The url_status method should process the incoming data from the merchant's API, update the order status accordingly, and return a response to the merchant.

url_status(req, res, param) {
  return (async () => {
    if(req.METHOD !== 'POST')
      return Promise.reject('Method not allowed');

    if(!param || !param.post || !param.post.order_id) {
      console.error("[Merchant]->" + this.id_key + " param no client_id data available:\n\t", param);
      return Promise.reject("error no data available");
    }

    // Header checking for signature, API key, and other security measures
    if (req.headers['authorization'] !== 'Bearer ' + this.config.apiKey){
      console.error("[Merchant]->" + this.id_key + " error authorization header:\n\t", req.headers);
      return Promise.reject("error authorization header");
    }

    const txnData = param.post;

    const order = await this.API.call( "server/exchanger/order/get", {}, 
      { uid: String(txnData.order_id).replace("id_", "") }, "server")
      .then((r) => (!!r && !!r.data && !!r.data.order ? r.data.order : null));
    if (!order) {
      console.error("[Merchant]->" + this.id_key + " order not found:\n\t", param);
      return Promise.reject("order not found");
    }

    await checkPaymentModule(this.id_key, order.methodMerchant);

    const { status, amount, txn_id, currency } = txnData;

    if(status !== 'success') {
      console.error("[Merchant]->" + this.id_key + " error status:\n\t", txnData);
      return Promise.reject("transaction isn't success");
    }

    if (order.status !== "waitPayment" && order.status !== "inProgress"){
      console.error( "[Merchant]->" + this.id_key + " error change status:\n\t", txnData);
      await this.API
      .call( "server/exchanger/order/update-status", {}, {
          orderId: String(order._id),
          status: "errorPayment",
          comment: "[Merchant " + this.id_key + "] Payment came after we waited for payment " + amount + " . Change order status from: #" + order.status,
        }, "server");
      return "error_change_status";
    }

    const currencyProvider = this.getNetworkByXML(order.route.from.xml);
    if (currencyProvider !== currency) {
      console.error("[Merchant]->" + this.id_key + " incorrect currency:\n\t", param);
      await this.API
        .call('server/exchanger/order/update-status', {}, {
          orderId: String(order._id),
          status: "errorPayment",
          comment: "[Merchant " + this.id_key + "] INCORRECT currency: " + currencyProvider + " !== " + String(currency) + ", TX: " + String(txn_id) + "."
        }, 'server');
      return "Incorrect currency: " + currency;
      }

    if (new BigNumber(order.inAmount).gt(+amount)) {
      console.error( "[Merchant]->" + this.id_key + " incorrect amount:\n\t", txnData);
      await this.API
        .call("server/exchanger/order/update-status", {}, {
          orderId: String(order._id),
          status: "errorPayment",
          comment: "[Merchant " + this.id_key + "] INCORRECT AMOUNT: " + amount + " but we wait " + Number(order.inAmount) + ", TxID: " + txn_id,
        }, "server" );
      return "incorrect_amount";
    }

    await this.API.call(
        "server/exchanger/order/update-status", {}, {
          orderId: String(order._id),
          status: "inProgressPayout",
          comment: "[Merchant " + this.id_key + "] Accepted amount:" + amount + ", TxID: " + txn_id,
        }, "server");

      return `${order.uid}|success`;
  })()
    .then((txt) => {
      res.set("Content-Type", "text/plain");
      res.writeHead(200);
      res.end(txt);
    })
    .catch((err) => {
      console.error("[Merchant]->" + this.id_key + " error IPN:", err);
      res.set("Content-Type", "text/plain");
      res.writeHead(500);
      res.end("Error:" + err);
    });
} 

Parameters:

  • req: The express request object containing the incoming data from the merchant's API.

  • res: The express response object used to send a response back to the merchant's API.

  • param: The parameters object containing the parsed request data, such as post for POST data and get for query parameters.

  • The url_status method processes the incoming data from the merchant's API, updating the order status based on the payment status.

  • The method validates the request method, authorization header, and order ID to ensure the request is legitimate. If the merchant have a signature, API key, or other security measures, they should be checked here.

  • The method retrieves the order details based on the order ID which we specify in the create_payment method.

  • The method checks the payment module status to ensure that the payment processing is valid and secure using the checkPaymentModule method.

  • The method validates the payment status, amount, and order status to ensure that the payment is successful and matches the expected values.

  • If the currency is passed in the request, it is compared with the expected currency to ensure that the payment currency matches the order currency.

  • If the payment amount is less than the expected amount or status is not success or done or approved , an error is logged, and the order status is updated to errorPayment to reflect the payment error.

  • If the payment is successful, the order status is updated to inProgressPayout, indicating that the payment is being processed.

  • this.API.call: A method to call the system's API to update the order status based on the payment processing results.

    • server/exchanger/order/update-status: The API endpoint to update the order status in the system.

    • orderId: The unique identifier for the order, used to identify the order to update.

    • status: The new status to set for the order, such as inProgressPayout or errorPayment.

    • comment: A comment to log with the order status update, providing context for the status change and this comment is displayed in the order history for the administrator.

  • The method returns a response to the merchant's API, indicating the order UID and payment status, such as success or an error message.

Optional Methods

The MerchantName class can also define additional methods to handle specific merchant logic, such as getting the currency by XML, getting the network by XML, or signature validation.

getCurrencyByXML(xml) {
  if (!xml) return null;
  //  Fiat currency
  if (xml.indexOf('CARDUAH') === 0) return 'UAH';

  // OR Cryptocurrency
  if (xml.indexOf('USDTERC20') === 0) 
    return 'USDT';

  return xml;
}

getNetworkByXML(xml) {
  if (!xml) return null;
  // Cryptocurrency with network
  if (xml.indexOf('USDTERC20') === 0) 
    return 'ETH';
  if (xml.indexOf('BTC') === 0) 
    return 'BTC';

  return xml;
}
  • XML is used to determine the currency provider for the order, it is a standard representation of the currency. Sites like jsons.info and bestchange.com can be used to get the XML of the currency.

  • getCurrencyByXML method returns the currency based on the XML provided, such as USDTERC20 for USDT or BTC for Bitcoin.

  • getNetworkByXML method returns the network based on the XML provided, such as ETH for Ethereum or BTC for Bitcoin.

Export

The index.js file should export the MerchantName class so that it can be used by the system to interact with the merchant plugin.

module.exports = MerchantName;
  • The module.exports statement exports the MerchantName class, making it available for use by the system to process payments, update order statuses, and handle payment processing logic.

Last updated