小游戏支付
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 组装请求参数#
由支付文档可知必填的参数有哪些,以下是组装好的请求参数:
/**
* 组装统一下单请求数据
*/
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 调起支付#
组装前端调起支付需要的数据,如下所示:
/**
* 组装前端发起支付需要的数据
*
* @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 支付回调#
用户支付完成后,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;
}
如果文章有帮到你的话,别忘了点赞收藏噢