Authentication

OPay utilizes the RSA authentication method to guarantee the security of your data. All API request and response parameters must be encrypted and signed.

Procedures

Step 1: Encrypt your request parameters using OPay’s public key to generate the paramContent, append a timestamp to the end of the paramContent, and sign it with your private key.

Step 2: OPay will verify the signature using your public key and decrypt the paramContent with OPay's private key to retrieve the original request parameters.

Step 3: OPay will process your request.

Step 4: OPay encrypts the response parameters with your public key to create the “data”, appends a timestamp to the end of the data, and signs it with OPay's private key to generate the parameter sign.

Step 5: Verify the signature with OPay's public key and then decrypt the parameters with your private key to obtain your response.

Demo
JSPythonJava
const NodeRSA = require("node-rsa");
const {
  KEYUTIL,
  KJUR,
  hextob64,
  hextob64u,
  RSAKey,
  b64tohex
} = require("jsrsasign");
const axios = require("axios");

// Replace "TODO" with OPay Public Key
const opayPublicKey = `
-----BEGIN PUBLIC KEY-----
todo something...
-----END PUBLIC KEY-----
`;

// Replace "TODO" with Merchant Private Key
const merchantPrivateKey = `
-----BEGIN PRIVATE KEY-----
todo something...
-----END PRIVATE KEY-----
`;

/**
 * Custom RSA
 */
class CustomRsa {
  constructor({ opayPublicKey, merchantPrivateKey }) {
    this.data = null;
    this.opayPublicKey = opayPublicKey || "";
    this.merchantPrivateKey = merchantPrivateKey || "";
  }
  encrypt(data, timestamp) {
    this.data = data || this.data;
    if (!this.data || !timestamp) return "";

    const rsa = new NodeRSA({ b: 1024 });
    rsa.setOptions({ encryptionScheme: { scheme: "pkcs1" } });
    rsa.importKey(this.opayPublicKey, "pkcs8-public-pem");
    const encrypted = rsa.encrypt(
      JSON.stringify(this._traverseData(data)),
      "base64"
    );
    const encryptData = {
      paramContent: encrypted,
      sign: this._setSign(encrypted + timestamp)
    };
    return encryptData;
  }
  decrypt(data) {
    const rsa = new NodeRSA(this.merchantPrivateKey);
    rsa.setOptions({ encryptionScheme: { scheme: "pkcs1" } });
    const decrypted = rsa.decrypt(data.data, "utf8");
    const responseData = JSON.parse(decrypted);
    const { sign } = data;
    delete data.sign;
    return {
      verify: this._verfiySign(sign, this._traverseData(data)),
      data: responseData
    };
  }
  _setSign(inputString = "{}") {
    const rsa = KEYUTIL.getKey(this.merchantPrivateKey);
    const sig = new KJUR.crypto.Signature({
      alg: "SHA256withRSA"
    });
    sig.init(rsa);
    sig.updateString(inputString);
    return hextob64(sig.sign());
  }
  _verfiySign(sign, data) {
    let signatureVf = new KJUR.crypto.Signature({
      alg: "SHA256withRSA",
      prvkeypem: this.opayPublicKey
    });
    let mapSplicing = "";
    for (let k in data) {
      if (mapSplicing) {
        mapSplicing += "&";
      }
      mapSplicing += `${k}=${data[k]}`;
    }
    signatureVf.updateString(mapSplicing);
    return signatureVf.verify(b64tohex(sign));
  }
  _getDataType(data) {
    return Object.prototype.toString.call(data);
  }
  _traverseData(data) {
    let r = null;
    switch (this._getDataType(data)) {
      case "[object Object]":
        r = {};
        const keyArr = Object.keys(data).sort();
        keyArr.forEach(key => {
          const val = data[key];
          r[key] = this._traverseData(val);
        });
        break;
      case "[object Array]":
        result = [];
        data.sort().forEach(item => {
          r.push(this._traverseData(item));
        });
        break;
      default:
        r = data;
    }
    return r;
  }
}

/**
 * Test case
 */
(() => {
  const interfaceParameters = {
    // todo something...
    headMerchantId: "256622092286390",
    merchantId: "256622092286391",
    outOrderNo: "2334345345348734",
    amount: "2200.00",
    currency: "NGN",
    orderExpireTime: 300,
    productInfo: JSON.stringify({
      filmName: "Avatar:The Way of Water",
      filmTitle: "ticket title",
      filmTicketNum: "film ticker number",
      seatNum: "Seat number(multiple seats, concatenate)",
      filmTicketAmount: "100",
      filmFeeAmount: "5",
      filmDate: "movie date 2024-05-12",
      filmTime: "movie showtime, 19:00"
    }),
    isSplit: "N",
    remark: "test film app",
    sceneEnum: "COLLECTION_SDK",
    subSceneEnum: "LOGISTIC",
    sn: "9210264890"
  };

  const crsa = new CustomRsa({
    opayPublicKey,
    merchantPrivateKey
  });
  const timestamp = new Date().getTime();
  const encryptData = crsa.encrypt(interfaceParameters, timestamp);

  console.info("encryptData ==>", encryptData);
  axios({
    method: "POST",
    url: "https://payapi.opayweb.com/openApi/order/checkout/createOrder",
    headers: {
      "Content-Type": "application/json",
      version: "V1.0.1",
      bodyFormat: "JSON",
      clientAuthKey: "9a2ca7297afd493a84d7a538a04e210m", // todo something...
      timestamp: timestamp
    },
    data: encryptData
  })
    .then(res => {
      console.info("res ==>", res.data);
      const { code, message } = res.data;
      if (code == "00000") {
        const { verify, data } = crsa.decrypt(res.data);
        console.log("Verify sign:", verify); // Verify sign: true
        console.log("Decrypt response data:", data); // Decrypt response data: { orderNo: '20240314998765432101' }  
      } else {
        throw new Error(message);
      }
    })
    .catch(err => {
      console.log("err ==>", err);
    });
})();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178

How to generate your own RSA keys

An RSA key pair comprises a private key and a public key. The private key is required for creating digital signatures, while the public key is used to authenticate these signatures. Various tools can generate your RSA key pair, but the following steps are specifically tailored for using OpenSSL as a reference.

Step 1. Generate the private key
openssl genrsa -out client_private_key_php_dotnet.pem 1024

Step 2. If you are a Java developer, convert the private key to PKCS8 format
openssl pkcs8 -topk8 -inform PEM -in client_private_key_php_dotnet.pem -outform PEM -nocrypt -out client_private_key_pkcs8.pem

Step 3. Generate the public key
openssl rsa -in client_private_key_php_dotnet.pem -pubout -out client_public_key_php_dotnet.pem

Step 4. Generate the private key that can be used in Java
cat client_private_key_pkcs8.pem | grep -v "^-" | tr -d "\n" | sed 's/%$//' > client_private_key_java.pem

Step 5. Generate the public key that can be used in Java
cat client_public_key_php_dotnet.pem | grep -v "^-" | tr -d "\n" | sed 's/%$//' > client_public_key_java.pem

Last Updated: 11/7/2024, 2:29:28 AM