小游戏支付

未匹配的标注

0 类成员变量

/**
 * 签名生成的时间戳timestamp和下单的时间戳要一样
 *
 * @var string 时间戳,毫秒数
 */
private $timestamp = null;

/**
 * @var string CP游戏在开放平台申请的appId
 */
private $appId = null;

/**
 * @var string 用户在oppo的身份标识(登录时传入的token)
 */
private $openId = null;

/**
 * @var string 游戏上架时分配的Key
 */
private $appKey = null;

/**
 * @var string 平台分配的支付公钥,用于[支付回调]的验签
  */
private $publicKey = null;

/**
 * 私钥获取
 * https://cdofs.oppomobile.com/cdo-activity/static/201810/26/quickgame/documentation/#/pay/pay-secret-key
 *
 * @var null 统一下单时,使用私钥对数据进行签名
 */
private $privateKey = null;

/**
 * 初始化值
 */
public function __construct()
{
    $this->timestamp  = time().'000';
    $this->appId      = '这里填你申请到的appId';
    $this->openId     = '这里是获取到登录用户的标识';
    $this->appKey     = '这里填游戏上架时分配的Key';
    $this->publicKey  = '这里填平台分配的支付公钥';
    $this->privateKey = '这里填开发者私钥';
}

1 组装请求参数

文档地址:cdofs.oppomobile.com/cdo-activity/...
由支付文档可知必填的参数有哪些,以下是组装好的请求参数:

/**
 * 组装统一下单请求数据
 */
public function getOrderData()
{
    $postData = [
        'appId'         => $this->appId,                        // CP游戏在开放平台申请的appId
        'openId'        => $this->openId,                       // 用户在oppo的身份标识(登录时传入的token)
        'timestamp'     => $this->timestamp,                    // 时间戳,毫秒数
        'productName'   => '商品名称',                           // 商品名称
        'productDesc'   => '商品描述',                           // 商品描述
        'count'         => 1,                                   // 商品数量
        'currency'      => 'CNY',                               // 币种
        'price'         => 100,                                 // 商品价格,以分为单位
        'attach'        => $this->appId,                        // 附加信息
        'callBackUrl'   => 'https://your.domain.name/notify',   // 回调地址
        'cpOrderId'     => time(),                              // 订单号
        'appVersion'    => '1.6.4',                             // 游戏版本
        'engineVersion' => 1090,                                // 快应用引擎版本
    ];

    $postData['sign'] = $this->generateOppoSign($postData);     // 使用开发者私钥计算签名
    return $postData;
}
  • generateOppoSign:使用私钥对数据进行签名,具体代码见 1.1。

1.1 对请求统一下单的数据签名

/**
 * 使用私钥对请求统一下单的数据进行签名
 *
 * @param $postData array 需要签名的数据
 *
 * @return string
 */
public function generateOppoSign($postData)
{
    // 排序,拼接
    ksort($postData);
    $signStr = urldecode(http_build_query($postData));

    // 格式化私钥
    $pkResource = $this->formatPrivateKey($this->privateKey);

    // 签名
    openssl_sign($signStr, $signature, $pkResource, OPENSSL_ALGO_SHA256);
    return base64_encode($signature);
}


/**
 * 格式化私钥
 * PKCS#8通用密钥格式:
 * -----BEGIN PRIVATE KEY-----
 * 64字节长度的 base64 内容
 * -----END PRIVATE KEY-----
 */
private function formatPrivateKey($privateKeyString)
{
    $formatedStr = $privateKeyString;

    //  字符串没有以标准格式头开始时,认为是非格式化的
    if (false === strpos($formatedStr, '-----BEGIN PRIVATE KEY-----')) {
        $formatedStr = "-----BEGIN PRIVATE KEY-----\n";
        $formatedStr .= wordwrap($privateKeyString, 64, "\n", true);
        $formatedStr .= "\n-----END PRIVATE KEY-----";
    }

    $key = openssl_get_privatekey($formatedStr);
    return $key;
}

2 请求统一下单

/**
 * 获取统一下单预支付订单号
 *
 * @param $postData array 请求数组
 *
 * @return false|mixed
 */
private function postCurlPreOrder($postData)
{
    $ch = curl_init('https://jits.open.oppomobile.com/jitsopen/api/pay/v1.0/preOrder');
    curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-type: application/json']);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($postData, JSON_UNESCAPED_UNICODE));
    $output = curl_exec($ch);
    curl_close($ch);

    return json_decode($output, true) ? : false;
}

