这篇文章主要是想整理一下学习了微信小程序商城构建全栈应用微信支付时候的思路和实现的方法。不然老是忘记。
主要的步骤有
1.获取Token。
2.根据Token下订单。
3.创建订单成功后,继续发送下单的接口到后台,后台调用微信的SDK接口,将时间戳,加密的,签名,appid,商户号等信息发送给微信,微信返回信息到后台,后台再传给小程序,小程序再调用微信的接口拉起收款。
4.微信收款成功后,微信会根据后台之前传入的api地址来给系统后台异步发送微信支付成功的信息。然后在触发了该api之后,系统更改订单的状态,扣除商品的数量。
1.获取Token:
Token是必须的,有些隐秘的资源需要Token来获取。根据token也可以分出权限来,权限的不同则表示了你能否获得该资源。
通过微信传过来的code换取appid,然后再根据appid查找数据库中是否有该用户,没有则创建
// 发送小程序的code到指定的微信地址从而获取该用户的appid
$res = curl_get($this->loginUrl);
// true 为 字符串转换成数组,false为转换成对象
$wxResult = json_decode($res, true);
if (empty($wxResult)) {
throw new Exception('获取session_key及openID时异常,微信内部错误');
} else {
$loginFail = array_key_exists('errcode', $wxResult);
if ($loginFail) {
throw new weChatException([
'errorcode' => $wxResult['errcode'],
'msg' => $wxResult['errmsg']
]);
} else {
return $this->grantToken($wxResult);
}
}
判断是否有该用户
$openid = $wxResult['openid'];
$user = User::getByOpenID($openid);
if ($user) {
$uid = $user->id;
} else {
$uid = $this->newUser($openid);
}
完成了之后,将用户的信息放入cache中,生成token,并且以token作为key,用户的信息作为value传入cache
制作Value
private function prepareCacheValue($wxResult, $uid)
{
$cacheValue = $wxResult;
$cacheValue['uid'] = $uid;
// 指定用户权限的数值,根据数值的不同,表示能否访问该权限
$cacheValue['scope'] = ScopeEnum::User;
return $cacheValue;
}
生成Token传入cache
public static function generateToken()
{
// 32个字符组成一组随机字符串
// 总共三组
// 随机字符串
// 时间戳
// 盐,放入config中
$randChars = getRandChars(32);
$timestamp = $_SERVER['REQUEST_TIME'];
$salt = config('secure.salt');
return md5($randChars . $timestamp . $salt);
}
// 传入cache中
$key = $this->generateToken();
$value = json_encode($cacheValue);
$expire = config('setting.token_expire_in');
$request = Cache::set($key,$value,$expire);
if (!$request) {
throw new TokenException([
'msg' => '服务器缓存异常',
'errorcode' => 10005
]);
}
return $key;
2.根据Token下订单
通过Token获取到Cache里面的uid,通过此来进行下订单。
传输订单的接口为
[
{
id:1,
count:1
},
{
id:2,
count:2
}
]
通过商品id查询商品
private function getProductsByOrder($oProducts)
{
$oPids = [];
foreach ($oProducts as $item) {
$oPids[] = $item['product_id'];
}
$products = Product::all($oPids)->visible(['id', 'name', 'price', 'stock', 'main_img_url'])->toArray();
return $products;
}
检测商品库存是否足够并且获取订单价格和数量,将下单商品的详细信息保存到数组中
private function getOrderStatus()
{
$status = [
'pass' => true,
'orderPrice' => 0,
'orderCount' => 0,
'pStatusArray' => []
];
foreach ($this->oProducts as $oProduct) {
$pStatus = $this->getProductStatus($oProduct['product_id'], $oProduct['count'], $this->products);
if (!$pStatus['haveStock']) {
$status['pass'] = false;
}
$status['orderPrice'] += $pStatus['totalPrice'];
$status['orderCount'] += $pStatus['count'];
array_push($status['pStatusArray'], $pStatus);
}
return $status;
}
// 获取商品的状态
private function getProductStatus($oPid, $oCount, $products)
{
$pIndex = -1;
$pStatus = [
'id' => null,
'haveStock' => false,
'count' => 0,
'name' => '',
'totalPrice' => 0
];
for ($i = 0; $i < count($products); $i++) {
if ($oPid == $products[$i]['id']) {
$pIndex = $i;
}
}
if ($pIndex == -1) {
throw new OrderException([
'msg' => 'id为' . $oPid . '的商品不存在,创建订单失败'
]);
} else {
$product = $products[$pIndex];
$pStatus['id'] = $product['id'];
$pStatus['count'] = $oCount;
$pStatus['name'] = $product['name'];
$pStatus['totalPrice'] = $product['price'] * $oCount;
if ($product['stock'] >= $oCount) {
$pStatus['haveStock'] = true;
}
}
return $pStatus;
}
检测商品是否通过检查,通过则生成订单编号并且生成订单快照,不通过则直接返回到客户端。
由于商品和价格都可能会发生变化,所以使用外链的方法会出现错误,所以需要把当时商品的信息记录下来。
创建订单编号
public static function makeOrderNum()
{
$yCode = array('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J');
$orderSn = $yCode[intval(date('Y') - 2018)] . strtoupper(dechex(date('m'))) . date('d') . substr(time(), -5) . substr(microtime(), 2, 5) . sprintf('%02d', rand(0, 99));
return $orderSn;
}
生成订单快照
private function snapOrder($status)
{
$orderSnap = [
'orderPrice' => 0,
'totalCount' => 0,
'pStatus' => [],
'snapAddress' => null,
'snapName' => '',
'snapImg' => ''
];
$orderSnap['orderPrice'] = $status['orderPrice'];
$orderSnap['totalCount'] = $status['orderCount'];
$orderSnap['pStatus'] = $status['pStatusArray'];
$orderSnap['snapAddress'] = json_encode($this->getUserAddress());
$orderSnap['snapName'] = $this->products[0]['name'];
if (count($this->products) > 1) {
$orderSnap['snapName'] .= '等';
}
$orderSnap['snapImg'] = $this->products[0]['main_img_url'];
return $orderSnap;
}
做完这一切的步骤后,就可以插入数据库了
主要是录入订单信息,和录入订单商品信息(id,关联的订单id,数量)
private function createOrder($orderSnap)
{
Db::startTrans();
try {
$orderNum = self::makeOrderNum();
$order = new OrderModel();
$order->order_no = $orderNum;
$order->user_id = $this->uid;
$order->total_price = $orderSnap['orderPrice'];
$order->snap_img = $orderSnap['snapImg'];
$order->snap_name = $orderSnap['snapName'];
$order->total_count = $orderSnap['totalCount'];
$order->snap_items = json_encode($orderSnap['pStatus']);
$order->snap_address = json_encode(UserAddress::where('user_id', 'eq', $this->uid)->find()->toArray());
$order->save();
$orderId = $order->id;
$createTime = $order->create_time;
foreach ($this->oProducts as &$p) {
$p['order_id'] = $orderId;
}
$orderProduct = new OrderProduct();
$orderProduct->saveAll($this->oProducts);
Db::commit();
return [
'order_no' => $orderNum,
'order_id' => $orderId,
'create_time' => $createTime
];
} catch (Exception $ex) {
Db::rollback();
throw $ex;
}
}
3.根据订单号实现预支付
根据订单号来判断是否存在该订单号,该订单号是否属于该token的uid,该订单号的状态是否为未支付,最后还需要再检查库存。
注:部分和上面方法通用,需写为一个通用的方法。
实现了上面的步骤之后,就可以使用起微信的SDK了。
将微信的文件放在了该目录下:
由于TP5.1取消了Loader载入的方法,所以我找了好久,使用的是Env来获取微信文件的地址。通过此来加载SDK的文件。
include Env::get('root_path') . 'extend/wxPay/WxPay.Api.php';
注:微信支付的WxPay.config改变了,我们需要继承该方法,填入自己的appid,appsecret和mmid等信息才行。
引入微信的SDK后,首先需要做的就是设置各种属性。
$wxOrderData = new \WxPayUnifiedOrder();
// 会通过异步传回来给我们
$wxOrderData->SetOut_trade_no($this->orderNum);
$wxOrderData->SetTrade_type('JSAPI');
$wxOrderData->SetTotal_fee($totalPrice * 100);
$wxOrderData->SetBody('零食商贩');
$wxOrderData->SetOpenid($openid);
// 支付成功后,微信的回调,要用post提交
$wxOrderData->SetNotify_url('http://paysdk.weixin.qq.com/notify.php');
return $this->getPaySignature($wxOrderData);
设置好了微信需要的信息之后,就开始发送到微信了,这里需要将继承的config传入里面发送给微信
private function getPaySignature($wxOrderData)
{
$config = new WxPayConfig();
$wxOrder = \WxPayApi::unifiedOrder($config, $wxOrderData);
if ($wxOrder['return_code'] != 'SUCCESS' || $wxOrder['result_code'] != 'SUCCESS') {
Log::record($wxOrder, 'error');
Log::record('获取支付订单失败', 'error');
}
// 记录prepay_id
$this->recordPayOrder($wxOrder);
$signature = $this->sign();
return $signature;
}
如果获取到的信息正确,就需要将需要的信息返回给客户端,客户端根据此信息来调用微信的支付接口。
private function sign($wxOrder)
{
$jsApiPayData = new \WxPayJsApiPay();
$jsApiPayData->SetAppid(config('wx.appid'));
$jsApiPayData->SetTimeStamp((string)time());
$rand = md5(time() . mt_rand(0, 1000));
$jsApiPayData->SetNonceStr($rand);
$jsApiPayData->SetPackage('prepay_id=' . $wxOrder['prepay_id']);
$jsApiPayData->SetSignType('md5');
$sign = $jsApiPayData->MakeSign();
$rawValues = $jsApiPayData->GetValues();
$rawValues['paySign'] = $sign;
unset($rawValues['appId']);
return $rawValues;
}
根据微信支付的信息,返回给客户端相应的信息。SDK在设置完之后,通过GetValue可以传换成数组
客户端得到数据后调用该微信API就能实现微信支付了。
4.微信支付成功后的回调处理
由于微信返回的是xml格式的数据,所以同样可以通过sdk来进行处理。
需要做的是继承WxPayNotify,并且改写里面的NotifyProcess方法。然后调用Handle方法即可。
里面主要的步骤就是,检测库存,减库存,修改订单信息这几步。
public function NotifyProcess($objData, $config, &$msg)
{
if ($objData['result_code'] == 'SUCCESS') {
$orderNo = $objData['out_trade_no'];
Db::startTrans();
try {
$order = OrderModel::where('order_no', 'eq', $orderNo)->find();
if ($order->status == 1) {
$orderService = new OrderService();
$stockStatus = $orderService->checkOrderStock($order->id);
if ($stockStatus['pass']) {
$this->updateOrderStatus($order->id, true);
$this->reduceStock($stockStatus['pStatusArray']);
} else {
$this->updateOrderStatus($order->id, false);
}
}
Db::commit();
return true;
} catch (Exception $ex) {
Db::rollback();
Log::error($ex);
return false;
}
} else {
return true;
}
}
需要返回True来表示接受成功,返回false,微信会隔一段时间再发送一次POST请求。
网友评论