Interwallet Transfer

To receive events from the Finswich Platform

There are two types of webhooks that are used to communicate with the Finswich platform. The outgoing webhook is used to communicate with a merchant when their users want to send funds to another platform (Outgoing Transactions) while the incoming webhook is used when a user from another platform transacts with their own users (Incoming transactions).

FOR OUTGOING TRANSACTIONS

An outgoing transaction is a kind of transaction in which the users on your platform intend to send money on your wallet to other wallets, banks in other countries.

BEFORE INTEGRATION - DO THIS

Before you begin any integration you need to add an outgoing webhook where all request between Finswich & your application will be posted. See Below for Image reference

TRANSACTION FLOW

To perform an outgoing transaction on Finswich, there are 4 events that has to take place.

1. Retrieve User Event - The Finswich platform will contact your application via the outgoing webhook to get your user's data.

The data below is sent (Request body) to your outgoing webhook registered on Intrapay.

{"event":"RETRIEVE_USER","data":{"user_reference":"testappemcodeng001","mode":"LIVE"}}

//The user_reference is the user reference provided via the checkout 
//mode decides if you are testing "TEST" or have gone live "LIVE"

Here is a sample of the code on your backend

// check if the user reference set from finswich is in the data field of req.body


      if (body.event === 'RETRIEVE_USER') {
        //! TODO: send the user data to FinSwitch

        // check if there is data field in the req.body
        if (body?.data) {
          // check if the user reference in the data field of req.body
          if (body.data.user_reference) {
            const user_reference = body.data.user_reference;

            // retrieve the user from my third-party database using the unique user reference
            const user = await this.user_repository.FindById(
              user_reference,
              [
                '-permissions',
                '-settings',
                '-recovery_keys',
                '-contigency_lock_expires',
                '-contigency_level',
                '-verification_code',
              ],
            );

            //: send user data to FINSWITCH
            res.send ({
              status: 'success',
              message: 'User Data sent',
              data: {
                first_name: user.firstname,
                last_name: user.lastname,
                email: user.email,
                user_reference: user_reference,
                country: user.country,
                phone_number: user.phone,
              }),
            };
          }
        }
      }

2. Transaction Initiate - Once the Wallet Checkout is opened, the transaction Initiate is trigger when the user want to perform a transaction and they need to authorize the transaction as shown below.

Req body sent from FInswich to your outgoing webhook

{"event":"TRANSACTION_INITIATE","data":{"user_reference":"emcodeApp001Live","amount":2010.1420689287522,"currency":"ngn","txn_reference":"FIN-DXVb1CpC6jnTXkqJ1682607236","beneficiary_finswich_code":"APP-070848f9ea8049629f2af7067afa13c5","mode":"LIVE"}}

Here is a code sample to handle the transaction initiate event on your end

 if (body.event === 'TRANSACTION_INITIATE') {
        //!: MAKE SURE THE USER IS VALID AND HAVE THE REQUIRE BALANCE AND AUTHORIZATION

        //! consider debiting your wallet here
        const debit_model = {
          txn_reference: body.data.txn_reference,
          user_reference: body.data.user_reference,
          amount: body.data.amount,
          currency: body.data.currency,
        };

        // Retrieve user balance to confirm if they have enough money to continue transaction
        const wallet = await this.wallet_repository.FindByUserId(
          debit_model.user_reference,
        );

        // check if the balance actually exists
        if (!wallet) {
          throw new notFoundException(en['wallet-not-found']);
        }

        // check if the balance is enough for transaction
        if (!wallet.balance < debit_model.amount) {
          throw new notFoundException(en['insuficient-balance']);
        }

        // create a transaction log on third-party system
        await this.finswich_txn_logs_repository.CreateTxnLogs({
          txn_reference: debit_model.txn_reference,
          amount: debit_model.amount,
          currency: debit_model.currency,
          user: debit_model.user_reference,
          wallet_id: wallet.id,
          type: 'outgoing',
          status: 'pending',
        });

        res.send({
          status: 'success',
          message: 'Txn Permitted',
          data: {
            user_category: 'dfa292a668bc4109af10cd5bddc8dab6', // this is obtained from the policy on ur finswitch account
          },
        });
      }

3. Send OTP - At the initiation of the transaction, an OTP request is sent to your application for you to generate an OTP for your user to that they can authorize the payment. Once the user submit the OTP, we will combine the data with the Complete Transaction Payload that will be sent to your application.


{"event":"SEND_OTP","data":{"otp_message":"You are about to make a payment of 10.0 sar to Ahmed Emcode on SaudiApp platform from your EmcodeCashApp wallet.\nUse this One Time Password to verify your transaction. _OTP_.","txn_reference":"FIN-DXVb1CpC6jnTXkqJ1682607236", user_reference: "", "mode":"LIVE"}}
/ Some code

Here is a sample code on your end for the send OTP event