如果请求参数没错的话,会返回如下结果:

{
    "code": "200",
    "msg": "success",
    "data": {
        "appId": "xxxxxxx",
        "cpOrderId": "1630986023",
        "orderNo": "GC202109071140136120700711111"
    }
}

3 调起支付

文档地址:cdofs.oppomobile.com/cdo-activity/...
组装前端调起支付需要的数据,如下所示:

/**
 * 组装前端发起支付需要的数据
 *
 * @param $orderNo string 预支付订单号
 *
 * @return array
 */
private function getPayData($orderNo)
{
    $signData = [
        'timestamp' => $this->timestamp,                    // 时间戳,毫秒数,与统一下单的时间戳一致
        'orderNo'   => $orderNo,                            // 下单订单号,步骤 2 中返回的 orderNo
        'appKey'    => $this->appKey,                       // 游戏上架时分配的Key
    ];
    $paySign  = $this->generateOppoSign($signData);         // 使用开发者私钥进行签名

    return [
        'appId'     => $this->appId,
        'token'     => $this->openId,
        'timestamp' => $this->timestamp,
        'orderNo'   => $orderNo,
        'paySign'   => $paySign,
    ];
}
  • generateOppoSign():对请求参数使用私钥进行签名,具体代码见 1.1。

4 支付回调

文档地址:cdofs.oppomobile.com/cdo-activity/...
用户支付完成后,OPPO 小游戏平台会通知商户,商户需要在接受数据、处理数据后,返回应答。
回调地址是在步骤1中的callBackUrl字段填写的。

public function notify()
{
    $signData = [
        'notifyId'     => request()->param('notifyId'),     // 回调通知 ID(该值使用系统为这次支付生成的订单号)
        'partnerOrder' => request()->param('partnerOrder'), // 订单号
        'productName'  => request()->param('productName'),  // 商品名称
        'productDesc'  => request()->param('productDesc'),  // 商品描述
        'price'        => request()->param('price'),        // 商品价格(以分为单位)
        'count'        => request()->param('count'),        // 商品数量(一般为 1)
        'attach'       => request()->param('attach'),       // 附加参数:值为appid
        'paymentWay'   => request()->param('paymentWay'),   // 支付方式
        'payResult'    => request()->param('payResult'),    // 支付结果
        'sign'         => request()->param('sign'),         // 签名
    ];

    // 检查回调参数是否正确
    if (empty($signData['attach']) || empty($signData['sign'])) {
        return "result=FAIL&resultMsg=回调参数格式校验失败";
    }

    // 对回调数据进行验签
    $verifyRes = $this->verifyNotifyData($signData);
    if ($verifyRes == false) {
        return 'result=FAIL&resultMsg=验签失败';
    }

    // TODO:按照业务更新订单支付状态等等
    return 'result=OK&resultMsg=成功';
}
  • verifyNotifyData():对回调数据进行验签,具体代码见 4.1。

4.1 对回调数据进行验签

/**
 * 对支付回调数据进行验签
 *
 * @param $signData array 回调数据
 *
 * @return false|int
 */
public function verifyNotifyData($signData)
{
    ksort($signData);
    $sign = $signData['sign'];
    unset($signData['sign']);  // 不参与验签的字段
    $verifyStr = urldecode(http_build_query($signData));

    $pkResource = $this->formatPublicKey($this->publicKey);
    return openssl_verify($verifyStr, base64_decode($sign), $pkResource, OPENSSL_ALGO_SHA256);
}


/**
 * 格式化公钥
 * PKCS#8通用密钥格式:
 * -----BEGIN PUBLIC KEY-----
 * 64字节长度的 base64 内容
 * -----END PUBLIC KEY-----
 */
private function formatPublicKey($_str)
{
    $formatedStr = $_str;

    //  字符串没有以标准格式头开始时,认为是非格式化的
    if (false === strpos($formatedStr, '-----BEGIN PUBLIC KEY-----')) {
        $formatedStr = "-----BEGIN PUBLIC KEY-----\n";
        $formatedStr .= wordwrap($_str, 64, "\n", true);
        $formatedStr .= "\n-----END PUBLIC KEY-----";
    }
    $key = openssl_pkey_get_public($formatedStr);
    return $key;
}

如果文章有帮到你的话,别忘了点赞收藏噢 :smile:

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~