M_purchase.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724
  1. <template>
  2. <view class="page">
  3. <view class="topBody">
  4. <PageHeader title="M币充值" class="PageHeader">
  5. <template v-slot:right>
  6. <DropdownMenu :options="dropdownOptions" @select="handleDropdownSelect" />
  7. </template>
  8. </PageHeader>
  9. <view class="reserveASeat"></view>
  10. <view class="myGoldCoin-box">
  11. <view>我的M币</view>
  12. <view class="myGoldCoin-box-content">
  13. <image src="../../static/icon/coin_m.png" mode="widthFix" />
  14. <text>{{ myGoldCoin }}</text>
  15. </view>
  16. </view>
  17. <view class="myinfo">
  18. <view class="purchaseList" style="margin-top: 32rpx">
  19. <view class="item" :class="index == sel ? 'itemSel' : ''" v-for="(item, index) in list"
  20. @click="selTA(item, index)" :key="index">
  21. <view class="num1">
  22. <image src="../../static/icon/coin_m.png" mode="widthFix" />
  23. <view class="name">{{ item.num_gmm| formatNumberToK }} </view>
  24. </view>
  25. <view class="num2">
  26. <view class="name">{{ $t("txt.¥") }}{{ item.money }}</view>
  27. </view>
  28. </view>
  29. </view>
  30. <view v-if="!isIOS" class="jinchu">
  31. <text>{{ $t("txt.支付方式") }}</text>
  32. </view>
  33. <view v-if="isWeChatPay && !isIOS" class="mingxiList" @click="selPay('wechat')">
  34. <view class="left">
  35. <image class="icon" src="../../static/me/icon_wechat.png" mode="widthFix" />
  36. <text style="font-size: 28rpx; margin-left: 20rpx">{{
  37. $t("txt.微信支付")
  38. }}</text>
  39. </view>
  40. <view class="right">
  41. <image class="icon" :src="
  42. payType != 'wechat'
  43. ? '../../static/icon/wd_icon_gouxuan04.png'
  44. : '../../static/icon/wd_icon_gouxuan05.png'
  45. " />
  46. </view>
  47. </view>
  48. <!-- <view class="line"></view> -->
  49. <view v-if="!isIOS" class="mingxiList" @click="selPay('alipay')">
  50. <view class="left">
  51. <image class="icon" src="../../static/me/icon_alipay.png" mode="widthFix" />
  52. <text style="font-size: 28rpx; margin-left: 20rpx">{{
  53. $t("txt.支付宝支付")
  54. }}</text>
  55. </view>
  56. <view class="right">
  57. <image class="icon" :src="
  58. payType != 'alipay'
  59. ? '../../static/icon/wd_icon_gouxuan04.png'
  60. : '../../static/icon/wd_icon_gouxuan05.png'
  61. " />
  62. </view>
  63. </view>
  64. <!-- <view class="line"></view> -->
  65. </view>
  66. <view class="agree">
  67. <view class="agree2" @click="agreeChk()">
  68. <image src="../../static/icon/wd_icon_gouxuan04.png" v-if="is_agree == 0"></image>
  69. <image src="../../static/icon/wd_icon_gouxuan05.png" v-if="is_agree == 1"></image>
  70. </view>
  71. <view>
  72. 同意
  73. <text class="xy" @click="goPage('/pages/AboutUs/pay_xy')">
  74. 《充值服务协议》 </text>,充值M币仅【萌创星球】使用,点击查看
  75. <text class="xy" @click="goPage('/pages/vip/record?type=coin')"> 充值记录 </text>
  76. </view>
  77. </view>
  78. <view class="btn_submit" :class="{'btn-loading': isSubmitting}" @click="submitData">
  79. <text v-if="!isSubmitting">¥{{ money }}</text>
  80. <text v-if="!isSubmitting">确认充值</text>
  81. <view v-if="isSubmitting" class="loading-spinner"></view>
  82. </view>
  83. <view class="blankHeight"></view>
  84. </view>
  85. <!-- 提示框 -->
  86. <DialogBox ref="DialogBox"></DialogBox>
  87. </view>
  88. </template>
  89. <script>
  90. import {
  91. Iap,
  92. IapTransactionState
  93. } from "../../common/iap.js"
  94. import DropdownMenu from '@/components/DropdownMenu.vue'
  95. import {
  96. mapState
  97. } from 'vuex'
  98. export default {
  99. components: {
  100. DropdownMenu
  101. },
  102. computed: {
  103. ...mapState('hideModule', ['isWeChatPay'])
  104. },
  105. data() {
  106. return {
  107. title: "",
  108. sel: 1,
  109. payType: "wechat",
  110. list: [],
  111. money: 0,
  112. tid: 0,
  113. linkid: "",
  114. is_agree: 0,
  115. myGoldCoin: 0,
  116. dropdownOptions: [{
  117. label: '购买记录',
  118. type: 'vipRecord'
  119. }],
  120. isSubmitting: false,
  121. lastClickTime: 0,
  122. isIOS: false, // 是否为iOS系统
  123. currentOrderId: "", // 当前苹果内购订单ID
  124. productId: "",
  125. productList: [],
  126. appleProducts: [], // 苹果产品列表
  127. appleProductsLoaded: false // 苹果产品是否已加载
  128. };
  129. },
  130. onLoad() {
  131. // setTimeout(function() {
  132. // uni.setNavigationBarColor({
  133. // frontColor: '#ffffff',
  134. // backgroundColor: '#00000000',
  135. // animation: {
  136. // duration: 400,
  137. // timingFunc: 'easeIn'
  138. // }
  139. // })
  140. // }, 200);
  141. },
  142. onShow() {
  143. this.checkPlatform();
  144. this.loadData();
  145. let that = this;
  146. },
  147. methods: {
  148. onBack() {},
  149. goPage(page) {
  150. uni.navigateTo({
  151. url: page,
  152. });
  153. },
  154. selTA(item, se) {
  155. this.tid = item.id;
  156. this.sel = se;
  157. if (this.list != null && this.list != undefined) {
  158. this.money = this.list[se]["money"];
  159. }
  160. },
  161. selPay(se) {
  162. this.payType = se;
  163. },
  164. chkSel() {
  165. if (this.sel == 1) {
  166. this.sel = 0;
  167. } else {
  168. this.sel = 1;
  169. }
  170. },
  171. submitData() {
  172. const now = Date.now();
  173. if (now - this.lastClickTime < 3000) {
  174. uni.showToast({
  175. title: "请勿频繁点击",
  176. icon: "none"
  177. });
  178. return;
  179. }
  180. this.lastClickTime = now;
  181. if (this.isSubmitting) return;
  182. if (this.is_agree == 0) {
  183. uni.showToast({
  184. title: "请确认并选择协议",
  185. icon: "none",
  186. });
  187. return;
  188. }
  189. this.isSubmitting = true;
  190. let that = this;
  191. // iOS系统使用苹果内购
  192. if (this.isIOS) {
  193. this.submitAppleIap();
  194. return;
  195. }
  196. uni.request({
  197. url: this.$apiHost + "/Order/submit",
  198. data: {
  199. uuid: getApp().globalData.uuid,
  200. product_id: this.tid,
  201. type: "buyM",
  202. payType: this.payType,
  203. },
  204. header: {
  205. "content-type": "application/json",
  206. },
  207. success: (res) => {
  208. console.log("res-pay", res.data);
  209. if (res.data.success == "yes") {
  210. this.linkid = res.data.linkid;
  211. // 微信支付逻辑
  212. if (this.payType === "wechat") {
  213. uni.requestPayment({
  214. provider: "wxpay",
  215. orderInfo: {
  216. appid: res.data.wepay.appid,
  217. partnerid: res.data.wepay.partnerid,
  218. prepayid: res.data.wepay.prepayid,
  219. package: "Sign=WXPay",
  220. noncestr: res.data.wepay.noncestr,
  221. timestamp: res.data.wepay.timestamp,
  222. sign: res.data.wepay.sign
  223. },
  224. success(res) {
  225. console.log("微信支付成功:", res);
  226. setTimeout(function() {
  227. that.showPayCall();
  228. }, 1000);
  229. },
  230. fail(e) {
  231. console.log("微信支付失败:", e);
  232. uni.showToast({
  233. title: "支付失败,请重试",
  234. icon: "none"
  235. });
  236. }
  237. });
  238. }
  239. // 支付宝支付逻辑
  240. else if (this.payType === "alipay") {
  241. uni.requestPayment({
  242. provider: "alipay",
  243. orderInfo: res.data.ali_pay, // 直接使用后端返回的支付宝支付参数
  244. success(res) {
  245. console.log("支付宝支付成功:", res);
  246. setTimeout(function() {
  247. that.showPayCall();
  248. }, 1000);
  249. },
  250. fail(e) {
  251. console.log("支付宝支付失败:", e);
  252. uni.showToast({
  253. title: "支付失败,请重试",
  254. icon: "none"
  255. });
  256. }
  257. });
  258. }
  259. } else {
  260. uni.showToast({
  261. title: "创建订单失败,请联系客服",
  262. icon: "none",
  263. });
  264. }
  265. },
  266. fail: (err) => {
  267. console.log("请求失败:", err);
  268. uni.showToast({
  269. title: "网络错误,请重试",
  270. icon: "none"
  271. });
  272. },
  273. complete: () => {
  274. that.isSubmitting = false;
  275. }
  276. });
  277. },
  278. showPayCall() {
  279. let that = this;
  280. this.$refs["DialogBox"]
  281. .confirm({
  282. title: "提示",
  283. content: "我已经支付完成",
  284. DialogType: "inquiry",
  285. btn1: "否",
  286. btn2: "是",
  287. animation: 0,
  288. })
  289. .then((res) => {
  290. uni.request({
  291. url: this.$apiHost + "/Order/getstatus",
  292. data: {
  293. uuid: getApp().globalData.uuid,
  294. linkid: that.linkid,
  295. },
  296. header: {
  297. "content-type": "application/json",
  298. },
  299. success: (res) => {
  300. if (res.data.success == "yes") {
  301. uni.showToast({
  302. title: "充值成功",
  303. icon: "none",
  304. });
  305. } else {
  306. uni.showToast({
  307. title: "还未检测到充值状态,请稍后再试",
  308. icon: "none",
  309. });
  310. setTimeout(function() {
  311. that.showPayCall();
  312. }, 1000);
  313. }
  314. },
  315. complete: (com) => {},
  316. });
  317. });
  318. },
  319. loadData() {
  320. const that = this;
  321. // 获取M币数量
  322. uni.request({
  323. url: this.$apiHost + '/My/getnum',
  324. method: 'GET',
  325. header: {
  326. 'content-type': 'application/json',
  327. 'sign': getApp().globalData.headerSign
  328. },
  329. data: {
  330. uuid: getApp().globalData.uuid
  331. },
  332. success: (res) => {
  333. console.log("获取用户M币数量:", res.data);
  334. if (res.data && res.data.num_gmm) {
  335. this.myGoldCoin = res.data.num_gmm;
  336. }
  337. }
  338. });
  339. // 获取充值列表
  340. uni.request({
  341. url: this.$apiHost + "/User/getCzList",
  342. data: {
  343. uuid: getApp().globalData.uuid,
  344. },
  345. header: {
  346. "content-type": "application/json",
  347. },
  348. success: (res) => {
  349. console.log("res", res.data);
  350. this.num = res.data.num;
  351. if (res.data.list != null && res.data.list != undefined) {
  352. this.list = res.data.list;
  353. this.tid = this.list[1]["id"];
  354. this.sel = 1;
  355. this.money = this.list[1]["money"];
  356. let SpecArr = [];
  357. for (let i = 0; i < this.list.length; i++) {
  358. SpecArr.push(this.list[i].spec)
  359. }
  360. // #ifdef APP-PLUS-IOS
  361. // 创建示例
  362. this._iap = new Iap({
  363. products: SpecArr // 苹果开发者中心创建
  364. })
  365. this.initApple();
  366. // #endif
  367. // 充值列表已加载完成
  368. }
  369. },
  370. });
  371. },
  372. async initApple() {
  373. uni.showLoading({
  374. title: '检测支付环境...'
  375. });
  376. try {
  377. // 初始化,获取iap支付通道
  378. await this._iap.init();
  379. // 从苹果服务器获取产品列表
  380. this.productList = await this._iap.getProduct();
  381. this.productList[0].checked = true;
  382. this.productId = this.productList[0].productid;
  383. // 填充产品列表,启用界面
  384. this.disabled = false;
  385. } catch (e) {
  386. uni.showModal({
  387. title: "init",
  388. content: e.message,
  389. showCancel: false
  390. });
  391. } finally {
  392. uni.hideLoading();
  393. }
  394. if (this._iap.ready) {
  395. this.restoreComplateRequest();
  396. }
  397. },
  398. handleDropdownSelect(item) {
  399. switch (item.type) {
  400. case "vipRecord":
  401. uni.navigateTo({
  402. url: "/pages/vip/record?type=coin",
  403. });
  404. break;
  405. }
  406. },
  407. agreeChk() {
  408. if (this.is_agree == 0) {
  409. this.is_agree = 1;
  410. } else {
  411. this.is_agree = 0;
  412. }
  413. },
  414. // 检查平台类型
  415. checkPlatform() {
  416. const systemInfo = uni.getSystemInfoSync();
  417. this.isIOS = systemInfo.platform === 'ios';
  418. console.log('当前平台:', systemInfo.platform, '是否为iOS:', this.isIOS);
  419. // iOS系统已检测完成
  420. },
  421. // 验证苹果产品是否可用(简化版本)
  422. validateAppleProduct(productId) {
  423. // 对于M币充值,我们采用简化的验证方式
  424. // 在实际购买时如果产品ID无效,苹果会返回错误
  425. console.log('验证苹果产品ID:', productId);
  426. // 基本验证:检查产品ID是否符合苹果内购产品ID格式
  427. if (!productId || typeof productId !== 'string' || productId.length === 0) {
  428. return false;
  429. }
  430. // 标记已加载,避免重复检查
  431. this.appleProductsLoaded = true;
  432. return true;
  433. },
  434. // 苹果内购提交
  435. submitAppleIap() {
  436. let that = this;
  437. // 获取当前选中的充值项目信息
  438. const currentItem = this.list[this.sel];
  439. console.log("当前选中的充值项目:", currentItem);
  440. if (!currentItem || !currentItem.spec) {
  441. uni.showToast({
  442. title: "产品配置错误,请联系客服",
  443. icon: "none",
  444. });
  445. that.isSubmitting = false;
  446. return;
  447. }
  448. console.log("准备创建苹果内购订单 - 产品ID:", this.tid, "苹果产品ID:", currentItem.spec);
  449. // 先创建订单
  450. uni.request({
  451. url: this.$apiHost + "/AppleIap/submit",
  452. method: "GET",
  453. data: {
  454. uuid: getApp().globalData.uuid,
  455. product_id: this.tid,
  456. type: "buyM"
  457. },
  458. header: {
  459. "content-type": "application/json",
  460. sign: getApp().globalData.headerSign,
  461. },
  462. success: (res) => {
  463. console.log("苹果内购订单创建响应:", res.data);
  464. if (res.data.success == "yes") {
  465. that.currentOrderId = res.data.order_id;
  466. // 使用数据库中配置的spec作为苹果产品ID
  467. const appleProductId = res.data.apple_product_id || currentItem.spec;
  468. console.log("使用苹果产品ID进行支付:", appleProductId);
  469. // 调用苹果内购
  470. that.requestApplePayment(appleProductId);
  471. } else {
  472. uni.showToast({
  473. title: res.data.msg || "创建订单失败",
  474. icon: "none",
  475. });
  476. that.isSubmitting = false;
  477. }
  478. },
  479. complete: (com) => {
  480. // uni.hideLoading();
  481. },
  482. fail: (err) => {
  483. console.log("完整错误信息:", JSON.stringify(err));
  484. console.log("错误消息:", err.errMsg);
  485. console.log("状态码:", err.statusCode);
  486. let errorMessage = "网络错误,请重试";
  487. if (err.errMsg) {
  488. if (err.errMsg.includes('timeout')) {
  489. errorMessage = "请求超时,请检查网络";
  490. } else if (err.errMsg.includes('fail')) {
  491. errorMessage = "网络连接失败";
  492. }
  493. }
  494. uni.showToast({
  495. title: errorMessage,
  496. icon: "none"
  497. });
  498. that.isSubmitting = false;
  499. }
  500. });
  501. },
  502. // 调用苹果内购
  503. requestApplePayment(productId) {
  504. let that = this;
  505. // 验证产品ID是否有效
  506. if (!this.validateAppleProduct(productId)) {
  507. console.log('苹果产品ID无效:', productId);
  508. uni.showToast({
  509. title: '产品ID无效',
  510. icon: 'none'
  511. });
  512. this.isSubmitting = false;
  513. return;
  514. }
  515. console.log('开始苹果内购,产品ID:', productId);
  516. console.log('苹果内购参数:', {
  517. provider: 'appleiap',
  518. orderInfo: {
  519. productid: productId
  520. }
  521. });
  522. // 请求苹果内购
  523. uni.requestPayment({
  524. provider: 'appleiap',
  525. orderInfo: {
  526. productid: productId
  527. },
  528. success: function(res) {
  529. console.log('苹果内购支付成功:', res);
  530. // 验证收据
  531. that.verifyAppleReceipt(res.transactionReceipt, res.transactionIdentifier);
  532. },
  533. fail: function(err) {
  534. console.log('苹果内购支付失败详情:', err);
  535. let errorMessage = '支付失败';
  536. // 根据错误类型给出更具体的提示
  537. if (err.errMsg) {
  538. if (err.errMsg.includes('cancel')) {
  539. errorMessage = '支付已取消';
  540. } else if (err.errMsg.includes('订单的ID不存在') || err.errMsg.includes('product')) {
  541. errorMessage = '产品ID不存在,请检查App Store Connect配置';
  542. console.error('产品ID配置错误 - 当前产品ID:', productId);
  543. console.error('请确认以下事项:');
  544. console.error('1. App Store Connect中是否已创建此产品ID');
  545. console.error('2. 产品状态是否为"Ready for Sale"');
  546. console.error('3. Bundle ID是否匹配');
  547. console.error('4. 是否在正确的环境(沙盒/生产)');
  548. } else if (err.errMsg.includes('network')) {
  549. errorMessage = '网络错误,请检查网络连接';
  550. } else {
  551. errorMessage = '支付失败: ' + (err.errMsg || JSON.stringify(err));
  552. }
  553. }
  554. uni.showToast({
  555. title: errorMessage,
  556. icon: 'none',
  557. duration: 3000
  558. });
  559. that.isSubmitting = false;
  560. }
  561. });
  562. },
  563. // 验证苹果收据
  564. verifyAppleReceipt(receiptData, transactionId) {
  565. let that = this;
  566. uni.request({
  567. url: that.$apiHost + "/AppleIap/verify",
  568. method: 'POST',
  569. data: {
  570. uuid: getApp().globalData.uuid,
  571. order_id: that.currentOrderId,
  572. receipt_data: receiptData,
  573. transaction_id: transactionId
  574. },
  575. header: {
  576. "content-type": "application/json",
  577. },
  578. success: (res) => {
  579. console.log("苹果收据验证结果:", res.data);
  580. if (res.data.success == "yes") {
  581. uni.showToast({
  582. title: "充值成功",
  583. icon: "success",
  584. });
  585. // 刷新M币数量
  586. that.loadData();
  587. } else {
  588. uni.showToast({
  589. title: res.data.msg || "验证失败",
  590. icon: "none",
  591. });
  592. }
  593. that.isSubmitting = false;
  594. },
  595. fail: (err) => {
  596. console.log("验证苹果收据失败:", err);
  597. uni.showToast({
  598. title: "验证失败,请联系客服",
  599. icon: "none"
  600. });
  601. that.isSubmitting = false;
  602. }
  603. });
  604. },
  605. },
  606. };
  607. </script>
  608. <style scoped lang="scss">
  609. @import "M_purchase.scss";
  610. .agree {
  611. width: 90%;
  612. margin: 0 auto;
  613. color: #666666;
  614. font-size: 24rpx;
  615. display: flex;
  616. align-items: center;
  617. text-align: left;
  618. line-height: 32rpx;
  619. position: absolute;
  620. bottom: 215rpx;
  621. left: 50%;
  622. transform: translateX(-50%);
  623. .agree2 {
  624. display: flex;
  625. flex-direction: row;
  626. justify-content: flex-start;
  627. align-items: center;
  628. padding-right: 8rpx;
  629. flex-shrink: 0;
  630. padding-bottom: 34rpx;
  631. }
  632. .xy {
  633. color: #0084ff;
  634. display: inline;
  635. }
  636. image {
  637. width: 32rpx;
  638. height: 32rpx;
  639. }
  640. }
  641. .btn_submit {
  642. width: 626rpx;
  643. height: 88rpx;
  644. background: linear-gradient(to left, #1f1f1f, #444444);
  645. border-radius: 76rpx;
  646. margin: 0 auto;
  647. margin-top: 70rpx;
  648. color: #acf934;
  649. display: flex;
  650. align-items: center;
  651. justify-content: center;
  652. font-size: 32rpx;
  653. line-height: 0;
  654. position: relative;
  655. overflow: hidden;
  656. transition: all 0.3s ease;
  657. position: absolute;
  658. bottom: 100rpx;
  659. left: 50%;
  660. transform: translateX(-50%);
  661. &.btn-loading {
  662. opacity: 0.7;
  663. pointer-events: none;
  664. }
  665. .loading-spinner {
  666. width: 40rpx;
  667. height: 40rpx;
  668. border: 4rpx solid rgba(255, 255, 255, 0.3);
  669. border-radius: 50%;
  670. border-top-color: #fff;
  671. animation: spin 1s linear infinite;
  672. }
  673. text {
  674. display: inline-block;
  675. margin-right: 10rpx;
  676. }
  677. }
  678. @keyframes spin {
  679. to {
  680. transform: rotate(360deg);
  681. }
  682. }
  683. </style>