小程序和php怎么对接_php对接微信小程序支付

本文详细介绍了微信小程序如何与PHP后台对接实现支付功能,包括用户下单、获取登录凭证、服务器处理订单、调用微信支付API、处理支付异步回调等关键步骤。同时,提供了相关代码示例,包括小程序端的调用逻辑和PHP端的接口处理,帮助开发者理解并实现微信支付的完整流程。
摘要由CSDN通过智能技术生成

注:个人注册微信小程序不支持微信支付功能

开发前流程:

开发流程

注意:支付按钮要加上锁避免用户多次点击

1.用户点击下单按钮

2.微信小程序用wx.login方法获取用户登录凭证code,code有效期为五分钟

3.微信小程序调用服务器接口,传入code,money

4.服务器接收到code,money后,调用微信api的code2Session方法进行登录凭证校验,获取到用户唯一标识open_id

5.服务器验证open_id,并创建支付订单

6.服务器将数据进行签名调用统一下单api,获取到prepay_id

7.服务器再次生成签名信息,返回微信小程序

8.微信小程序调用wx.requestPayment方法调用微信支付窗口

9.服务器异步接收支付通知结果,进行签名验证,并校验用户和返回的订单金额是否与商户的订单金额一致,修改数据库并生成日志

签名流程:

1.参数名ASCII码从小到大排序

2.格式化参数,将数组转换成key1=valve1&key2=value2...的形式

3.追加key,key1=valve1&key2=value2...&key=*******

4.md5加密

5.转化为大写

异步回调处理流程

590d880611e5

支付异步回调处理流程

开发流程代码

1.用户点击下单按钮

wxml代码

当前选择:《不抱怨的世界》 ¥0.01

确认支付

确认支付

wxss代码

.container {

padding: 50rpx;

}

.pay {

margin-top: 30rpx;

color: #fff;

background-color: #1fb922;

}

.pay2 {

margin-top: 30rpx;

color: #fff;

background-color: #dedede;

}

js代码

Page({

/**

* 页面的初始数据

*/

data: {

pay: true,

},

onTap () {

let _self = this;

_self._togglePay();

wx.login({

success(res) {

if (res.code) {

//发起网络请求

wx.request({

url: 'http://api.djny.com/v1/pay/pay-sign',

method: "POST",

data: {

code: res.code,

money:0.01

},

header: {

"content-type": "application/x-www-form-urlencoded"

},

success(res) {

var params = res.data.data;

wx.requestPayment({

'timeStamp': String(params['timeStamp']),

'nonceStr': params['nonceStr'],

'package': params['package'],

'signType': params['signType'],

'paySign': params['paySign'],

'success': function (res) {

wx.showToast({

title: '支付成功',

icon: 'success',

duration: 2000

})

_self._togglePay();

},

'fail': function (res) {

wx.showToast({

title: '支付失败',

icon: 'none',

duration: 2000

})

_self._togglePay();

},

})

}

})

} else {

this._togglePay();

console.log('登录失败!' + res.errMsg)

}

}

})

},

_togglePay() {

this.setData({

pay: !this.data.pay

});

}

})

2.基本配置

'wx_pay' => [

'app_id' => 'wx2**********2965c', //小程序appid

'app_secret' => '55913********************6574c6b', //小程序secret

'mch_id' => '15******71', //商户平台商户号

'key' => 'sQm*******************aVQkca', //商户平台密钥key

'notify_url' => 'http://www.test.cn/v1/notify-pay', //支付异步回调地址

'name' => '测试支付', //商品简单描述

]

3.工具类方法

/**

* curl get

* @param $url 请求路径

* @param array $params 参数

* @return mixed

*/

public static function get($url, array $params)

{

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url . "?" . http_build_query($params));

curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 获取数据返回

curl_setopt($ch, CURLOPT_BINARYTRANSFER, true); // 在启用 CURLOPT_RETURNTRANSFER 时候将获取数据返回

$output = curl_exec($ch);

curl_close($ch);

return $output;

}

