NAV

Mobile - Mobile Web to App

Overview Demo API

INTEGRATION STEPS

  1. End-users select ZaloPay Payment method on the Merchant's Mobile Web.
  2. Merchant send a request to create order to ZaloPay. ZaloPay returns the order information to Merchant side.
  3. Merchant's Web ask user to launch the ZaloPay app to perform payment steps.
  4. Merchant server should  query for order's status  when haven't received ZaloPay's callback yet
  5. Merchant needs to handle  callback  from ZaloPay when End-User pay success.
  6. If payment complete, ZaloPay will relaunch the Merchant's Mobile Web to display the transaction results.

Description

When user buy goods on the Merchant App and choose ZaloPay Payment method, Merchant's Website will direct users to ZaloPay app to processing payment through Mobile Web to App method. When users have done with payment processing, ZaloPay app will let Merchant server know the final result.

Processing flow

* NOTE
Giá trị Ghi chú
MerchantSite Merchant's web/app.
MerchantServer Merchant's back-end.
ZaloPayServer ZaloPay's back-end.
ZaloPaySite ZaloPay's web/app.

Sequence Flow

API specification

When the Merchant server send a request to create order to ZaloPay server, ZaloPay server will response a redirect link in the orderurl field to Merchant side.

Example:

{
  "return_code": 1,
  "return_message": "Giao dịch thành công",
  "sub_return_code": 1,
  "sub_return_message": "Giao dịch thành công",
  "zp_trans_token": "AC-YGb2kvGFAmLzTmQD-PdjA",
  "order_url": "https://qcgateway.zalopay.vn/openinapp?order=eyJ6cHRyYW5zdG9rZW4iOiJBQy1ZR2Iya3ZHRkFtTHpUbVFELVBkakEiLCJhcHBpZCI6MjAwMDAwfQ==",
  "order_token": "AC-YGb2kvGFAmLzTmQD-PdjA",
  "qr_code": "00020101021226520010vn.zalopay0203001010627000503173916458628013369138620010A00000072701320006970454011899ZP23237O000015410208QRIBFTTA5204739953037045405690005802VN622108173916458628013369163043895"
}

When users do a payment processing, merchant website call the redirect link like below to direct users to ZaloPay Payment site.

Redirect link (orderurl value) is the link for ZaloPay App do a payment process purpose.

Redirect

After the user has finished paying, they will be redirected to the Merchant page (according to RedirectURL Merchant provided to ZaloPay) to display the results.

The query string data when ZaloPay redirect to the Merchant page

Parameter Datatype Description
appid int appid of order
apptransid String apptransid of order
pmcid int Payment channel
bankcode String Bank code
amount long Amount of order (VND)
discountamount long Discount (VND)
status int Error code
checksum String Use to check redirect is valid or not
HMAC(hmac_algorithm, key2, appid +"|"+ apptransid +"|"+ pmcid +"|"+ bankcode +"|"+ amount +"|"+ discountamount +"|"+ status)

Sample code

/*
    ASP.Net core
*/
using Microsoft.AspNetCore.Mvc;
using ZaloPay.Helper; // HmacHelper, RSAHelper, HttpHelper, Utils (download at DOWNLOADS page)
using ZaloPay.Helper.Crypto;

namespace ZaloPayExample.Controllers
{
    [Route("[controller]")]
    [ApiController]
    public class RedirectController: ControllerBase
    {
        private string key2 = "Iyz2habzyr7AG8SgvoBCbKwKi3UzlLi3";

        [HttpGet]
        public IActionResult Get()
        {
            var data = Request.Query;
            var checksumData = data["appid"] +"|"+ data["apptransid"] +"|"+ data["pmcid"] +"|"+ 
                data["bankcode"] +"|"+ data["amount"] +"|"+ data["discountamount"] +"|"+ data["status"]; 
            var checksum = HmacHelper.Compute(ZaloPayHMAC.HMACSHA256, key2, checksumData);

            if (!checksum.Equals(data["checksum"])) {
                return StatusCode(400, "Bad Request");
            }
            else {
                // check if the callback has been received, if not Merchant should use getOrderStatus API to get final result
                return StatusCode(200, "OK");
            }
        }
    }
}
import org.json.JSONObject;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.util.Map;
import java.util.logging.Logger;

@Controller
public class RedirectController {
    private Logger logger = Logger.getLogger(this.getClass().getName());
    private String key2 = "Iyz2habzyr7AG8SgvoBCbKwKi3UzlLi3";
    private Mac HmacSHA256;

    public RedirectController() throws Exception  {
        HmacSHA256 = Mac.getInstance("HmacSHA256");
        HmacSHA256.init(new SecretKeySpec(key2.getBytes(), "HmacSHA256"));
    }

    @GetMapping("/redirect-from-zalopay")
    public ResponseEntity redirect(@RequestParam Map<String, String> data) {

        String checksumData = data.get("appid") +"|"+ data.get("apptransid") +"|"+ data.get("pmcid") +"|"+ data.get("bankcode") +"|"+
                data.get("amount") +"|"+ data.get("discountamount") +"|"+ data.get("status");
        byte[] checksumBytes = HmacSHA256.doFinal(checksumData.getBytes());
        String checksum = DatatypeConverter.printHexBinary(checksumBytes).toLowerCase();

        JSONObject result = new JSONObject();
        if (!checksum.equals(data.get("checksum"))) {
            return ResponseEntity.badRequest().body("Bad Request");
        } else {
            // check if the callback has been received, if not Merchant should use getOrderStatus API to get final result
            return ResponseEntity.ok("OK");
        }
    }
}
// go version go1.11.1 linux/amd64
package main

