小程序用TP5.1从下订单到实现微信支付

这篇文章主要是想整理一下学习了微信小程序商城构建全栈应用微信支付时候的思路和实现的方法。不然老是忘记。
主要的步骤有
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请求。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354