/**

* curl post xml

* @param $xml 参数

* @param $url 请求地址

* @param int $second 设置超时

* @return mixed

*/

public static function postXml($xml, $url, $second = 60)

{

$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_setopt($ch, CURLOPT_CONNECTTIMEOUT, 20);

curl_setopt($ch, CURLOPT_TIMEOUT, 40);

set_time_limit(0);

//运行curl

$data = curl_exec($ch);

curl_close($ch);

return $data;

}

/**

* array to xml

* @param $arr

* @return string

*/

public static function arrayToXml($arr)

{

$xml = "";

foreach ($arr as $key => $val) {

if (is_array($val)) {

$xml .= "" . _arrayToXml($val) . "" . $key . ">";

} else {

$xml .= "" . $val . "" . $key . ">";

}

}

$xml .= "";

return $xml;

}

/**

* xml to array

* @param $xml

* @return mixed

*/

public static function xmlToArray($xml)

{

//禁止引用外部xml实体

libxml_disable_entity_loader(true);

$xmlstring = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);

$val = json_decode(json_encode($xmlstring), true);

return $val;

}

/**

* 生产随机字符串 默认32位

* @param int $length

* @return string

*/

public static function randStr($length = 32)

{

$chars = "abcdefghijklmnopqrstuvwxyz0123456789";

$str = "";

for ($i = 0; $i < $length; $i++) {

$str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);

}

return $str;

}

/**

* 格式化参数 array to key1=valve1&key2=value2

* @param $params

* @param $url_encode

* @return string

*/

public static function formatParams($params, $url_encode)

{

if (!$params) {

return '';

}

$paramUrl = "";

ksort($params);

foreach ($params as $k => $v) {

if ($url_encode) {

$v = urlencode($v);

}

$paramUrl .= $k . "=" . $v . "&";

}

if (strlen($paramUrl) > 0) {

$paramUrl = substr($paramUrl, 0, strlen($paramUrl) - 1);

}

return $paramUrl;

}

4.控制层代码

/**

* 获取支付签名

*/

public function actionPaySign()

{

$code = Yii::$app->request->post('code');

$money = Yii::$app->request->post('money');

if (!$code || $money <= 0) {

$this->render_json(self::STATUS_CODE_CLIENT_ERROR, '参数错误');

}

$wxPay = new WxPay();

$openId = $wxPay->getOpenId($code);

if (!$openId) {

$this->render_json(self::STATUS_CODE_CLIENT_ERROR, $wxPay->getError());

}

$userInfo = WxMember::findOne(['openid' => $openId]);

if (!$userInfo) {

$this->render_json(self::STATUS_CODE_CLIENT_ERROR, '用户信息获取失败');

}

$wxPayOrder = new WxPayorder();

if (!$wxPayOrder->createPayOrder($userInfo, $money)) {

$this->render_json(self::STATUS_CODE_CLIENT_ERROR, $wxPayOrder->getFirstError('id'));

}

$paySign = $wxPay->paySign($openId, $wxPayOrder->out_trade_no, $wxPayOrder->order_amount);

if (!$paySign) {

$this->render_json(self::STATUS_CODE_CLIENT_ERROR, $wxPay->getError());

}

$this->render_json(self::STATUS_CODE_SUCCESS, '成功', $paySign);

}

/**

* 小程序支付异步回调

*/

public function actionNotifyPay()

{

$xml = file_get_contents('php://input', 'r');

$resData = Utility::xmlToArray($xml);

$wxPay = new WxPay();

if ($wxPay->checkNotifySign($resData)) {

$wxPayOrder = new WxPayorder();

$result = $wxPayOrder->updateNotifyPayOrder($resData['return_code'], $resData['openid'], $resData['out_trade_no'], $resData['total_fee'] / 100, $resData['transaction_id'], $resData);

if ($result) {

$resStr = $wxPay->notifyReturnSuccess();

} else {

$resStr = $wxPay->notifyReturnFail($wxPayOrder->getFirstError('id'));

}

} else {

$resStr = $wxPay->notifyReturnFail('签名验证失败');

}

echo $resStr;

}

