VIVO小游戏支付流程如下:
1.前端拉起商品列表
用户登录app后,进入商品购买页面,前端请求app商品列表api接口,获取商品列表信息并展示
2.下单
用户点击'购买'操作,发送给服务端一条当前商品信息,服务端保存该商品的相关订单数据,并返回给前端vivo支付所需要的请求数据,前端调用vivo支付sdk,发起支付请求
/**
* vivo支付下单
* @param array $params 订单信息
* @return array
*/
public static function Pay($params)
{
//获取商品内部订单号
$innerOno = self::genInnerOno(date("Y-m-d H:i:s"), $params['user_id'], $paymentType);
//生成订单
$order = new Order();
$order->order_id = $innerOno; // 内部订单号
$order->product_name = $params['name']; // 商品名称
$order->number = $params['num']; // 购买商品个数
$order->price = $params['price']; // 商品单价, 1个商品价格
$order->total_price = $params['total_price']; // 总的价格
$order->pay_amount = $params['pay_amount']; // 应付总额: 实际应该支付的价格= 总金额 - 优惠金额
$order->count = $params['count']; // 购买后得到商品对应的产品数量
$order->status = 0; // 状态:0 待支付
$order->user_id = $params['user_id']; // 用户游戏ID
$order->expired_at = $params['expired_at']; // 过期时间
$order->created_by = $params['created_by']; // 创建者
$order->updated_by = $params['updated_by']; // 创建者
$result = $order->save();
if (!$result) {
$errors = '';
if ($order->hasErrors()) {
$tmp = $order->getErrors();
foreach ($tmp as $rows) {
foreach ($rows as $row) {
$errors .= $row . '<br/>';
}
}
}
throw new \Exception($errors);
}
$orderId = $order->id;
//构建vivo支付所需要的数据
$title = $order->product_name;
//过滤掉特殊字符
$title = replaceStr($title);
$config = Yii::$app->params['vivo'];
$price = $order->pay_amount;
//构建向oppo发送的参数
$data = [
'appId' => $config['app_id'], //appId:由开发者平台申请得到
'cpOrderNumber' => $model->order_id, //商户订单号,务必保证唯一
'productName' => $title, //商品名(不能含有+号等特殊符号
'productDesc' => $title, //商品描述(不能含有+号等特殊符号)
//消费总金额,单位为分,传整数,如商品价格为6元则要传“600”, “600.0”则会报错,传“6”则会有不必要的损失
'orderAmount' => $price,
'extuid' => $user['wx'], // 用户的openId,登录后可以得到,必传
'notifyUrl' => $config['notify_url'], //回调地址
];
$pay = new Pay();
// 签名
$data['vivoSignature'] = $pay->getSign($data, $config['secret']);
return jsonFail($data);
}
/**
* 生成本系统内部订单号, 在向第三方服务商发起支付时需要使用.
*
* 下单时的处理流程:
* 1. 属性 ono 不要赋值, 成功插入(保存)订单.
* 1. 支付前生成本系统内部订单号, 向第三方发起支付请求.
* 1. 接收到支付成功回调通知时, 将第三方订单号保存到本次付款的订单的 ono 字段上.
*
* 格式: 下单时间+支付方式+ 4位数字修正值. 如 "20150930140041+1+1234".
* 最大长度 32 个字符. 这也是 wx 可接受的订单号最大长度.
*
* @param int $createdAt 订单创建时间戳, 即 下单时间戳.
* @param int $createdBy 订单创建人 ID, 即 下单会员 ID.
* @return string
* @throws \Exception
*/
public static function genInnerOno($createdAt, $createdBy)
{
$createdAt = strtotime($createdAt);
if (1 > $createdAt || 1 > $createdBy) {
throw new \Exception('参数错误');
}
// 生成一个修正值, 一定程度上增加订单号的随机性.
$tmp = intval(substr($createdAt, -4)) + intval(substr($createdBy, -2));
if (4 < strlen($tmp)) {
$tmp = substr($tmp, -4);
}
$tmp = date('YmdHis', $createdAt) . $tmp . rand(100, 900);
return $tmp;
}
3. 支付
前端获取服务端返回的支付所需要的请求数据后,拼接支付请求参数,调用vivo支付sdk,发起支付请求
4. 回调操作
前端支付成功后,vivo会请求服务端的回调方法,对支付订单进行校验验签操作,根据验签结果,处理订单业务逻辑
php以yii2框架为参考
/**
* vivo支付回调
*/
public function actionNotify()
{
//获取回调通知数据
$notifyData = $_POST;
//判断通知请求
$trans = Yii::$app->db->beginTransaction();
try {
//签名校验
$config = Yii::$app->params['vivo'];
// 检查回调参数是否正确
if (empty($notifyData['signature']) || $notifyData['respCode'] != 200
|| $notifyData['tradeStatus'] != '0000') { //回调参数错误
return "fail";
}
// 对回调数据进行验签
$pay = new Pay();
$sign = $pay->getSign($notifyData, $config['secret']);
if ($notifyData['signature'] != $sign) { //回调数据验签失败
return "fail";
}
$order_id = $notifyData["cpOrderNumber"]; //商户自定义的订单号
$order_number = $notifyData['orderNumber'];//交易流水号:vivo生成的交易单号
$paid_at = strtotime($notifyData['payTime']);//交易时间
// 订单状态
$status = UserOrder::STATUS_BUY;
//处理订单业务逻辑
//判断订单状态是否已经支付
$userOrder = UserOrder::find()
->where([
'status' => $status,
'order_id' => $order_id
])
->exists();
if ($userOrder) {
throw new \Exception('订单已支付');
}
// 变更订单属性
UserOrder::changeAttribute(
['order_id' => $order_id],
[
'status' => $status,
'paid_at' => $paid_at,
'order_number' => $order_number,
]
);
$trans->commit();
return "success"; // 通知vivo回调成功
} catch (\Exception $e) {
$trans->rollBack();
}
}
5.上面需要使用到的公共方法
/**
* 操作成功
* @example1 jsonSuccess();
*/
function jsonSuccess($data = NULL, $message = '操作成功')
{
header("Content-Type:application/json");
$json = array();
$json['code'] = 0;
$json['data'] = $data;
$json['message'] = $message;
$json['request_time'] = now();
echo Json::encode($json);
exit();
}
/**
* 错误信息
* @example1 jsonFail();
* @example1 jsonFail("删除失败!");
* @example2 jsonFail($model->getErrors());
*/
function jsonFail($message = '请求失败', $code = 1, $data = null)
{
header("Content-Type:application/json");
$json = array();
$json['code'] = $code;
$json['data'] = $data;
$json['message'] = $message;
$json['request_time'] = now();
echo Json::encode($json);
exit();
}
/**
* 过滤掉特殊的字符
* @param $str
* @param string $replacement
* @return string|string[]|null
*/
function replaceStr($str, $replacement = '')
{
$regex = "/\/|\~|\,|\。|\!|\?|\“|\”|\【|\】|\『|\』|\:|\;|\《|\》|\’|\‘|\ |\·|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\+|\{|\}|\:|\<|\>|\?|\[|\]|\,|\.|\/|\;|\'|\`|\-|\=|\\\|\|/";
$result = preg_replace($regex, $replacement, $str);
return $result;
}
Yii2 VIVO相关配置:params-local.php
<?php
return [
//vivo配置
'vivo' => [
'app_id' => '', //应用ID
//异步通知地址
'notify_url' => "https://xxx/notify",
'pay_test' => 1, //是否支付测试
'is_screen_pay' => 0, //是否屏蔽支付
],
];
VIVO支付签名校验
<?php
/**
* vivo支付签名校验
*/
namespace common\vivo;
/**
* Class Pay
* @package common\vivo
*/
class Pay
{
/**
* 对数据进行签名
*
* @param $data array 需要签名的数据
* @param $secret string 小游戏支付密钥
*
* @return string
*/
public function getSign($data, $secret)
{
unset($data['signature'], $data['signMethod'], $data['extuid']); // 不参与签名字段
ksort($data);
$signStr = urldecode(http_build_query(array_filter($data, 'strlen'))).'&'.md5($secret);
return md5($signStr);
}
}