if (body.event === 'SEND_OTP') {
        //! SEND CUSTOM OTP MESSAGE TO UR USERS VIA SMS, VOICE, EMAIL OR WHATSAPP
        //TODO: InitiateOTP fxn
        // await this.InitiateOTP(body.data?.user_reference)

        // create code for a transaction on third-party system
        const { code } = await this.finswich_txn_logs_repository.CreateCode(
          body.data?.txn_reference,
        );

        const user = await this.user_repository.FindById(
          body.data?.user_reference,
        );

        // Send custom otp message to user
        securityConfig.NO_PROD != true
          ? taskQueue
              .add(
                'send_sms',
                {
                  to: user.phone,
                  code: code,
                },
                { removeOnComplete: true, removeOnFail: true },
              )
              .catch((error) => {
                this.error_service.CreateErrorLog(error, req);
                return;
              })
          : console.log(code);

        res.send({
          status: 'success',
          message: 'OTP SENT!',
        });
      }

4. Complete Transactions - Finswich will send you the payload request with the OTP. You have the sole responsibility to verify the OTP before you issue an authorization for Finswich to honor the request.

NOTE

a. User Category according to the policy you created on your Finswich dashboard will apply for every outgoing transaction. I.e If a user’s category can only send $100 per day, that limit will apply for everything such user can do on the Finswich Checkout.

b. If you do not create a user category or any policy in your dashboard, The Finswich default wallet policy and category will be imposed all your users.

c. Once the request is honored, we will place a debit to your wallet, it’s important that you debit your own users on their wallet on your application before you respond Finswich with a Success Status.

d. We advised that you create your user categories and policy before integration and according to the AML policy of your organization

Request to your webhook

{"event":"COMPLETE_TRANSACTION","data":{"otp":"123456","txn_reference":"FIN-DXVb1CpC6jnTXkqJ1682607236","mode":"LIVE"}}

Sample code for Complete Transaction Event



        if (body.event === 'COMPLETE_TRANSACTION') {
          //! if there is an error
          if (!('data' in body)) {
            //: reject
            throw new badRequestException(en['transaction-error'], 400, {
              user_category: '',
            });
          }

          const get_data = body.data;

          //const body = body
          //const hash_payload = req.headers["x-finswich-signature"]
          //  const can_give_value = await hl.FinswichHashMessageAuthCode(body, secret, hash_payload)
          //  const can_give_value = await hl.FinswichHashMessageAuthCode(body, secret, hash_payload)
          

          //! extract payload 
          const b2b_payload = {
            txn_reference: get_data.txn_reference,
            auth_code: get_data.otp,
          };

          // Verify the otp on third-party system
          const debit_log = await this.VerifyCode(
            get_data.otp,
            get_data.txn_reference,
          );

          // retrieve the user
          const user = await this.user_repository.FindById(
            debit_log.user,
            [],
            ['app'],
          );
          
          // retrieve the owner of the app
          const merchant = await this.merchant_repository.FindById(
            user.app.merchant,
          );

          if (get_data.mode == 'LIVE') {
            //!: make sure is valid on finswich before giving value to the user  in your wallet system
            //! please see a sample of our hmac for reference

            // retrieve third parties secret key and public key
            const secret = this.decrypt_finswich_key(
              merchant.finswich_secret_key,
            ); // this is the secret key you downloaded earlier
            const public_key = this.decrypt_finswich_key(
              merchant.finswich_public_key,
            ); // this is the public key you downloaded earlier

            // sign the payload which would be sent back to finswich
            const hash_payload = await this.FinswichHashMessageAuthCode(
              b2b_payload,
              secret,
            );

            //! make the b2b wallet to wallet transfer now
            const get_response = await this.AuthorizeFinswich(
              public_key,
              hash_payload,
              b2b_payload.txn_reference,
              b2b_payload.auth_code,
            );
            console.log(
              '\x1b[41m%s\x1b[0m',
              '#RESPONSE FROM FINSWICH B2B WALLET SERVER',
              get_response,
            );
            if ('status' in get_response) {
              if (get_response.status === 'success') {
                //: debit wallet
                await this.wallet_repository.DeductBalance(
                  debit_log.wallet_id,
                  debit_log.amount,
                );

                await this.wallet_repository.UpdateById(
                  debit_log.wallet_id,
                  {},
                  { total_outgoing: debit_log.amount },
                );

                //! update txn status
                await this.finswich_txn_logs_repository.UpdateById(debit_log.id, {
                  status: 'success',
                });

                return {
                  status: 'success',
                  message: en['transaction-completed'],
                  data: {
                    user_category: '',
                  },
                };
              }
            }
          }

          throw new badRequestException(en['transaction-error'], 400, {
            user_category: '',
          });
        }
        
        
      decrypt_finswich_key(encrypted_text) {
    const algorithm = 'aes-256-cbc';
    const decipher = crypto.createDecipher(
      algorithm,
      securityConfig.ENCRYPTION_KEY,
    );
    let decrypted = decipher.update(encrypted_text, 'hex', 'utf-8');
    decrypted += decipher.final('utf-8');
    return decrypted;
  }

  async VerifyCode(code, txn_reference) {
    try {
      let debit_logs_exist = await this.finswich_txn_logs_repository.FindByFilter(
        {
          txn_reference,
        },
        ['+verification_code'],
      );

      if (!debit_logs_exist) {
        throw new Error(en['wallet-not-found']);
      }

      const hashedCode = crypto.createHash('sha256').update(code).digest('hex');

      if (debit_logs_exist.verification_code === hashedCode) {
        const update_data = {
          verification_code: null,
          verification_expires: null,
        };

        const debit_log = await this.finswich_txn_logs_repository.UpdateById(
          debit_logs_exist.id,
          update_data,
        );

        return debit_log;
      }

      throw new authException(en['token-verification-error']);
    } catch (error) {
      console.log(error);
      throw new authException(error.message);
    }
  }

  async FinswichHashMessageAuthCode(body, secret_key) {
    let bdy = typeof body == 'object' ? JSON.stringify(body) : body;
    const crypto = require('crypto');
    const verify_hmac = crypto
      .createHmac('sha512', secret_key)
      .update(bdy)
      .digest('hex');
    return verify_hmac;
  }

  async AuthorizeFinswich(public_key, signature, txn_ref, auth_code) {
    return new Promise((resolve, reject) => {
      const request = require('request');

      const options = {
        method: 'POST',
        url: "https://finswichstaging.instance.exchange/api/v1/app-transfer/wallet",
        headers: {
          'x-finswich-signature': signature,
          'x-public-key': public_key,
          'content-type': 'application/json',
        },
        body: { txn_reference: txn_ref, auth_code },
        json: true,
      };

      request(options, function (error, response, body) {
        if (error) {
          reject(error);
        }
        resolve(body);
      });
    });
  }  
        
        
        