5.微信支付接口类

class WxPay

{

/**

* 小程序appid

* @var string

*/

private $_appId;

/**

* 小程序secret

* @var string

*/

private $_appSecret;

/**

* 商户平台商户id

* @var string

*/

private $_mchId;

/**

* 商户平台密钥key

* @var string

*/

private $_key;

/**

* 支付回掉地址

* @var string

*/

private $_notifyUrl;

/**

* 获取用户唯一标识open_id api 接口地址

* @var string

*/

private $_code2SessionApiUrl;

/**

* 统一下单api 接口地址

* @var string

*/

private $_unifiedOrderApiUrl;

/**

* 错误信息

* @var string

*/

public $errorInfo;

/**

* WxPay constructor.

*/

public function __construct()

{

$this->_appId = Yii::$app->params['wx_pay']['app_id'];

$this->_appSecret = Yii::$app->params['wx_pay']['app_secret'];

$this->_mchId = Yii::$app->params['wx_pay']['mch_id'];

$this->_key = Yii::$app->params['wx_pay']['key'];

$this->_notifyUrl = Yii::$app->params['wx_pay']['notify_url'];

$this->_code2SessionApiUrl = 'https://api.weixin.qq.com/sns/jscode2session';

$this->_unifiedOrderApiUrl = 'https://api.mch.weixin.qq.com/pay/unifiedorder';

}

/**

* 添加错误信息

* @param $err

*/

public function setError($err)

{

$this->errorInfo = $err;

}

/**

* 获取错误信息

* @return string

*/

public function getError()

{

return $this->errorInfo;

}

/**

* 获取用户唯一标识open_id

* @param $code

* @return string

*/

public function getOpenId($code)

{

if (!$code) {

return '';

}

$params = [

'appid' => $this->_appId,

'secret' => $this->_appSecret,

'js_code' => $code,

'grant_type' => 'authorization_code'

];

$resJson = Curl::get($this->_code2SessionApiUrl, $params);

$res = json_decode($resJson);

if (isset($res->errcode)) {

$this->errorInfo = '获取用户open_id失败';

return '';

}

return $res->openid;

}

/**

* 获取支付签名

* @param $open_id

* @param $order_no

* @param $money

* @return array

*/

public function paySign($open_id, $order_no, $money)

{

if (!$open_id || !$order_no || !$money) {

$this->setError('参数错误');

return [];

}

$prepay_id = $this->_unifiedorder($open_id, $order_no, $money);

if (!$prepay_id) {

return [];

}

$params = array(

'appId' => $this->_appId,

'timeStamp' => time(),

'nonceStr' => Utility::randStr(),

'package' => 'prepay_id=' . $prepay_id,

'signType' => 'MD5'

);

$params['paySign'] = $this->_getSign($params);

return $params;

}

/**

* 异步签名验证

* @param $data

* @return bool

*/

public function checkNotifySign($data)

{

if (!$data) {

return false;

}

$sign = $data['sign'];

unset($data['sign']);

if ($sign == $this->_getSign($data)) {

return true;

}

return false;

}

/**

* 异步回调处理成功时返回内容

* @param $msg

* @return string

*/

public function notifyReturnSuccess($msg = 'OK')

{

return '';

}

/**

* 异步回调处理失败时返回内容

* @param $msg

* @return string

*/

public function notifyReturnFail($msg = 'FAIL')

{

return '';

}

/**

* 统一下单

* @param $open_id

* @param $order_no

* @param $money

* @return string

*/

private function _unifiedOrder($open_id, $order_no, $money)

{

$params = [

'appid' => $this->_appId,

'mch_id' => $this->_mchId,

'nonce_str' => Utility::randStr(),

'body' => Yii::$app->params['wx_pay']['name'], //商品简单描述

'out_trade_no' => $order_no, //商户系统内部订单号

'total_fee' => $money * 100, //订单总金额,单位为分

'spbill_create_ip' => Utility::getClientIp(), //用户端ips

'notify_url' => $this->_notifyUrl, //通知地址

'trade_type' => 'JSAPI', //交易类型

'openid' => $open_id //用户标识

];

$params['sign'] = $this->_getSign($params);

$xmlData = Utility::arrayToXml($params);

$returnXml = Curl::postXml($xmlData, $this->_unifiedOrderApiUrl);

$returnArr = Utility::xmlToArray($returnXml);

if ($returnArr['return_code'] == 'FAIL') {

$this->setError($returnArr['return_msg']);

return '';

}

return $returnArr['prepay_id'];

}

/**

* 获取签名

* @param $params

* @return string

*/

private function _getSign($params)

{

if (!$params) {

return '';

}

//step1: 排序

ksort($params);

//step2:格式化参数

$paramUrl = Utility::formatParams($params, false);

//step3:追加key

$paramUrl = $paramUrl . '&key=' . $this->_key;

//step4: md5加密

$paramUrl = md5($paramUrl);

//step5:转化为大写

$sign = strtoupper($paramUrl);

return $sign;

}

}

