OPPO手机支付流程如下:
1.前端拉起商品列表
用户登录app后,进入商品购买页面,前端请求app商品列表api接口,获取商品列表信息并展示
2.下单
用户点击'购买'操作,发送给服务端一条当前商品信息,服务端保存该商品的相关订单数据,并返回给前端oppo支付所需要的请求数据,前端调用oppo支付sdk,发起支付请求
/**
* oppo支付下单
* @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;
//构建oppo支付所需要的数据
$title = $order->product_name;
//过滤掉特殊字符
$title = replaceStr($title);
$config = Yii::$app->params['oppo'];
$price = $order->pay_amount;
//构建向oppo发送的参数
$data = [
'order' => $model->order_id, //商户订单号,务必保证唯一
'amount' => $price, //消费总金额,单位为分
'productName' => $title, //商品名(不能含有+号等特殊符号
'productDesc' => $title, //商品描述(不能含有+号等特殊符号)
'callbackUrl' => $config['notify'], //回调地址
];
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. 支付
前端获取服务端返回的支付所需要的请求数据后,拼接支付请求参数,调用oppo支付sdk,发起支付请求
4. 回调操作
前端支付成功后,oppo SDK 服务器会根据开发上传的回调地址请求服务端的回调方法(回调方法为 HTTP POST。 Content-type:application/x-www-form-urlencoded),对支付订单进行校验验签操作,根据验签结果,处理订单业务逻辑
php以yii2框架为参考
/**
* oppo支付回调
*/
public function actionNotify()
{
//获取回调通知数据
$inBodyArray = $_POST;
//判断通知请求
$trans = Yii::$app->db->beginTransaction();
try {
//验证签名
//参数名 类型 长度限制 说明
//notifyId string 50 回调通知 ID(该值使用系统为这次支付生成的订单号)
//partnerOrder string 100 开发者订单号(客户端上传)
//productName string 40 商品名称(客户端上传)
//productDesc string 120 商品描述(客户端上传)
//price int 商品价格(以分为单位)
//count int 商品数量(一般为 1)
//attach string 请求支付时上传的附加参数(客户端上传)
//sign string 签名
$params['notifyId'] = $inBodyArray["notifyId"];
$params['partnerOrder'] = $inBodyArray["partnerOrder"];
$params['productName'] = $inBodyArray["productName"];
$params['productDesc'] = $inBodyArray["productDesc"];
$params['price'] = $inBodyArray["price"];
$params['count'] = $inBodyArray["count"];
$params['attach'] = $inBodyArray["attach"];
$params['sign'] = $inBodyArray["sign"]; //签名
//校验签名
OppoPay::signCheck($params);
// 处理业务逻辑
$order_number = $inBodyArray["notifyId"]; //回调通知 ID(该值使用系统为这次支付生成的订单号)
$order_id = $inBodyArray['partnerOrder']; //商户订单号(商户内部生成的订单号)
// 订单状态
$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' => time(), //支付时间戳
'order_number' => $order_number,
]
);
$trans->commit();
$result = [
'result' => "OK",
'resultMsg' => '',
];
} catch (\Exception $e) {
$result = [
'result' => "FAIL",
'resultMsg' => $e->getMessage(),
];
$trans->rollBack();
}
echo json_encode($result);
}
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 OPPO相关配置:params-local.php
<?php
return [
//oppo配置
'oppo' => [
'app_id' => 'xxx', //app id
'app_key' => 'xxx', //app_key
'app_secret' => 'xxx',//app_secret
//oppo异步通知地址
'notify' => "http://xxx/notify",
'pay_test' => 1, //是否支付测试
'is_screen_pay' => 1, //是否屏蔽支付
],
];
OPPO支付签名校验
<?php
/**
* oppo支付签名校验
*/
namespace oppo;
/**
* Class Pay
* @package oppo
*/
class Pay
{
/**
* @param $params
* @throws \Exception
*/
public static function signCheck($params)
{
$result = self::rsa_verify($params);
if ($result != 1) { //验证失败
throw new \Exception($result);
}
}
/**
* 校验签名
* @param $contents
* @return int
*/
public static function rsa_verify($contents)
{
$str_contents = "notifyId={$contents['notifyId']}&partnerOrder={$contents['partnerOrder']}&productName={$contents['productName']}&productDesc={$contents['productDesc']}&price={$contents['price']}&count={$contents['count']}&attach={$contents['attach']}";
$publickey = ''; //公钥
$pem = chunk_split($publickey, 64, "\n");
$pem = "-----BEGIN PUBLIC KEY-----\n" . $pem . "-----END PUBLIC KEY-----\n";
$public_key_id = openssl_pkey_get_public($pem);
$signature = base64_decode($contents['sign']);
return openssl_verify($str_contents, $signature, $public_key_id);//成功返回1,0失败,-1错误,其他看手册
}
}