接入指南 接入指南
  • V2 (opens new window)
  • V3 (opens new window)
  • V4-English (opens new window)
  • V4-经典版 (opens new window)
  • 接入指南
    • 接入准备
    • 快速启动
    • 开发指南
    • 接入方式
    • 工具
    • 附录
    • 术语
  • API文档
    • checkout
    • dispute
    • Tokenization
    • 异步通知
    • Marketplaces
    • Terminal integration API
  • Marketplaces
  • Accept in-person payments with Terminal
  • 覆盖国家
  • 风险管理
  • 对账服务
  • 支付方式
  • V2 (opens new window)
  • V3 (opens new window)
  • V4-English (opens new window)
  • V4-经典版 (opens new window)
  • 开发指南

    • API调用说明
    • 如何生成请求签名
      • 签名类型
      • 待签名字符串组装
      • 计算签名值
      • 签名工具类
      • 验签工具类
    • 支付通知接入说明
    • 最佳实践

      • klarna OSM
      • 如何获取订单状态
      • 换汇说明
      • 交易幂等
      • 交易挽回
      • APM 接入方式实践
      • 特殊支付方式退款
    • 常见问题

      • 集成问题
  • v4

签名规约

# 签名规约

PingPongCheckout API v4通过验证签名来保证请求的真实性和数据的完整性。

注意

PingPongCheckout API v4中accId,clientId,signType,version,bizContent均参与签名,如果之前对接的是v2或者v3版本要升级到v4,需要按照新的签名规则进行对接。

# 签名类型

签名类型 描述
MD5 表示选择 MD5 算法,商户使用 Salt 对报文进行摘要签名和验签
SHA256 表示选择 SHA256算法,商户使用 Salt 对报文进行摘要签名和验签

# 待签名字符串组装

  1. 获取所有 post 请求的内容,剔除 sign 字段;
  2. 按照第一个字符的键值 ASCII 码递增排序(字母升序排序);
  3. 将排序后的参数与其对应值,组合成 参数=参数值 的格式,并且把这些参数用 & 字符连接起来,然后把签名秘钥(salt)放入待签名字符串的开头 , 即signContent = {salt}key1=val2&key2=val2&key3=val3,此时获取到的为完整的待签名字符串;

# 计算签名值

推荐使用SHA256签名方式,安全度高于MD5

签名过程将根据请求体中的数据生成签名值,并将其添加到请求体中。签名值是使用 salt、请求参数和签名方法计算出来的字符串,取决于具体的签名方法(MD5 或 SHA256)。签名值将用于验证请求的来源和完整性。

