這篇文章主要是想整理一下學習了微信小程序商城構(gòu)建全棧應(yīng)用微信支付時候的思路和實現(xiàn)的方法妖啥。不然老是忘記蛤签。
主要的步驟有
1.獲取Token拴清。
2.根據(jù)Token下訂單泻蚊。
3.創(chuàng)建訂單成功后躲舌,繼續(xù)發(fā)送下單的接口到后臺,后臺調(diào)用微信的SDK接口性雄,將時間戳没卸,加密的羹奉,簽名,appid约计,商戶號等信息發(fā)送給微信诀拭,微信返回信息到后臺,后臺再傳給小程序煤蚌,小程序再調(diào)用微信的接口拉起收款耕挨。
4.微信收款成功后,微信會根據(jù)后臺之前傳入的api地址來給系統(tǒng)后臺異步發(fā)送微信支付成功的信息尉桩。然后在觸發(fā)了該api之后筒占,系統(tǒng)更改訂單的狀態(tài),扣除商品的數(shù)量蜘犁。
1.獲取Token:
Token是必須的翰苫,有些隱秘的資源需要Token來獲取。根據(jù)token也可以分出權(quán)限來这橙,權(quán)限的不同則表示了你能否獲得該資源革骨。
通過微信傳過來的code換取appid,然后再根據(jù)appid查找數(shù)據(jù)庫中是否有該用戶析恋,沒有則創(chuàng)建
// 發(fā)送小程序的code到指定的微信地址從而獲取該用戶的appid
$res = curl_get($this->loginUrl);
// true 為 字符串轉(zhuǎn)換成數(shù)組良哲,false為轉(zhuǎn)換成對象
$wxResult = json_decode($res, true);
if (empty($wxResult)) {
throw new Exception('獲取session_key及openID時異常,微信內(nèi)部錯誤');
} 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;
// 指定用戶權(quán)限的數(shù)值巍实,根據(jù)數(shù)值的不同,表示能否訪問該權(quán)限
$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' => '服務(wù)器緩存異常',
'errorcode' => 10005
]);
}
return $key;
2.根據(jù)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;
}
檢測商品庫存是否足夠并且獲取訂單價格和數(shù)量膝昆,將下單商品的詳細信息保存到數(shù)組中
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;
}
// 獲取商品的狀態(tài)
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 . '的商品不存在丸边,創(chuàng)建訂單失敗'
]);
} 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;
}
檢測商品是否通過檢查,通過則生成訂單編號并且生成訂單快照荚孵,不通過則直接返回到客戶端妹窖。
由于商品和價格都可能會發(fā)生變化,所以使用外鏈的方法會出現(xiàn)錯誤收叶,所以需要把當時商品的信息記錄下來骄呼。
創(chuàng)建訂單編號
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;
}
做完這一切的步驟后,就可以插入數(shù)據(jù)庫了
主要是錄入訂單信息,和錄入訂單商品信息(id蜓萄,關(guān)聯(lián)的訂單id隅茎,數(shù)量)
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.根據(jù)訂單號實現(xiàn)預支付
根據(jù)訂單號來判斷是否存在該訂單號,該訂單號是否屬于該token的uid嫉沽,該訂單號的狀態(tài)是否為未支付患膛,最后還需要再檢查庫存。
注:部分和上面方法通用耻蛇,需寫為一個通用的方法踪蹬。
實現(xiàn)了上面的步驟之后,就可以使用起微信的SDK了臣咖。
將微信的文件放在了該目錄下:
由于TP5.1取消了Loader載入的方法跃捣,所以我找了好久,使用的是Env來獲取微信文件的地址夺蛇。通過此來加載SDK的文件疚漆。
include Env::get('root_path') . 'extend/wxPay/WxPay.Api.php';
注:微信支付的WxPay.config改變了,我們需要繼承該方法刁赦,填入自己的appid娶聘,appsecret和mmid等信息才行。
引入微信的SDK后甚脉,首先需要做的就是設(shè)置各種屬性丸升。
$wxOrderData = new \WxPayUnifiedOrder();
// 會通過異步傳回來給我們
$wxOrderData->SetOut_trade_no($this->orderNum);
$wxOrderData->SetTrade_type('JSAPI');
$wxOrderData->SetTotal_fee($totalPrice * 100);
$wxOrderData->SetBody('零食商販');
$wxOrderData->SetOpenid($openid);
// 支付成功后,微信的回調(diào)牺氨,要用post提交
$wxOrderData->SetNotify_url('http://paysdk.weixin.qq.com/notify.php');
return $this->getPaySignature($wxOrderData);
設(shè)置好了微信需要的信息之后狡耻,就開始發(fā)送到微信了,這里需要將繼承的config傳入里面發(fā)送給微信
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;
}
如果獲取到的信息正確猴凹,就需要將需要的信息返回給客戶端夷狰,客戶端根據(jù)此信息來調(diào)用微信的支付接口。
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;
}
根據(jù)微信支付的信息郊霎,返回給客戶端相應(yīng)的信息沼头。SDK在設(shè)置完之后,通過GetValue可以傳換成數(shù)組
客戶端得到數(shù)據(jù)后調(diào)用該微信API就能實現(xiàn)微信支付了书劝。
4.微信支付成功后的回調(diào)處理
由于微信返回的是xml格式的數(shù)據(jù)进倍,所以同樣可以通過sdk來進行處理。
需要做的是繼承WxPayNotify庄撮,并且改寫里面的NotifyProcess方法背捌。然后調(diào)用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,微信會隔一段時間再發(fā)送一次POST請求亚铁。