6.模型层

class WxPayorder extends \common\models\base\BaseMain

{

/**

* 充值类型

*/

const TYPE_XCX = 1;

const TYPE_GZH = 2;

/**

* 充值状态: 1未审核 2失败 3成功 4失效 5其他 6处理中

*/

const STATUS_WAITING = 1;

const STATUS_FAILED = 2;

const STATUS_SUCCESS = 3;

const STATUS_LOSE = 4;

const STATUS_OTHER = 5;

const STATUS_PROCESSING = 6;

/**

* 充值时最大金额 单位 元

*/

const MONEY_MAX_RECHARGE = 5000;

/**

* 账户最大金额 单位 元

*/

const MONEY_MAX_ACCOUNT = 6000;

/**

* @inheritdoc

*/

public static function tableName()

{

return '{{%wx_payorder}}';

}

/**

* @inheritdoc

*/

public function rules()

{

return [

[['memberid', 'out_trade_no', 'type', 'status', 'order_amount', 'createtime'], 'required', 'on' => 'insert'],

[['status', 'operation_time'], 'required', 'on' => 'updateStatus'],

[['payamount', 'remark', 'operation_time'], 'required', 'on' => 'updatePaySuccess'],

[['remark', 'operation_time'], 'required', 'on' => 'updatePayFail'],

[['memberid', 'type', 'status', 'operation_time', 'createtime'], 'integer'],

[['payamount', 'order_amount'], 'number'],

[['type', 'status'], 'required'],

[['out_trade_no'], 'string', 'max' => 50],

[['remark'], 'string', 'max' => 100],

];

}

/**

* @inheritdoc

*/

public function attributeLabels()

{

return [

'id' => 'ID',

'memberid' => '会员ID',

'out_trade_no' => '订单号',

'payamount' => '充电金额',

'type' => '终端充值类型(1小程序2公众号)',

'status' => '充值状态(1未审核2失败3成功4失效5其他)',

'order_amount' => '订单充值金额',

'remark' => '备注',

'operation_time' => '响应操作时间(充值成功或失败时需要更新时间)',

'createtime' => '创建时间',

];

}

/**

* before validate

* @return bool

*/

public function beforeValidate()

{

parent::afterValidate();

if ($this->getIsNewRecord()) {

$this->createtime = time();

} else {

$this->operation_time = time();

}

return true;

}

/**

* 添加订单

* @param $userInfo 用户对象

* @param $money

* @return bool

*/

public function createPayOrder($userInfo, $money)

{

$moneyArr = [0.01, 10, 50, 100, 200, 500, 1000];

if (!in_array($money, $moneyArr)) {

$this->addError('id', '充值金额有误');

return false;

}

if ($userInfo->balance >= self::MONEY_MAX_RECHARGE) {

$this->addError('id', '账户金额已经上限,不能再充值了');

return false;

}

if ($userInfo->balance + $money >= self::MONEY_MAX_ACCOUNT) {

$this->addError('id', '充值金额已经上限');

return false;

}

$this->memberid = $userInfo->id;

$this->out_trade_no = $this->_getOrderId($userInfo->id);

$this->type = self::TYPE_XCX;

$this->status = self::STATUS_WAITING;

$this->order_amount = $money;

$this->setScenario('insert');

if (!$this->save()) {

$this->addError('id', '创建订单失败');

return false;

}

return true;

}

/**

* 支付成功修改数据库

* @param $result 支付状态

* @param $open_id open_id

* @param $out_trade_no 客户订单号

* @param $money 金额

* @param $wx_trade_no 微信支付订单号

* @param $data

* @return bool

* @throws \Exception

*/

public function updateNotifyPayOrder($result, $open_id, $out_trade_no, $money, $wx_trade_no, $data)

{

//记录异步通知日志

$wxPayOrderLog = new WxPayorderLog();

$wxPayOrderLog->setScenario('insert');

$wxPayOrderLog->open_id = $open_id;

$wxPayOrderLog->type = WxPayorderLog::TYPE_XCX;

$wxPayOrderLog->out_trade_no = $out_trade_no;

$wxPayOrderLog->money = $money;

$wxPayOrderLog->wx_trade_no = $wx_trade_no;

$wxPayOrderLog->remark = '异步请求';

$wxPayOrderLog->remark_back = json_encode($data);

if (!$wxPayOrderLog->save()) {

$this->addError('id', '订单日志保存失败');

return false;

}

//订单查询

$wxPayOrder = WxPayorder::findOne(['out_trade_no' => $out_trade_no]);

if (!$wxPayOrder) {

$this->addError('id', '订单不存在');

return false;

}

//如果订单支付状态为成功直接返回

if ($wxPayOrder->status == WxPayorder::STATUS_SUCCESS) {

return true;

}

//订单支付状态为未处理

if ($wxPayOrder->status == WxPayorder::STATUS_WAITING) {

//修订订单状态为处理中

$wxPayOrder->status = WxPayorder::STATUS_PROCESSING;

$wxPayOrder->setScenario('updateStatus');

if (!$wxPayOrder->save()) {

$this->addError('id', '订单状态修改失败');

return false;

}

//验证用户

$wxMember = WxMember::findOne(['openid' => $open_id]);

if (!$wxMember) {

$this->addError('id', '用户不存在');

return false;

}

if ($wxMember->id != $wxPayOrder->memberid) {

$this->addError('id', '用户信息有误');

return false;

}

//验证金额

if ($wxPayOrder->order_amount != $money) {

$this->addError('id', '订单金额有误');

return false;

}

//判断订单支付状态

if ($result == 'SUCCESS') {

//修改订单状态为成功

$wxPayOrder->status = self::STATUS_SUCCESS;

$wxPayOrder->setScenario('updateStatus');

if (!$wxPayOrder->save()) {

$this->addError('id', '订单状态修改失败');

return false;

}

//修改订单信息

$wxPayOrder->payamount = $wxPayOrder->order_amount;

$wxPayOrder->remark = '支付成功';

$wxPayOrder->setScenario('updatePaySuccess');

if (!$wxPayOrder->save()) {

$this->addError('id', '订单信息保存失败');

return false;

}

//保存账户余额表

$wxMember->balance = $wxMember->balance + $wxPayOrder->payamount;

if (!$wxMember->save()) {

$this->addError('id', '用户账户信息保存失败');

return false;

}

return true;

} else {

//保存订单表

$wxPayOrder->status = self::STATUS_FAILED;

$wxPayOrder->remark = '支付失败';

$wxPayOrder->setScenario('updatePayFail');

if (!$wxPayOrder->save()) {

$this->addError('id', '数据错误');

return false;

}

}

}

$this->addError('id', '订单处理失败');

return false;

}

/**

* 获取用户唯一订单号 最大32位

* @param $userId

* @return string

*/

private function _getOrderId($userId)

{

return 'XCX' . (time() . $userId . rand(1000, 9000) . rand(1000, 9000));

}

}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值