使用uniapp开发字节跳动小程序的微信支付和支付宝支付
发表时间:2021-1-5
发布人:葵宇科技
浏览次数:320
使用uniapp开发字节跳动小程序的微信支付和支付宝支付(后端PHP,tp5)
准备工作
- 微信支付配置
- 支付宝支付配置
- 字节跳动配置
1,微信支付配置:
开通微信支付的h5支付,关联APPID,保存key,商户ID(mch_id ),配置回调域名,记得把字节跳动的回调域名也写进h5支付的域名配置当中:snssdk.com
2,支付宝支付配置:
开通支付宝的app支付,在网页&移动应用中创建一个应用接入app支付,并且签约并且保证生效。我是通过支付宝证书进行支付的,所以支付宝这边需要自己去配置证书并且下载下来,后面要用到。
3,字节跳动配置
开通字节跳动的支付,获得字节跳动的商户号,appid,支付secret
业务代码:
- 后端
- 前端
1,后端
上传参数,通过支付宝和微信配置订单信息传给前端uniapp,,微信和支付宝的回调正确的话,自己配置正确,按自己的情况添加订单到数据库就可以了,这里就不写了。aliUrlZhengshu方法是支付宝支付,unifiedOrder方法是微信支付。
抖音的配置我写在了config.php里面
'douyin'=>[
'appId'=>'*****',
'appSecret'=>'*****',
'url'=>'*****',//抖音授权链接
'payAppId' =>'*****',
'tt_pay_app_secret'=>'*****',//支付secret
'merchant_id'=>'*****',//支付secret
],
public function _initialize()
{
$this->appid = '*****';//微信的appid
$this->mch_id = '*****';//微信的商户id
$this->key = '*****';//key
$this->notify_url = '*****';//自己配置的微信支付回调地址
}
public function payReady(){
$config = config('douyin');
$openid = $this->request->post('openid');
$truePrice = $this->request->post('price');
$video_id = $this->request->post('video_id');
$user_id = $this->request->post('user_id');
$subject = $this->request->post('subject');
$body = $this->request->post('body');
$truePrice = (int)$truePrice;
$tt_result = $this->ttOrder($user_id,$video_id,$truePrice,$subject,$body,$openid);
$out_order_no = $tt_result['out_order_no'];//自定义的订单号
$arr = [
'merchant_id' =>$config['merchant_id'],//字节跳动商户号 前提条件->字节跳动->4 完成填写后
'app_id' => $config['payAppId'],//字节跳动APPID 前提条件->字节跳动->4 完成填写后
'sign_type' => 'MD5',//定死的别动!!!
'timestamp' => strval($tt_result['time']),//需要为字符串类型的时间戳
'version' => '2.0',//定死的别动!!!
'trade_type' => 'H5',//定死的别动!!!
'product_code' => 'pay',//定死的别动!!!
'payment_type' => 'direct',//定死的别动!!!
'out_order_no' => strval($out_order_no),//自定义的订单号
'uid' => $openid,// 用户的openid 登录后可以获取到
'total_amount' => $tt_result['fee'],//金额 这里单位:分
'currency' => 'CNY',//定死的别动!
'trade_no' => $tt_result['out_order_no'],//刚刚获取的字节跳动订单 忘了往上找找
'subject' => $tt_result['subject'],//之前定好的标题
'body' => $tt_result['body'],//之前定好的内容
'trade_time' => strval($tt_result['time']),//一定要和 上面的 timestamp 字段相同
'valid_time' => '3000',//测试留的时间长
'notify_url' => 'https://tp-pay.snssdk.com/paycallback',//定死的别动!!!
];
$aliurl = $this->aliUrlZhengshu($tt_result);//获取 alipay_url
$arr['alipay_url'] = $aliurl;
$wx_res = $this->unifiedOrder($tt_result['out_order_no'],$body,$subject);
if(!empty($wx_res)){
$pay_url = $wx_res;
}
$arr['wx_type'] = 'MWEB';
$arr['wx_url'] = $pay_url;
$stringToBeSigned = $this->getSignContent($arr);//这里待签名处理.方法下面
$sign = md5($stringToBeSigned . $config['tt_pay_app_secret']);//这生成签名咯, 不要乱, 签名好多的
//这两个字段的写入原因: 在待签名字符串 getSignContent 方法中不能有 sign和risk_info 所以在生成签名($sign)之后写入到里面
$arr['sign'] = $sign;
$arr['risk_info'] = json_encode(['ip' => request()->ip()]);
//这两个字段的写入原因: 在待签名字符串 getSignContent 方法中不能有 sign和risk_info 所以在生成签名($sign)之后写入到里面
$res = htmlspecialchars_decode(json_encode($arr));//这里html的编译解析, 防止html编译
// $this->success('返回orderinfo', $res);
$this->success('返回orderinfo', ['list' => $res]);
}
/**
* 获取抖音订单号
*/
public function ttOrder($user_id,$video_id,$order_price,$subject,$body,$openid){
$config = config('douyin');
$time = time();
// if(!$user_id){
// $this->error('请先授权登录');
// }
$price = $order_price*100;
//$price = 1;
//------------------自定义订单信息--------------------------
$data = http://www.wxapp-union.com/['out_order_no' => $time, //随便搞个订单号
'openid' => $openid, //抖音用户openid
'fee' => $price, //金额 单位:分!分!分!
'cid' => 1,
'time' => $time,
'body' => $body, //支付的内容(支付宝)
'subject' => $subject, //支付的标题(支付宝)
//body 和 subject 刚开始先用数字(中文会有其他问题)
];
//------------------自定义订单信息--------------------------
//TP5框架 fastadmin
//------------------组合请求sign信息--------------------------
//↓↓↓获取用户真实IP
$risk_info = request()->ip();
//↓↓↓头条支付分配给业务方的ID(不是头条小程序的appid)
$payload['app_id'] = $config['payAppId'];
//↓↓↓头条支付分配给业务方的支付秘钥
$app_secret = $config['tt_pay_app_secret'];
//↓↓↓请求使用的编码格式
$payload['charset'] = "utf-8";
//↓↓↓接口名称
$payload['method'] = "tp.trade.create";
//↓↓↓发送请求的时间
$payload['timestamp'] = $time;
// 请求参数的集合 json
$biz_content = [
//商户订单号
"out_order_no" => $data['out_order_no'],
//唯一标识用户open_id
"uid" => $data['openid'],
//金额,分为单位,应传整型
"total_amount" => $data['fee'],
//商户订单名称
"subject" => $data['subject'],
//商户订单详情
"body" => $data['body'],
//头条支付分配给业务方的商户号
"merchant_id" => $config['merchant_id'],
//货币种类
"currency" => "CNY",
//下单时间戳
"trade_time" => $time,
//订单有效时间(此处测试 时间留的长) 单位:秒
"valid_time" => "3000",
//服务器异步通知地址尽量https 没试过http
"notify_url" => 'https://admin.shitutu.com/public/api/Dou_alipay/notify',
//用户的真实ip 一定要json序列化
"risk_info" => json_encode(['ip' => $risk_info]),
];
$payload['biz_content'] = json_encode($biz_content);
//字节跳动采用的是MD5加密
$payload['sign_type'] = "MD5";
$payload['format'] = "json";
$payload['version'] = "1.0";
//这里写了一个签名的方法, 千万别乱, 此处签名用来请求的, 与其他签名没有任何关联;(共3个签名)
$stringToBeSigned = $this->getSignContent($payload, $payload['charset']);
$payload["sign"] = md5($stringToBeSigned . $app_secret);
$url = "https://tp-pay.snssdk.com/gateway"; // 请求地址正式环境
// $url = "$url?app_id=$payload[app_id]&secret=$appSecret&code=$code&anonymous_code=$anonymous_code";
$result = $this->file_get_contents_post($url, $payload);
/*返回示例:
{"response":{"code":"10000","msg":"Success","trade_no":"SP2020081509594510822988870791"},"sign":"E7RRJSJCVAhA4DMyMPr/Q1IOc1RKpyQNikl9l8b3ObW6dAYzep7rK6wY5YVjSubhmINsI0iWb/cu+YCqp1D+amifkXh4nX2JG3D0xgi2eWJrTv3Ou27zuEPbEb5y10SBG1f4QCYoa7r2upmOL5xbjY6kG5iDPjiS4JIthsojR5Q="}*/
$result = json_decode($result, true);
//------------------组合请求sign信息--------------------------
$res['out_order_no'] = $result['response']['trade_no'];
$res['fee'] = $price;
$res['cid'] = 1;
$res['time'] = $time;
$res['body'] = $body;
$res['subject'] = $subject;
$order_res = $this->order_create($user_id,$video_id,$order_price,$res['out_order_no']);//这里是我自己的创建订单到本地数据库,这个方法就不展示了。
if ($result['response']['code'] != 10000) {
$this->error('错误', $res['response']['code']);
} elseif ($res) {
return $res;
}
}
/**
* 签名处理
* @param $params
* @param $charset
* @return string
*/
public function getSignContent($params, $charset = 'utf-8')
{
ksort($params);
$stringToBeSigned = "";
$i = 0;
foreach ($params as $k => $v) {
if (false === $this->checkEmpty($v) && "@" != substr($v, 0, 1)) {
// 转换成目标字符集
$v = $this->characet($v, $charset);
if ($i == 0) {
$stringToBeSigned .= "$k" . "=" . "$v";
} else {
$stringToBeSigned .= "&" . "$k" . "=" . "$v";
}
$i++;
}
}
unset ($k, $v);
return $stringToBeSigned;
}
public function file_get_contents_post($url, $post) {
$options = array(
'http' => array(
'method' => 'POST',
// 'content' => 'name=caiknife&email=caiknife@gmail.com',
'header' => "Content-type: application/x-www-form-urlencoded ",
'content' => http_build_query($post),
),
);
$result = file_get_contents($url, false, stream_context_create($options));
return $result;
}
/**
* @param $data
* @return mixed
* 阿里url证书
*/
public function aliUrlZhengshu($data)
{
//需要在AopCertClient.php文件中加入
//namespace app\api\controller;
//use think\Exception;
//引入文件 用来实例化
$config = config('douyin');
//require_once EXTEND_PATH . '/alipay/AopSdk.php';
require_once VENDOR_PATH . 'aop/AopCertClient.php';
// require_once EXTEND_PATH . 'aop/AopCertClient.php';
$c = new AopCertClient;
$appCertPath = VENDOR_PATH . 'aop/crt/appCertPublicKey_2021002114608375.crt';//应用证书路径(要确保证书文件可读)
$alipayCertPath = VENDOR_PATH . 'aop/crt/alipayCertPublicKey_RSA2.crt';//支付宝公钥证书路径(要确保证书文件可读)
$rootCertPath = VENDOR_PATH . 'aop/crt/alipayRootCert.crt';//支付宝根证书路径(要确保证书文件可读)
$c->gatewayUrl = "https://openapi.alipay.com/gateway.do";
$c->appId = self::APPID;
$c->rsaPrivateKey = self::RSA_PRIVATE_KEY;
$c->format = "json";
$c->charset = "UTF-8";
$c->signType = "RSA2";
//调用getPublicKey从支付宝公钥证书中提取公钥
$c->alipayrsaPublicKey = $c->getPublicKey($alipayCertPath);
//是否校验自动下载的支付宝公钥证书,如果开启校验要保证支付宝根证书在有效期内
$c->isCheckAlipayPublicCert = true;
//调用getCertSN获取证书序列号
$c->appCertSN = $c->getCertSN($appCertPath);
//调用getRootCertSN获取支付宝根证书序列号
$c->alipayRootCertSN = $c->getRootCertSN($rootCertPath);
//实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.open.public.template.message.industry.modify
//文件中加入 namespace app\api\controller; 即可
require_once VENDOR_PATH . 'aop/request/AlipayTradeAppPayRequest.php';
$request = new AlipayTradeAppPayRequest();
//此次只是参数展示,未进行字符串转义,实际情况下请转义
$request->setBizContent($this->getcontent($data));
$response = $c->sdkExecute($request);
return $response;
}
/**
* @param $data
* @return false|string
* 业务数据
*/
public function getcontent($data)
{
$biz_content = array(
'out_trade_no' => $data['out_order_no'], //之前咱们自定义的订单号 out_trade_no
'product_code' => 'QUICK_MSECURITY_PAY', //定死了 别动
'total_amount' => $data['fee'] / 100, //单位换算
'subject' => $data['subject'], //之前定好的 标题
'method' => 'alipay.trade.app.pay', //定死了 别动
'notify_url' => '*****',//回调接口需要配置到支付宝
'body' => $data['body'], //之前定好的 内容
'timeout_express' => '1m', //支付超时时间 文档去支付宝搜索咯 1m-15d
);
return json_encode($biz_content);
}
/**
* 下单方法
* param $params 下单参数
*/
public function unifiedOrder($order_no,$body,$subject){
$config = config('douyin');
$params['body'] = $subject; //商品描述
$params['out_trade_no'] = $order_no; //订单号
$params['total_fee'] = 1; //金额是以分为单位,除测试外,需乘以100
$params['trade_type'] = 'MWEB'; //交易类型,h5支付,默认如此
$params['scene_info'] = $body; //场景信息,h5固定
$params['spbill_create_ip'] = $this->getIp(); //终端IP
$params['appid'] = $this->appid;
$params['mch_id'] = $this->mch_id;
$params['nonce_str'] = $this->genRandomString(); //随机字符串
$params['notify_url'] = $this->notify_url; //通知地址
//获取签名数据
$params['sign'] = $this->MakeSign( $params ); //签名
$xml = $this->data_to_xml($params);
$uri = 'https://api.mch.weixin.qq.com/pay/unifiedorder'; //请求地址
$response = $this->postXmlCurl($uri,$xml); //自定义封装的xml请求格式,文章最下面为参考postxml
if( !$response ){
return false;
}
$result = $this->xml_to_data( $response );
if( !empty($result['result_code']) && !empty($result['err_code']) ){
$result['err_msg'] = $this->error_code( $result['err_code'] );
}
if($result['result_code'] == 'SUCCESS' && $result['return_msg'] == 'OK'){
//发起微信支付url
$pay_url = $result['mweb_url'];//.'&redirect_url='.urlencode($this->notify_url)
}else{
$pay_url = '';
}
return $pay_url;
//return $result;
}
/**
* 生成签名
* @return 签名
*/
public function MakeSign( $params ){
//签名步骤一:按字典序排序数组参数
ksort($params);
$string = $this->ToUrlParams($params);
//签名步骤二:在string后加入KEY
$string = $string . "&key=".$this->key;
//签名步骤三:MD5加密
$string = md5($string);
//签名步骤四:所有字符转为大写
$result = strtoupper($string);
return $result;
}
/**
* 输出xml字符
* param $params 参数名称
* return string 返回组装的xml
**/
public function data_to_xml( $params ){
if(!is_array($params)|| count($params) <= 0)
{
return false;
}
$xml = "<xml>";
foreach ($params as $key=>$val)
{
if (is_numeric($val)){
$xml.="<".$key.">".$val."</".$key.">";
}else{
$xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
}
}
$xml.="</xml>";
return $xml;
}
/**
* @param $url
* @param $xml
* @param int $second
* @return bool|string
* xml请求
*/
public function postXmlCurl($url,$xml,$second = 30){
$ch = curl_init();
//设置超时
curl_setopt($ch, CURLOPT_TIMEOUT, $second);
curl_setopt($ch,CURLOPT_URL, $url);
curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE);
curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,FALSE);
//设置 header
curl_setopt($ch, CURLOPT_HEADER, FALSE);
//要求结果为字符串且输出到屏幕上
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
//post 提交方式
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
//运行 curl
$data = http://www.wxapp-union.com/curl_exec($ch);
//返回结果
if($data){
curl_close($ch);
return $data;
}else{
$error = curl_errno($ch);
curl_close($ch);
echo"curl 出错,错误码:$error"."<br>";
}
}
/**
* 将xml转为array
* param string $xml
* return array
*/
public function xml_to_data($xml){
if(!$xml){
return false;
}
//将XML转为array
//禁止引用外部xml实体
libxml_disable_entity_loader(true);
$data = http://www.wxapp-union.com/json_decode(json_encode(simplexml_load_string($xml,'SimpleXMLElement', LIBXML_NOCDATA)), true);
return $data;
}
/**
* 校验$value是否非空
* @param $value
* @return boolean;
* if not set ,return true;
* if is null , return true;
**/
public function checkEmpty($value)
{
if (!isset($value))
return true;
if ($value =http://www.wxapp-union.com/== null)
return true;
if (trim($value) ==="")
return true;
return false;
}
/**
* 转换字符集编码
* @param $data
* @param $targetCharset
* @return string
*/
public function characet($data, $targetCharset)
{
if (!empty($data)) {
$fileType = "UTF-8";
if (strcasecmp($fileType, $targetCharset) != 0) {
$data = http://www.wxapp-union.com/mb_convert_encoding($data, $targetCharset, $fileType);
}
}
return $data;
}
复制代码
2,前端,uniapp
我是通过字节跳动的收银台吊起的支付的,吊起收银台后可以选择支付宝或者微信支付,然后跳转到微信或者支付宝,所以上面要把微信和支付宝的支付url都传给了前端。
gopay(){
wxpay({
'openid':'****',
'price':'****',
'video_id':'****',
'user_id':'****',
'subject':'****',
'body':this.detailList.title,
}).then(e => {
let order = JSON.parse(e.data.data.list);
let order_no = order.trade_no
if(e.data.code == 1){
uni.requestPayment({
service: 1, // 不拉起字节跳动小程序收银台
_debug: 1,
payChannel: {
default_pay_channel: 'alipay' // wx || alipay
},
orderInfo: order, // 订单信息
getOrderStatus(res) {
let { out_order_no } = res;
return new Promise(function (resolve, reject) {
})
},
success: (res) => {
console.log(res)
if(res.code == 0){
uni.showToast({
title: '支付成功',
duration: 2000
});
}
if(res.code == 1){
uni.showToast({
title: '支付超时',
duration: 2000
});
}
if(res.code == 2){
uni.showToast({
title: '支付失败',
duration: 2000
});
}
if(res.code == 2){
uni.showToast({
title: '支付失败',
duration: 2000
});
}
if(res.code == 4){
orderQuery({
'order_no':order_no,
}).then(e => {
if(e.data.code == 1){
uni.showToast({
title: '支付成功',
duration: 2000
});
}else{
uni.showToast({
title: '支付失败',
duration: 2000
});
}
});
}
if(res.code == 9){
uni.showToast({
title: '订单状态未知',
duration: 2000
});
}
},
fail: (res) => {
console.log("失败1");
console.log(res); // 错误代码:CD0015 CD0025
},
complete: (res) => {
console.log("结束")
}
})
// _this.loadModal = false;
}
})
},
复制代码
这里有一个问题我不知道是不是字节官方的原因,我通过收银台支付,使用支付宝支付的时候,支付成功,返回小程序是有字节的支付回调页面并且提示成功的。但是微信支付没有显示这个支付回调的页面提示,并且code一直是4,所以我在code等于4的时候去后端验证了一下支付状态,手动给用户提示了支付状态,如果有朋友知道原因可以麻烦告知我一下,有问题也欢迎留言。