> ## Documentation Index
> Fetch the complete documentation index at: https://developer.cardplus.cc/llms.txt
> Use this file to discover all available pages before exploring further.

> Use the CardPlus sandbox, sign requests, and test APIs.

# Request signing

This page summarizes the CardPlus sandbox access details and a signing example you can use for testing.

## Web login

Open `<SANDBOX_PORTAL_URL>` and sign in with one of the sandbox accounts:

* **Agency account**: `xxxx` / `xxxx`
* **Customer account**: `xxx` / `xxx`

## API base URL

Use the sandbox API base URL:

* `https://xxxxxxxxx`

## Required fields

You will use the following fields for requests:

| Field          | Example      |
| -------------- | ------------ |
| `agencyCode`   | `xxxx`       |
| `appId`        | `xxxx`       |
| `birthday`     | `2003-12-08` |
| `documentType` | `PASSPORT`   |
| `mobile`       | `xxxxxxx86`  |
| `secretKey`    | `xxxxxxxxx`  |

## API example

### Send request

```kotlin theme={null}
private val json = "application/json; charset=utf-8".toMediaType()

private fun sendRequest(requestURL: String, param: Any): Boolean {
    val request = Request.Builder()
        .url(requestURL)
        .post(JSON.toJSONString(param).toRequestBody(json))
        .build()

    val response = okHttpClient.newCall(request).execute()
    response.use {
        if (!it.isSuccessful || it.body == null) {
            return false
        }

        val result = it.body!!.string()
        return result == "SUCCESS"
    }
}
```

## Parameter signature

Generate the MD5 signature using the following rules:

1. Sort and concatenate keys in lexicographic order: `key1=value1&key2=value2`
2. Append the downstream private key: `key1=value1&key2=value2&key=privateKey`
3. Convert the final hash value to uppercase

### Signature example

```kotlin theme={null}
val url = "/wallet/api/user/registerAndKyc"
val requestParam = TreeMap<String, String>()

requestParam["agencyCode"] = "xxxxx"
requestParam["appId"] = "xxxxxxx"
requestParam["appUserId"] = "xxxxxx"
requestParam["birthday"] = "2003-12-08"
requestParam["documentNo"] = "xxxxxxxx"
requestParam["documentType"] = "ID_CARD"
requestParam["email"] = "xxxxxx"
requestParam["firstName"] = "xxxx"
requestParam["lastName"] = "xxxxx"
requestParam["mobile"] = "xxxxxxx"
requestParam["photos"] = "xxxxxxx"
requestParam["region"] = "HK"

// sign
requestParam["sign"] = DSSignUtil.getMD5Hash(requestParam, secretKey)
val result = sendRequest(url, requestParam)
```

## Signature tools

```kotlin theme={null}
object DSSignUtil {
    private const val encodingCharset = "UTF-8"

    /**
     * key1=value1&key2=value2&key=privateKey
     */
    fun getMD5Hash(map: Map<String, Any?>, key: String): String {
        val list = ArrayList<String>()
        map.forEach {
            if (it.value != null) {
                list.add("${it.key}=${it.value}&")
            }
        }

        val arrayToSort = list.toTypedArray()
        Arrays.sort(arrayToSort, CASE_INSENSITIVE_ORDER)

        val sb = StringBuilder()
        for (item in arrayToSort) {
            sb.append(item)
        }

        var result = sb.toString()
        result += "key=$key"
        result = md5(result, encodingCharset)!!.toUpperCase()
        return result
    }

    fun md5(value: String, charset: String?): String? {
        return try {
            val data = value.toByteArray(charset(charset!!))
            val md = MessageDigest.getInstance("MD5")
            val digestData = md.digest(data)
            toHex(digestData)
        } catch (e: NoSuchAlgorithmException) {
            e.printStackTrace()
            null
        } catch (e: UnsupportedEncodingException) {
            e.printStackTrace()
            null
        }
    }

    private fun toHex(input: ByteArray?): String? {
        if (input == null) {
            return null
        }

        val output = StringBuffer(input.size * 2)
        for (i in input.indices) {
            val current: Int = input[i] and 0xff
            if (current < 16) {
                output.append("0")
            }
            output.append(current.toString(16))
        }
        return output.toString()
    }
}
```