# 签名工具类

    import com.alibaba.fastjson.JSONObject;
    import org.apache.commons.codec.digest.DigestUtils;
    import org.apache.commons.lang3.StringUtils;
    
    import java.security.MessageDigest;
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    
    /**
     * 这个类用于对请求内容进行签名,以确保请求的安全性。
     */
    public class PingPongCheckoutClient {
    
        private final String salt;  // 盐值,用于增加签名的复杂度
        private final SignAlgorithm signAlgorithm; // 签名算法枚举
    
        /**
         * 构造函数,用于初始化盐值和签名算法。
         *
         * @param salt          盐值
         * @param signAlgorithm 签名算法
         */
        public PingPongCheckoutClient(String salt, SignAlgorithm signAlgorithm) {
            this.salt = salt;
            this.signAlgorithm = signAlgorithm;
        }
    
        /**
         * 对请求内容进行签名,并将签名结果添加到请求参数中。
         *
         * @param requestBody 请求内容
         * @return 添加了签名结果的请求参数
         */
        public JSONObject signRequest(JSONObject requestBody) {
            String sign = getSign(salt, signAlgorithm, requestBody);  // 获取请求内容的签名
            requestBody.put("sign", sign);  // 将签名结果添加到请求参数中
            return requestBody;
        }
    
        /**
         * 获取请求内容的签名结果。
         *
         * @param salt          盐值
         * @param signAlgorithm 签名算法
         * @param requestBody   请求内容
         * @return 请求内容的签名结果
         */
        public static String getSign(String salt, SignAlgorithm signAlgorithm, JSONObject requestBody) {
            StringBuilder stringBuilder = new StringBuilder();
            List<String> keys = new ArrayList<>(requestBody.keySet());
            Collections.sort(keys);  // 对请求参数的键进行升序排序
    
            for (String key : keys) {
                Object valueObject = requestBody.get(key);
                // 剔除空值
                if (valueObject == null) {
                    continue;
                }
                // 剔除非字符串类型的值
                if (!(valueObject instanceof String)) {
                    throw new IllegalArgumentException("request body illegal");
                }
    
                String value = (String) valueObject;
                if (StringUtils.isNotBlank(value)) {
                    stringBuilder.append(key).append("=").append(value).append("&");  // 将请求参数的键和值拼接成字符串
                }
            }
            String needSignStr = stringBuilder.toString();
            if (needSignStr.endsWith("&")) {
                needSignStr = needSignStr.substring(0, needSignStr.length() - 1);  // 去掉最后一个 & 符号
            }
    
            String sign = null;
            if (signAlgorithm == SignAlgorithm.MD5) {
                sign = md5Sign(salt, needSignStr);  // 调用 md5Sign 方法对字符串进行 MD5 加密
            } else if (signAlgorithm == SignAlgorithm.SHA256) {
                sign = sha256(salt, needSignStr);  // 调用 sha256 方法对字符串进行 SHA256 加密
            } else {
                throw new IllegalArgumentException("Signature algorithm not supported");  // 如果签名算法不支持,则抛出异常
            }
    
            return sign;  // 返回签名结果
        }
    
        /**
         * 对请求内容进行MD5签名。
         *
         * @param salt    盐值
         * @param content 请求内容
         * @return 请求内容的MD5签名结果
         */
        public static String md5Sign(String salt, String content) {
            try {
                MessageDigest md = MessageDigest.getInstance("MD5");  // 创建 MD5 实例
                md.update(salt.getBytes());  // 将盐值加入到加密中
                md.update(content.getBytes());  // 将请求内容加入到加密中
                byte[] digest = md.digest();  // 获取字节数组
                return byteToHexString(digest);  // 将字节数组转换成十六进制字符串
            } catch (Exception e) {
                throw new RuntimeException("md5签名失败", e);  // 如果加密过程出错,则抛出异常
            }
        }
    
        /**
         * 将字节数组转化为十六进制字符串。
         *
         * @param bytes 字节数组
         * @return 十六进制字符串
         */
        public static String byteToHexString(byte[] bytes) {
            StringBuilder hexString = new StringBuilder();
            for (int i = 0; i < bytes.length; i++) {
                String hex = Integer.toHexString(bytes[i] & 0xFF);
                if (hex.length() == 1) {
                    hex = '0' + hex;
                }
                hexString.append(hex.toUpperCase()); // 将转换后的十六进制字符串拼接起来
            }
            return hexString.toString();  // 返回转换后的十六进制字符串
        }
    
        /**
         * 对请求内容进行SHA256签名。
         *
         * @param salt    盐值
         * @param content 请求内容
         * @return 请求内容的SHA256签名结果
         */
        public static String sha256(String salt, String content) {
            try {
                String contentStr = salt.concat(content);  // 将盐值和请求内容拼接成一个字符串
                return DigestUtils.sha256Hex(contentStr.getBytes("UTF-8")).toUpperCase();  // 调用 DigestUtils.sha256Hex 方法对字符串进行 SHA256 加密,并将加密结果转换成大写的十六进制字符串
            } catch (Exception e) {
                throw new RuntimeException("sha256签名失败", e);  // 如果加密过程出错,则抛出异常
            }
        }
    
        /**
         * 签名算法枚举类。
         */
        public enum SignAlgorithm {
            MD5,
            SHA256
        }
    }
        
    
    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
    <?php
    
    
    namespace App\Service;
    
    /**
     * 这个类用于对请求内容进行签名,以确保请求的安全性。
     */
    
    class PingPongCheckoutClient
    {
        const SignAlgorithm = ['MD5', 'SHA256']; // 签名加密方式
    
        /**
         * 对请求内容进行签名,并将签名结果添加到请求参数中
         *
         * @param string $salt
         * @param array $requestBody
         *
         * @return array
         */
        public function signRequest(string $salt, array $requestBody)
        {
            //参数校验
            $this->validate($requestBody);
            //拼接请求参数
            $signContent = $this->signContent($salt, $requestBody);
            $sign = $this->getSign($signContent, $requestBody);
            $requestBody['sign'] = strtoupper($sign);
            if (is_array($requestBody['bizContent'])) {
                $requestBody['bizContent'] = json_encode($requestBody['bizContent'], JSON_UNESCAPED_SLASHES);
            }
            return $requestBody;
        }
    
        /**
         * 拼接请求参数
         *
         * @param string $salt
         * @param array $requestBody
         * @return array|string
         */
        public function signContent(string $salt, array $requestBody)
        {
            unset($requestBody['sign']);
            if (is_array($requestBody['bizContent'])) {
                $requestBody['bizContent'] = json_encode($requestBody['bizContent'],JSON_UNESCAPED_SLASHES);
            }
            //根据键进行排序
            ksort($requestBody);
            $signContent = urldecode(http_build_query($requestBody));
            $signContent = $salt . $signContent;
            return $signContent;
        }
    
    
        /**
         * 获取请求内容的签名结果。
         *
         * @param string $signContent
         * @param array $requestBody
         * @return array|string
         */
        public function getSign(string $signContent, array $requestBody)
        {
            switch ($requestBody['signType']) {
                case "MD5":
                    $sign = $this->md5Sign($signContent);
                    break;
                case "SHA256":
                    $sign = $this->sha256($signContent);
                    break;
            }
            return $sign;
        }
    
        /**
         * md5 加密
         * 
         * @param string $signContent
         * @return string
         */
        public function md5Sign(string $signContent)
        {
            return md5($signContent);
        }
    
        /**
         * sha256 加密
         *
         * @param string $signContent
         * @return string
         */
        public function sha256(string $signContent)
        {
            return hash("sha256", $signContent);
        }
    
    
        /**
         * 对于参与的签名参数进行校验
         *
         * @param array $requestBody
         * @return true
         * @throws \Exception
         */
        public function validate(array $requestBody)
        {
            $keys = ['accId', 'clientId', 'signType', 'version', 'bizContent'];
            foreach ($keys as $key) {
                if (!isset($requestBody[$key]) || empty($requestBody[$key])) {
                    throw new \Exception('invalid -' . $key);
                }
            }
            if (!array($requestBody['signType'], self::SignAlgorithm)) {
                throw new \Exception('invalid - signType' );
            }
            if (!is_array($requestBody['bizContent']) && !is_string($requestBody['bizContent'])) {
                throw new \Exception('invalid - bizContent' );
            }
            return true;
        }
    }
    
    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
    // Make sure to add code blocks to your code group

    # 验签工具类

      
      <?php
      
      
      namespace App\Service;
      
      /**
       * 这个类用于对请求内容进行验签,以确保请求的安全性。
       */
      
      class PingPongCheckoutDecrypt
      {
          /**
           * 对异步通知内容进行验签
           *
           * @param string $salt
           * @param array $requestBody {"clientId":"2023120712300910285","code":"000000","bizContent":"{\"exchangedCurrency\":\"USD\",\"amount\":\"1.080000\",\"authenticationInfo\":{\"avsResult\":\"Unknown\",\"cvvResult\":\"Y\",\"threeDSecure\":\"N\"},\"cardInfo\":{\"firstName\":\"James\",\"isoCountryA2\":\"RU\",\"lastName\":\"LeBron\",\"lastFourDigits\":\"1112\",\"cardLevel\":\"CLASSIC\",\"paymentBrand\":\"VISA\",\"cardType\":\"CREDIT\",\"issuringBank\":\"\",\"ipCountry\":\"CN\",\"firstSixDigits\":\"401200\",\"isoCountry\":\"RUSSIAN FEDERATION\"},\"issuerInfo\":{\"issuerResultMsg\":\"Successful approval/completion or V .I.P .PIN\\n verification is successful\",\"issuerResultCode\":\"00\"},\"threeDSecure\":\"\",\"remark\":\"Remark customer defined txt\",\"transactionTime\":\"1702349526000\",\"transactionId\":\"2023121250013357\",\"notifyType\":\"RECHARGE\",\"requestId\":\"3931b66e-78ba-4107-d75d-098a005f7bc3\",\"merchantTransactionId\":\"PShop2023121210510ES\",\"paymentMethod\":{\"type\":\"VISA\"},\"currency\":\"USD\",\"exchangedAmount\":\"1.080000\",\"captureDelayHours\":0,\"status\":\"SUCCESS\"}","sign":"B9BFE38FFE24B81FCAA41FC46F5560CB","accId":"2023120712300910285520","description":"Transaction succeeded","signType":"MD5"}
           *
           * @return boolean
           */
          public function decryptRequest(string $salt, string $requestBody)
          {
              //拼接请求参数
              $signData = $this->signContent($salt, $requestBody);
              $sign = $this->getSign($signData['signContent'], $signData['data']);
              if ($signData['sign'] == strtoupper($sign)) {
                  return true;
              } else {
                  return false;
              }
          }
      
      
          /**
           * 拼接请求参数
           *
           * @param string $salt
           * @param array $requestBody
           * @return array|string
           */
          public function signContent(string $salt, string $requestBody)
          {
              //替换特殊字符
              $requestBody = str_replace("\\n", '\\\\n', $requestBody);
              $requestBody = str_replace("\\r", '\\\\r', $requestBody);
              $requestBody = str_replace("\\f", '\\\\f', $requestBody);
              $requestBody = str_replace("\\t", '\\\\t', $requestBody);
              $requestBody = str_replace("\\v", '\\\\v', $requestBody);
              $requestBody = str_replace('\\\\"', '\\\\\\"', $requestBody);
              $data = json_decode($requestBody,true);
              if (!$data) {
                  if (function_exists('json_last_error') && json_last_error() != 0) {
                      $error = json_last_error_msg();
                  } else {
                      $error = 'data does not meet the requirements';
                  }
                  throw new \Exception($error);
              }
              $sign = $data['sign'];
              unset($data['sign']);
              //根据键进行排序
              ksort($data);
              $signContent = urldecode(http_build_query($data));
              $signContent = $salt . $signContent;
              return ['sign' => $sign, 'data' => $data, 'signContent' => $signContent];
          }
      
      
          /**
           * 获取请求内容的签名结果。
           *
           * @param string $signContent
           * @param array $requestBody
           * @return array|string
           */
          public function getSign(string $signContent, array $requestBody)
          {
              switch ($requestBody['signType']) {
                  case "MD5":
                      $sign = $this->md5Sign($signContent);
                      break;
                  case "SHA256":
                      $sign = $this->sha256($signContent);
                      break;
              }
              return $sign;
          }
      
          /**
           * md5 加密
           * 
           * @param string $signContent
           * @return string
           */
          public function md5Sign(string $signContent)
          {
              return md5($signContent);
          }
      
          /**
           * sha256 加密
           *
           * @param string $signContent
           * @return string
           */
          public function sha256(string $signContent)
          {
              return hash("sha256", $signContent);
          }
      
      }
      
      
      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
      // Make sure to add code blocks to your code group
      上次更新: 2025/08/14, 14:08:19

      Previous
      ← API调用说明
      Next
      支付通知接入说明→

      杭州乒乓智能技术有限公司 | Copyright © 2015-2026 checkout.pingpongx.com.All Rights Reserved.
      • 浅色模式