import (
    "fmt"
    "log"
    "net/http"

    "github.com/zpmep/hmacutil"
)

// App config
var (
    key2 = "Iyz2habzyr7AG8SgvoBCbKwKi3UzlLi3"
)

func main() {
    mux := http.DefaultServeMux
    mux.HandleFunc("/redirect-from-zalopay", func(w http.ResponseWriter, r *http.Request) {
        r.ParseForm()

        data := r.Form
        checksumData := data.Get("appid") + "|" + data.Get("apptransid") + "|" + data.Get("pmcid") + "|" + data.Get("bankcode") + "|" + data.Get("amount") + "|" + data.Get("discountamount") + "|" + data.Get("status")
        checksum := hmacutil.HexStringEncode(hmacutil.SHA256, key2, checksumData)

        if checksum != data.Get("checksum") {
            w.WriteHeader(400)
            fmt.Fprint(w, "Bad Request")
        } else {
            // check if the callback has been received, if not Merchant should use getOrderStatus API to get final result
            fmt.Fprint(w, "Ok")
        }
    })

    log.Println("Server is listening at port :8001")
    http.ListenAndServe(":8001", mux)
}
// Node v10.15.3
const CryptoJS = require('crypto-js');
const express = require('express');
const app = express();

const config = {
  key2: "Iyz2habzyr7AG8SgvoBCbKwKi3UzlLi3"
};

app.get('/redirect-from-zalopay', (req, res) => {
  let data = req.query;
  let checksumData = data.appid + '|' + data.apptransid + '|' + data.pmcid + '|' + data.bankcode + '|' + data.amount + '|' + data.discountamount + '|' + data.status;
  let checksum = CryptoJS.HmacSHA256(checksumData, config.key2).toString();

  if (checksum != data.checksum) {
    res.sendStatus(400);
  } else {
    // check if the callback has been received, if not Merchant should use getOrderStatus API to get final result
    res.sendStatus(200);
  }
});

app.listen(8001, function () {
  console.log('Server is listening at port :8001');
});
<?php

// PHP Version 7.3.3

$key2 = "Iyz2habzyr7AG8SgvoBCbKwKi3UzlLi3";
$data = $GET;
$checksumData = $data["appid"] ."|". $data["apptransid"] ."|". $data["pmcid"] ."|". $data["bankcode"] ."|". $data["amount"] ."|". $data["discountamount"] ."|". $data["status"];
$checksum = hash_hmac("sha256", $checksumData, $key2);

if (strcmp($mac, $data["checksum"]) != 0) {
  http_response_code(400);
  echo "Bad Request";
} else {
  // check if the callback has been received, if not Merchant should use getOrderStatus API to get final result
  http_response_code(200);
  echo "Ok";
}
# ruby 2.5.1p57
# rails 5.2.3

# config/routes.rb
# Rails.application.routes.draw do
#   match '/redirect-from-zalopay' => 'redirect#handle', via: :get
# end

# app/controllers/redirect_controller.rb
require 'json'
require 'openssl'

class RedirectController < ApplicationController 
  def initialize
    super
    @config = {
      key2: 'Iyz2habzyr7AG8SgvoBCbKwKi3UzlLi3'
    }
  end

  # POST /callback
  def handle
    data = request.query_parameters
    checksumData = data["appid"] +"|"+ data["apptransid"] +"|"+ data["pmcid"] +"|"+ data["bankcode"] +"|"+ data["amount"] +"|"+ data["discountamount"] +"|"+ data["status"]
    checksum = OpenSSL::HMAC.hexdigest('sha256', @config[:key2], checksumData)

    if checksum != data['checksum']
      render text: 'Bad Request', status: :bad_request
    else
      # check if the callback has been received, if not Merchant should use getOrderStatus API to get final result
      render text: 'OK', status: :ok
    end
  end
end
# coding=utf-8
# Python 3.6

from flask import Flask, request, json
import hmac, hashlib

app = Flask(__name__)
config = {
  'key2': 'Iyz2habzyr7AG8SgvoBCbKwKi3UzlLi3'
}

@app.route('/redirect-from-zalopay', methods=['GET'])
def redirect():
  data = request.args
  checksumData = "{}|{}|{}|{}|{}|{}|{}".format(data.get('appid'), data.get('apptransid'), data.get('pmcid'), data.get('bankcode'), data.get('amount'), data.get('discountamount'), data.get('status'))
  checksum = hmac.new(config['key2'].encode(), checksumData, hashlib.sha256).hexdigest()

  if checksum != data.get('checksum'):
    return "Bad Request", 400
  else:
    # check if the callback has been received, if not Merchant should use getOrderStatus API to get final result
    return "Ok", 200

if __name__ == '__main__':
  app.run(host='0.0.0.0', port=8001)
No matching results were found