• 微信支付
    • 申请微信账号和配置
    • 1. 添加支付页面的路由
    • 2. 添加vue页面
    • 看效果
    • 总结

    微信支付

    微信支付,最难的地方不在于技术,而是在于微信有一套自己的技术规范。

    建议每位同学都要从官方文档看起。 虽然市面上有一些集成工具,例如 Ping++ , 但是往往这些产品不是特别方便,收费也比较高昂。 出了问题不好调试。

    所以,我们对于核心技术,一定要亲自掌握。

    申请微信账号和配置

    这里就不详述了。 我们假设全部的账号都已经做好了。

    1. 添加支付页面的路由

    1. import Pay from '@/components/pay'
    2. Vue.use(Router)
    3. export default new Router({
    4. routes: [
    5. {
    6. path: '/shops/pay',
    7. name: 'Pay',
    8. component: Pay
    9. },
    10. ]
    11. })

    2. 添加vue页面

    1. <template>
    2. <div class="background">
    3. <header class="top_bar">
    4. <a onclick="window.history.go(-1)" class="icon_back"></a>
    5. <h3 class="cartname">订单支付</h3>
    6. </header>
    7. <div class="tast_list_bd" style="background-color: #F3F3F3; padding-top: 0; padding-bottom: 80px;">
    8. <div class="goods_detail" style="">
    9. <main class="detail_box">
    10. <span class="divider"></span>
    11. <form style="margin-top: 45px;">
    12. <div class="column is-12">
    13. <label class="label">收货人</label>
    14. <p class="control has-icon has-icon-right">
    15. <input name="name" v-model="mobile_user_name" v-validate="'required|required'" :class="{'input': true, 'is-danger': errors.has('name') }" type="text" placeholder="例如: 张三" autofocus="autofocus"/>
    16. <span v-show="errors.has('name')" class="help is-danger">收货人不能为空</span>
    17. </p>
    18. </div>
    19. <div class="column is-12">
    20. <label class="label">收货地址</label>
    21. <p class="control has-icon has-icon-right">
    22. <input name="url" v-model="mobile_user_address" v-validate="'required|required'" :class="{'input': true, 'is-danger': errors.has('url') }" type="text" placeholder="例如: 北京市朝阳区大望路西西里小区4栋2单元201"/>
    23. <span v-show="errors.has('url')" class="help is-danger">收货地址不能为空</span>
    24. </p>
    25. </div>
    26. <div class="column is-12">
    27. <label class="label">收货电话</label>
    28. <p class="control has-icon has-icon-right">
    29. <input name="phone" v-model="mobile_user_phone" v-validate="'required|numeric'" :class="{'input': true, 'is-danger': errors.has('phone') }" type="text" placeholder="例如: 18888888888"/>
    30. <span v-show="errors.has('phone')" class="help is-danger">电话号码不能为空</span>
    31. </p>
    32. </div>
    33. </form>
    34. <span class="divider"></span>
    35. <section class="product_info clearfix" v-if="single_pay">
    36. <div>
    37. <div class="fu_li_zhuan_qu" >
    38. <img :src="good_images[0]" class="logo_image"/>
    39. <div class="content" >
    40. <div class="title">
    41. {{good.name}}
    42. </div>
    43. <div class="logo_and_shop_name">
    44. <div class="product_pric">
    45. <span></span>
    46. <span class="rel_price">{{good.price}}</span>
    47. <span> &nbsp x {{buy_count}}</span>
    48. </div>
    49. </div>
    50. </div>
    51. </div>
    52. </div>
    53. </section>
    54. <section class="product_info clearfix" v-else v-for="product in cartProducts">
    55. <div>
    56. <div class="fu_li_zhuan_qu" >
    57. <img :src="product.image" class="logo_image"/>
    58. <div class="content" >
    59. <div class="title">
    60. {{product.title}}
    61. </div>
    62. <div class="logo_and_shop_name">
    63. <div class="product_pric">
    64. <span></span>
    65. <span class="rel_price">{{product.price}}</span>
    66. <span> &nbsp x {{product.quantity}}</span>
    67. </div>
    68. </div>
    69. </div>
    70. </div>
    71. </div>
    72. </section>
    73. <section>
    74. <span class="divider" style="height: 15px;"></span>
    75. <div class="extra_cost" style=" ">
    76. <span style="float: left; margin-left: 15px;"> 卖家留言:</span>
    77. <input v-model="guest_remarks" id="extra_charge" type="text" name="cost" placeholder="选填: 对本次交易的说明" style="border: 0; background-color: white;
    78. font-size: 15px; color: #48484b; outline: none; width: 60%;"></input>
    79. </div>
    80. </section>
    81. <section>
    82. <span class="divider"></span>
    83. <div class="extra_cost" style=" ">
    84. <span style="float: left; margin-left: 15px;"> 应付金额:</span>
    85. <div v-if="single_pay" class="rel_price" type="text" name="cost" style="border: 0; background-color: white;
    86. font-size: 20px; color: #ff621a; font-weight: bold; outline: none; text-align: right; padding-right: 20px;"> \{\{ total_cost | currency }}</div>
    87. <div v-else class="rel_price" type="text" name="cost" style="border: 0; background-color: white;
    88. font-size: 20px; color: #ff621a; font-weight: bold; outline: none; text-align: right; padding-right: 20px;"> \{\{ total | currency }}</div>
    89. </div>
    90. </section>
    91. </main>
    92. <span class="divider"></span>
    93. <div style="height: 55px; display: flex; width: 100%; padding: 0px 10px; background-color: #fff;" @click="">
    94. <div style="flex: 1; display: flex;">
    95. <div style="margin-top: 10px;">
    96. <img src="../../assets/微信icon@3x.png" style="width: 35px;"/>
    97. </div>
    98. <span style="margin-top: 8px; font-size: 18px; line-height:40px; margin-left: 10px;">微信支付</span>
    99. </div>
    100. <div style=" padding: 14px 10px;" @click="user_wechat">
    101. <img src="../../assets/选中3x.png" style="width: 28px;"/>
    102. </div>
    103. </div>
    104. </div>
    105. </div>
    106. <div class="shop_layout-scroll-absolute" style="">
    107. <div class="queding" @click="buy">
    108. 立即支付
    109. </div>
    110. </div>
    111. </div>
    112. </template>
    113. <script>
    114. import { go } from '../../libs/router'
    115. import { mapGetters } from 'vuex'
    116. export default{
    117. data(){
    118. return {
    119. good_images: [],
    120. good: "",
    121. buy_count: this.$route.query.buy_count,
    122. good_id: this.$route.query.good_id,
    123. open_id: this.$store.state.userInfo.open_id,
    124. mobile_user_address: '',
    125. mobile_user_name: '',
    126. mobile_user_phone: '',
    127. guest_remarks: '',
    128. is_use_wechat: false,
    129. }
    130. },
    131. watch:{
    132. },
    133. mounted(){
    134. if (this.single_pay) {
    135. this.$http.get(this.$configs.api + 'goods/goods_details?good_id=' + this.good_id).then((response)=>{
    136. console.info(this.good_id)
    137. console.info(response.body)
    138. this.good = response.body.good
    139. this.good_images = response.body.good_images
    140. },(error) => {
    141. console.error(error)
    142. });
    143. }
    144. },
    145. computed: {
    146. total () {
    147. return this.cartProducts.reduce((total, p) => {
    148. return (total + p.price * p.quantity)
    149. }, 0)
    150. },
    151. single_pay () {
    152. return this.good_id && this.buy_count
    153. },
    154. total_cost () {
    155. return this.good.price * this.buy_count
    156. },
    157. ...mapGetters({
    158. cartProducts: 'cartProducts',
    159. checkoutStatus: 'checkoutStatus'
    160. })
    161. },
    162. methods:{
    163. validateBeforeSubmit() {
    164. //拦截异步操作
    165. return new Promise((resolve, reject) => {
    166. this.$validator.validateAll().then(result => {
    167. console.info(result)
    168. if (result) {
    169. console.info("============表单验证成功===")
    170. resolve(true);
    171. } else {
    172. alert('请填写完整的收货信息!');
    173. resolve(false);
    174. }
    175. });
    176. })
    177. },
    178. plus () {
    179. this.buy_count = this.buy_count + 1
    180. },
    181. minus () {
    182. if(this.buy_count > 1) {
    183. this.buy_count = this.buy_count - 1
    184. }
    185. },
    186. user_wechat () {
    187. if (this.is_use_wechat === false) {
    188. this.is_use_wechat = true
    189. } else {
    190. this.is_use_wechat = false
    191. }
    192. },
    193. buy (){
    194. let result = this.validateBeforeSubmit().then((resolve)=>{
    195. if (resolve) {
    196. console.info('true ==== ')
    197. let params
    198. if (this.single_pay) {
    199. params = {
    200. good_id: this.good_id,
    201. buy_count: this.buy_count,
    202. total_cost: this.total_cost,
    203. guest_remarks: this.guest_remarks,
    204. mobile_user_address: this.mobile_user_address,
    205. mobile_user_name: this.mobile_user_name,
    206. mobile_user_phone: this.mobile_user_phone,
    207. open_id: this.open_id
    208. }
    209. } else {
    210. console.info(this.total)
    211. params = {
    212. goods: this.cartProducts,
    213. total_cost: this.total,
    214. guest_remarks: this.guest_remarks,
    215. mobile_user_address: this.mobile_user_address,
    216. mobile_user_name: this.mobile_user_name,
    217. mobile_user_phone: this.mobile_user_phone,
    218. open_id: this.open_id
    219. }
    220. }
    221. this.$http.post(this.$configs.api + 'goods/buy', params
    222. ).then((response) => {
    223. let order_number = response.body.order_number
    224. this.purchase(order_number)
    225. }, (error) => {
    226. console.error(error)
    227. });
    228. } else {
    229. console.info('== 请填写完整的收货信息')
    230. }
    231. });
    232. },
    233. purchase (order_number) {
    234. //调起微信支付界面
    235. if (typeof WeixinJSBridge == "undefined"){
    236. if( document.addEventListener ){
    237. document.addEventListener('WeixinJSBridgeReady', this.onBridgeReady, false);
    238. }else if (document.attachEvent){
    239. document.attachEvent('WeixinJSBridgeReady', this.onBridgeReady);
    240. document.attachEvent('onWeixinJSBridgeReady', this.onBridgeReady);
    241. }
    242. }else{
    243. this.onBridgeReady(order_number);
    244. }
    245. },
    246. onBridgeReady (order_number) {
    247. let that = this
    248. let total_cost
    249. if (this.single_pay) {
    250. total_cost = this.total_cost
    251. } else {
    252. total_cost = this.total
    253. }
    254. this.$http.post(this.$configs.api + 'payments/user_pay',
    255. {
    256. open_id: this.$store.state.userInfo.open_id,
    257. total_cost: total_cost,
    258. order_number: order_number
    259. }).then((response) => {
    260. WeixinJSBridge.invoke(
    261. 'getBrandWCPayRequest', {
    262. "appId": response.data.appId,
    263. "timeStamp": response.data.timeStamp,
    264. "nonceStr": response.data.nonceStr,
    265. "package": response.data.package,
    266. "signType": response.data.signType,
    267. "paySign": response.data.paySign
    268. },
    269. function(res){
    270. // 下面代码仅用于调试
    271. // alert("res.err_msg: " + res.err_msg + ", err_desc: " + res.err_desc)
    272. if(res.err_msg == "get_brand_wcpay_request:ok" ) {
    273. // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。
    274. that.$router.push({ path: '/shops/paysuccess?order_id=' + order_number });
    275. } else {
    276. // 显示取消支付或者失败
    277. that.$router.push({ path: '/shops/payfail?order_id=' + order_number });
    278. }
    279. }
    280. );
    281. }, (error) => {
    282. console.error(error)
    283. });
    284. }
    285. },
    286. }
    287. </script>

    核心代码如下:

    1. onBridgeReady (order_number) {
    2. //....
    3. this.$http.post(this.$configs.api + 'payments/user_pay',
    4. {
    5. open_id: this.$store.state.userInfo.open_id,
    6. total_cost: total_cost,
    7. order_number: order_number
    8. }).then((response) => {
    9. WeixinJSBridge.invoke(
    10. 'getBrandWCPayRequest', {
    11. "appId": response.data.appId,
    12. "timeStamp": response.data.timeStamp,
    13. //....
    14. },
    15. function(res){
    16. //...
    17. }
    18. );
    19. }, (error) => {
    20. console.error(error)
    21. });
    22. }

    上面的代码是用来给页面一准备好( WeixinJSBridge 准备好了)的时候,页面就要调用的。

    1. purchase (order_number) {
    2. //调起微信支付界面
    3. if (typeof WeixinJSBridge == "undefined"){
    4. if( document.addEventListener ){
    5. document.addEventListener('WeixinJSBridgeReady', this.onBridgeReady, false);
    6. }else if (document.attachEvent){
    7. document.attachEvent('WeixinJSBridgeReady', this.onBridgeReady);
    8. document.attachEvent('onWeixinJSBridgeReady', this.onBridgeReady);
    9. }
    10. }else{
    11. this.onBridgeReady(order_number);
    12. }
    13. },

    上面的代码用于调用出 “微信支付页面”. 其中的变量 WeixinJSBridge 是微信浏览器自带的变量, 不必声名,直接拿过来用就行。

    看效果

    微信的支付页面会跳出来 (图略)

    总结

    可以看到:

    1. 微信支付的细节处理,都交给了后台服务器端。 只要我们H5端把参数准备好,直接访问 http://shopweb.siwei.me/api/payments/user_pay 就可以了。
    2. 微信支付,分成单笔商品支付和多笔商品支付两种情况. 区别就是把参数重新组织一下即可。
    3. 新手对于 WeixinJSBridge 这个变量很难掌握, 一定要多看文档。 这个文档是 “微信公众号内支付”的文档, 不是微信APP , 或者微信普通H5的文档。 一定要梳理好逻辑。 另外,对于后端API的同学这里更难,建议多查多试。
    4. 在微信的后台,要配置不同的支付目录。 安卓和IOS 的粗略是不一样的。 建议大家百度一下。
    5. 微信的支付场景对应的支付方式和实现方式是不一样的。 本例是“微信的公众号内支付”。

    微信的官方文档中, 提供的例子都是基于经典的WEB页面(整体刷新的那种)的,目前还没有看到SPA的例子。 但是大家的问题很多。 我的个人博客也记录了一些内容: http://siwei.me/blog/posts/--27

    由于篇幅限制,微信相关的内容不再赘述。