FOR INCOMING TRANSACTIONS/FUNDING

An incoming transaction is a kind of transaction where users on your platform receives money between into wallet from other wallets, banks or other countries or a funding activity. This is what we refer to as an incoming transactions.

BEFORE INTEGRATION - DO THIS

Before you begin any integration you need to have created an incoming URL endpoint where all request between Finswich & your application will take place for interactions and notifications of incoming transactions. See Below for Image reference

TRANSACTION FLOW EVENTS

  1. User Category: this is used to ensure that the recipient user has the right policy setup to receive the incoming transaction

Req Body

{"event":"USER_CATEGORY","data":{"user_reference":"saudiemcode001","mode":"LIVE"}}

Sample Code on third party

      if (body.event === 'USER_CATEGORY') {

          const user = await this.user_repository.FindById(
            body.data.user_reference,
            [
              '-permissions',
              '-settings',
              '-recovery_keys',
              '-contigency_lock_expires',
              '-contigency_level',
              '-verification_code',
            ],
          );

          return {
            status: 'success',
            message: 'User category sent!',
            data: {
              user_category: body.data.user_reference,
              user,
            },
          };
        }

Credit Transactions Events -Once there is a credit to your Finswich wallet, the notifications will be posted to your incoming webhook URL. you can use the data to update your user’s wallet balance on your application.

Req Body

{"event":"CREDIT_TRANSACTION","data":{"user_reference":"saudiemcode001","amount"10,"currency":"sar","transaction_reference":"FIN-DXVb1CpC6jnTXkqJ1682607236","quarantine":false,"mode":"LIVE"}}
 if (body.event === 'CREDIT_TRANSACTION') {
          //! consider crediting your wallet here
          const credit_model = {
            txn_reference: body.data.txn_reference,
            user_reference: body.data.user_reference,
            amount: body.data.amount,
            currency: body.data.currency,
          };

          const wallet = await this.wallet_repository.FindByUserId(
            credit_model.user_reference,
          );

          if (!wallet) {
            throw new notFoundException(en['wallet-not-found']);
          }

          await this.finswich_txn_logs_repository.CreateTxnLogs({
            txn_reference: credit_model.txn_reference,
            amount: credit_model.amount,
            currency: credit_model.currency,
            user: credit_model.user_reference,
            wallet_id: wallet.id,
            type: 'incoming',
            status: 'success',
          });

          await this.wallet_repository.IncreaseBalance(
            credit_model.user_reference,
            credit_model.amount,
          );

          await this.wallet_repository.UpdateById(
            wallet.id,
            {},
            { total_incoming: credit_model.amount },
          );

          return {
            status: 'success',
            message: en['user-account-credited-success'],
          };
        } else {
          throw badRequestException(en['user-account-credited-failure']);
        }

Last updated