瀏覽代碼

Merge branch 'master' of www.gitinn.com:zaixianjiaoyu/sourcecode

KaysonCui 5 年之前
父節點
當前提交
505d3f3158
共有 35 個文件被更改,包括 544 次插入146 次删除
  1. 33 0
      front/project/Constant.js
  2. 1 0
      front/project/h5/routes/page/bind/page.js
  3. 81 47
      front/project/h5/routes/page/pay/page.js
  4. 7 3
      front/project/h5/stores/order.js
  5. 1 1
      front/project/www/app.less
  6. 17 2
      front/project/www/components/Login/index.js
  7. 50 5
      front/project/www/components/VipRenew/index.js
  8. 15 5
      front/project/www/routes/my/collect/page.js
  9. 3 3
      front/project/www/routes/my/error/page.js
  10. 6 3
      front/project/www/routes/my/message/page.js
  11. 2 2
      front/project/www/routes/my/note/page.js
  12. 9 7
      front/project/www/routes/my/order/page.js
  13. 40 5
      front/project/www/routes/my/tools/page.js
  14. 10 0
      front/project/www/routes/page/contract/index.js
  15. 3 0
      front/project/www/routes/page/contract/index.less
  16. 30 0
      front/project/www/routes/page/contract/page.js
  17. 2 1
      front/project/www/routes/page/index.js
  18. 0 10
      front/project/www/routes/page/order/index.js
  19. 4 0
      front/project/www/stores/my.js
  20. 7 3
      front/project/www/stores/order.js
  21. 17 7
      server/gateway-api/src/main/java/com/qxgmat/controller/api/OrderController.java
  22. 34 12
      server/gateway-api/src/main/java/com/qxgmat/dto/extend/UserOrderRecordExtendDto.java
  23. 27 0
      server/gateway-api/src/main/java/com/qxgmat/dto/request/RecordChangeDto.java
  24. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserOrderDetailDto.java
  25. 1 0
      server/gateway-api/src/main/java/com/qxgmat/help/MailHelp.java
  26. 10 0
      server/gateway-api/src/main/java/com/qxgmat/service/annotation/ChangeCheckout.java
  27. 81 12
      server/gateway-api/src/main/java/com/qxgmat/service/extend/OrderFlowService.java
  28. 8 1
      server/gateway-api/src/main/java/com/qxgmat/service/extend/TradeService.java
  29. 2 0
      server/gateway-api/src/main/profile/dev/application-runtime.yml
  30. 2 0
      server/gateway-api/src/main/profile/prod/application-runtime.yml
  31. 2 0
      server/gateway-api/src/main/profile/test/application-runtime.yml
  32. 11 10
      server/gateway-api/src/main/resources/application.yml
  33. 5 4
      server/tools/src/main/java/com/nuliji/tools/pay/WechatPay.java
  34. 1 1
      server/tools/src/main/java/com/nuliji/tools/third/sendcloud/SendCloudMail.java
  35. 12 2
      server/tools/src/main/java/com/tencent/protocol/pay_protocol/UnifiedOrderReqData.java

+ 33 - 0
front/project/Constant.js

@@ -191,6 +191,39 @@ export const AdPlace = [
   { label: '顶部', value: 'top', parent: 'course-index' },
 ];
 
+export const OrderInfoMap = {
+  service: {
+    qx_cat: {
+      service: '共包含6套CAT模考,在有效期内可重置一次,每套模考可做两次。',
+      refund_policy: '本商品为虚拟产品,购买成功后不支持退款。',
+      copyright_notes: '本商品仅限购买者本人使用,不可商用和传播。',
+      result: '您已成功购买“千行-CAT模考”服务,\n确认邮件已发送至您的邮箱:{email},请注意查收。\n您可至“个人中心- 工具”开通。',
+    },
+    vip: {
+      result: '您已成功购买“VIP({expireDays}天)”服务,\n确认邮件已发送至您的邮箱:{email},请注意查收。\nVIP有效期至:{endTime}',
+    },
+    textbook: {
+      service: '数学机经+阅读机经+逻辑机经,可在线查阅、在线练习或下载至本地,同时自动更新至预留邮箱。',
+      refund_policy: '本商品为虚拟产品,购买成功后不支持退款。',
+      copyright_notes: '本商品仅限购买者本人使用,不可商用和传播。',
+      result: '您已成功购买“机经”服务,\n确认邮件已发送至您的邮箱:{email},请注意查收。\n您可至“个人中心-工具”开通。',
+    },
+  },
+  data: {
+    refund_policy: '本商品为虚拟产品,购买成功后不支持退款。',
+    copyright_notes: '本商品仅限购买者本人使用,不可商用和传播。',
+    result: '您已成功购买“千行-CAT模考”服务,\n确认邮件已发送至您的邮箱:{email},请注意查收。\n您可至“个人中心- 工具”开通。',
+  },
+  course: {
+    refund_policy: '本商品为虚拟产品,购买成功后不支持退款。',
+    copyright_notes: '本商品仅限购买者本人使用,不可商用和传播。',
+  },
+  course_package: {
+    refund_policy: '本商品为虚拟产品,购买成功后不支持退款。',
+    copyright_notes: '本商品仅限购买者本人使用,不可商用和传播。',
+  },
+};
+
 export const TimeRange = [{ title: '今天', key: 'today' }, { title: '近一周', key: 'week' }, { title: '近一个月', key: 'month' }, { title: '近三个月', key: 'month3' }, { title: '全部', key: 'all' }];
 
 export const TextbookMinYear = 2018;

+ 1 - 0
front/project/h5/routes/page/bind/page.js

@@ -136,6 +136,7 @@ export default class extends Page {
           margin={25}
           radius
           block
+          disabled={this.state.validError || this.state.mobileError}
           onClick={() => {
             this.submit();
           }}

+ 81 - 47
front/project/h5/routes/page/pay/page.js

@@ -1,17 +1,16 @@
 import React from 'react';
 import './index.less';
-import { Link } from 'react-router-dom';
 import Page from '@src/containers/Page';
 import { Checkbox } from 'antd';
 // import Assets from '@src/components/Assets';
-// import { getMap, formatDate } from '@src/services/Tools';
+import { getMap } from '@src/services/Tools';
 import Button from '../../../components/Button';
 import Money from '../../../components/Money';
 import { Order } from '../../../stores/order';
 import { Main } from '../../../stores/main';
-// import { ServiceKey } from '../../../../Constant';
+import { ServiceKey, OrderInfoMap } from '../../../../Constant';
 
-// const ServiceKeyMap = getMap(ServiceKey, 'value', 'label');
+const ServiceKeyMap = getMap(ServiceKey, 'value', 'label');
 
 export default class extends Page {
   initState() {
@@ -20,8 +19,40 @@ export default class extends Page {
 
   initData() {
     const { id } = this.params;
-    Order.getOrder(id).then(result => {
-      this.setState({ data: result });
+    Order.getOrder(id).then(order => {
+      order.packageMap = {};
+      (order.packages || []).forEach(row => {
+        order.packageMap[row.id] = row;
+      });
+      order.courseMap = {};
+      (order.courses || []).forEach(row => {
+        order.courseMap[row.id] = row;
+      });
+      order.dataMap = {};
+      (order.datas || []).forEach(row => {
+        order.dataMap[row.id] = row;
+      });
+      order.checkouts.forEach(checkout => {
+        checkout.info = OrderInfoMap[checkout.productType];
+        switch (checkout.productType) {
+          case 'service':
+            checkout.title = ServiceKeyMap[checkout.service];
+            checkout.info = checkout.info[checkout.service];
+            break;
+          case 'data':
+            checkout.title = order.dataMap[checkout.productId].title;
+            break;
+          case 'course_package':
+            checkout.title = order.packageMap[checkout.productId].title;
+            break;
+          case 'course':
+            checkout.title = order.courseMap[checkout.productId].title;
+            break;
+          default:
+        }
+      });
+      const [checkout] = order.checkouts.filter(row => row.parentId === 0);
+      this.setState({ order, checkout });
     });
     Main.getContract('course').then(result => {
       this.setState({ contract: result });
@@ -30,103 +61,106 @@ export default class extends Page {
 
   pay() {
     const { id } = this.params;
-    Order.wechatJs(id).then(() => {});
+    Order.wechatJs(id).then(() => { });
   }
 
   renderView() {
-    const { productType } = this.state;
-    let info = '';
-    if (productType === 'data') {
-      info = this.renderData();
+    const { order = {}, contract = {}, checkout = {} } = this.state;
+    const { info = {}, productType } = checkout;
+    let content = '';
+    switch (productType) {
+      case 'data':
+        content = this.renderData();
+        break;
+      case 'course_package':
+        content = this.renderCoursePackage();
+        break;
+      case 'course':
+      case 'service':
+        content = this.renderSingle();
+        break;
+      default:
     }
-    if (productType === 'course_package') {
-      info = this.renderCoursePackage();
-    } else {
-      info = this.renderSingle();
-    }
-    info = this.renderData();
     return (
       <div className="detail">
         <div className="title" style={{ marginBottom: 0 }}>
           商品信息
         </div>
-        {info}
-        <div className="title">退款政策</div>
-        <div className="desc">本产品为虚拟产品,购买成功后不支持退款。</div>
-        <div className="title">版权说明</div>
-        <div className="desc">本商品仅限购买者本人使用,不可商用和传播。</div>
-        <div className="agree">
-          <Checkbox />
-          我已阅读并同意 <Link to="">千行课程购买协议</Link>
-        </div>
+        {content}
+        {info.refund_policy && [< div className="title">退款政策</div>,
+          <div className="desc">本产品为虚拟产品,购买成功后不支持退款。</div>]}
+        {info.copyright_notes && [<div className="title">版权说明</div>,
+          <div className="desc">本商品仅限购买者本人使用,不可商用和传播。</div>]}
+        {order.productTypes && order.productTypes.indexOf('course') > 0 && <div className="agree">
+          <Checkbox checked />
+          我已阅读并同意 <a onClick={() => this.setState({ showContract: true })}>{contract.title}</a>
+        </div>}
         <div className="fixed">
           <div className="tip">*若在购买过程中遇到问题,请联系千行小助手协助解决</div>
           <div className="fee">
-            应付: <Money value={1000} size="lager" />
+            应付: <Money value={order.money} size="lager" />
           </div>
           <Button
             width={110}
             className="f-r"
             radius
             onClick={() => {
-              this.buy();
+              this.pay();
             }}
           >
             立即购买
           </Button>
         </div>
-      </div>
+      </div >
     );
   }
 
   renderData() {
+    const { checkout } = this.state;
     return (
       <div className="info data">
         <div className="info-block">
-          XXXXXX资料 <Money className="f-r" value={300} />
+          {checkout.title} <Money className="f-r" value={checkout.money} />
         </div>
         <div className="info-block">
-          <div className="info-item">
-            OG 20 语法 SC <span className="f-r">开通有效期: 3个月 使用有效期: 3个月</span>
-          </div>
-          <div className="info-item">
-            OG 20 语法 SC <span className="f-r">开通有效期: 3个月 使用有效期: 3个月</span>
-          </div>
-          <div className="info-item">
-            OG 20 语法 SC <span className="f-r">开通有效期: 3个月 使用有效期: 3个月</span>
-          </div>
+          开通有效期 <span className="f-r">{checkout.expireDays ? `${checkout.expireDays}天` : '永久'}</span>
         </div>
       </div>
     );
   }
 
   renderCoursePackage() {
+    const { checkout, order } = this.state;
     return (
       <div className="info course-package">
         <div className="info-block">
-          OG 20套餐 <Money className="f-r" value={300} />
-        </div>
-        <div className="info-block">
-          开通有效期 <span className="f-r">3个月</span>
+          {checkout.title} <Money className="f-r" value={checkout.money} />
         </div>
         <div className="info-block">
-          使用有效期 <span className="f-r">3个月</span>
+          {order.checkouts.map(row => {
+            if (row.parentId === 0) return null;
+            return <div className="info-item">
+              {row.title} <span className="f-r">开通有效期: {checkout.expireDays ? `${checkout.expireDays}天` : '永久'} 使用有效期: {checkout.useExpireDays ? `${checkout.useExpireDays}天` : '永久'}</span>
+            </div>;
+          })}
         </div>
       </div>
     );
   }
 
   renderSingle() {
+    const { checkout } = this.state;
+    console.log(checkout);
     return (
       <div className="info single">
         <div className="info-block">
-          OG 20套餐 <Money className="f-r" value={300} />
+          {checkout.title} <Money className="f-r" value={checkout.money} />
         </div>
         <div className="info-block">
-          开通有效期 <span className="f-r">3个月</span>
+          开通有效期 <span className="f-r">{checkout.expireDays ? `${checkout.expireDays}天` : '永久'}</span>
         </div>
         <div className="info-block">
-          使用有效期 <span className="f-r">3个月</span>
+          使用有效期 <span className="f-r">{checkout.useExpireDays ? `${checkout.useExpireDays}天` : '永久'}</span>
         </div>
       </div>
     );

+ 7 - 3
front/project/h5/stores/order.js

@@ -10,6 +10,10 @@ export default class OrderStore extends BaseStore {
     return this.apiPost('/order/checkout/add', { productType, productId, service, param, number });
   }
 
+  changeCheckout(checkoutId, number) {
+    return this.apiDelete('/order/checkout/number', { checkoutId, number });
+  }
+
   removeCheckout(checkoutId) {
     return this.apiDelete('/order/checkout/delete', { checkoutId });
   }
@@ -23,15 +27,15 @@ export default class OrderStore extends BaseStore {
   }
 
   wechatQr(orderId) {
-    return this.apiPost('/order/wechat/qr', { orderId });
+    return this.apiPost('/order/pay/wechat/qr', { orderId });
   }
 
   wechatJs(orderId) {
-    return this.apiPost('/order/wechat/js', { orderId });
+    return this.apiPost('/order/pay/wechat/js', { orderId });
   }
 
   alipayQr(orderId) {
-    return this.apiPost('/order/alipay/qr', { orderId });
+    return this.apiPost('/order/pay/alipay/qr', { orderId });
   }
 
   query(orderId) {

+ 1 - 1
front/project/www/app.less

@@ -438,7 +438,7 @@ body,
     content: '';
     position: absolute;
     left: -10px;
-    top: 10px;
+    top: 5px;
     width: 5px;
     height: 5px;
     border-radius: 50%;

+ 17 - 2
front/project/www/components/Login/index.js

@@ -5,8 +5,10 @@ import Assets from '@src/components/Assets';
 import { asyncSMessage } from '@src/services/AsyncTools';
 import { Icon as GIcon } from '../Icon';
 import { Button as GButton } from '../Button';
+import RadioItem from '../RadioItem';
 import { User } from '../../stores/user';
 import { Common } from '../../stores/common';
+import { Main } from '../../stores/main';
 import { MobileArea, WechatPcAppId, PcUrl } from '../../../Constant';
 
 const LOGIN_PHONE = 'LOGIN_PHONE';
@@ -34,6 +36,10 @@ export default class Login extends Component {
       },
       false,
     );
+    Main.getContract('register')
+      .then(result => {
+        this.setState({ contract: result });
+      });
   }
 
   close() {
@@ -193,7 +199,7 @@ export default class Login extends Component {
   }
 
   renderLoginPhone() {
-    const { needEmail } = this.state;
+    const { needEmail, contract = {} } = this.state;
     return (
       <div className="body">
         <div className="title">手机号登录</div>
@@ -236,6 +242,10 @@ export default class Login extends Component {
             }}
           />
         )}
+        {needEmail && (<div>
+          <RadioItem checked theme="white" className="m-r-5" />
+          我已经阅读并同意 <a href={`/contract/${contract.key}`} target="_blank">{contract.title}</a> 与 <a href={`/contract/${contract.key}`} target="_blank">隐私政策</a>.
+        </div>)}
         <Button
           type="primary"
           size="large"
@@ -288,7 +298,7 @@ export default class Login extends Component {
   }
 
   renderBindPhone() {
-    const { needEmail } = this.state;
+    const { needEmail, contract = {} } = this.state;
     return (
       <div className="body">
         <div className="title">绑定手机号</div>
@@ -331,9 +341,14 @@ export default class Login extends Component {
             }}
           />
         )}
+        {needEmail && (<div>
+          <RadioItem checked theme="white" className="m-r-5" />
+          我已经阅读并同意 <a href={`/contract/${contract.key}`} target="_blank">{contract.title}</a> 与 <a href={`/contract/${contract.key}`} target="_blank">隐私政策</a>.
+        </div>)}
         <Button
           type="primary"
           size="large"
+          disabled={this.state.validError || this.state.mobileError}
           block
           onClick={() => {
             this.bind();

+ 50 - 5
front/project/www/components/VipRenew/index.js

@@ -27,12 +27,57 @@ export default class extends Component {
   }
 
   select(key) {
-    Order.speedPay().then(result => {
+    Order.speedPay({ productType: 'service', service: 'vip', param: key }).then(result => {
       this.setState({ order: result });
+      this.changePay('alipay');
     });
     this.setState({ select: key });
   }
 
+  changePay(key) {
+    const { order } = this.state;
+    if (!order) return;
+    this.setState({ info: {}, pay: key });
+    let handler = null;
+    switch (key) {
+      case 'wechatpay':
+        handler = Order.wechatQr(order.id)
+          .then((result) => {
+            this.setState({ info: result });
+          });
+        break;
+      case 'alipay':
+      default:
+        handler = Order.alipayQr(order.id)
+          .then((result) => {
+            this.setState({ info: result });
+          });
+    }
+    handler.then(() => {
+      this.queryPay();
+    });
+  }
+
+  queryPay() {
+    const { order, show } = this.state;
+    if (this.time) {
+      clearTimeout(this.time);
+    }
+    this.time = setTimeout(() => {
+      Order.query(order.id)
+        .then(result => {
+          if (result) {
+            // 支付成功
+            this.setState();
+          } else if (show) {
+            this.queryPay();
+          } else {
+            this.setState({ select: null, pay: null, order: null, info: null });
+          }
+        });
+    }, 1000);
+  }
+
   render() {
     const { show, onClose } = this.props;
     const { tab } = this.state;
@@ -66,7 +111,7 @@ export default class extends Component {
             onChange={key => this.select(key)}
           />
         </div>
-        {order && <div className="pay-layout">
+        <div className="pay-layout">
           <Tabs
             border
             size="small"
@@ -74,14 +119,14 @@ export default class extends Component {
             width={80}
             tabs={[{ key: 'alipay', title: '支付宝' }, { key: 'wechatpay', title: '微信' }]}
             render={item => <Assets name={item.key} />}
-            onChange={key => this.setState({ pay: key })}
+            onChange={key => this.changePay(key)}
           />
           <div className="qrcode">
             <Assets name="qrcode" />
           </div>
           <div className="t">请使用手机微信或支付宝扫码付款</div>
-          <div className="t">支付金额: ¥ {order.money}</div>
-        </div>}
+          {order && <div className="t">支付金额: ¥ {order.money}</div>}
+        </div>
       </div>
     );
   }

+ 15 - 5
front/project/www/routes/my/collect/page.js

@@ -238,7 +238,7 @@ export default class extends Page {
           this.setState({ showVip: true });
           return;
         }
-        if (selectList.length < 0) {
+        if (selectList.length < 10) {
           this.setState({ showWarn: true, warn: { title: '组卷练习', content: '不可小于10题,请重新选择' } });
           return;
         }
@@ -257,8 +257,8 @@ export default class extends Page {
           this.setState({ showVip: true });
           return;
         }
-        if (selectList.length < 0) {
-          this.setState({ showWarn: true, warn: { title: '导出', content: '不可小于10题,请重新选择' } });
+        if (selectList.length < 1) {
+          this.setState({ showWarn: true, warn: { title: '导出', content: '不可小于1题,请重新选择' } });
           return;
         }
         if (selectList.length > 100) {
@@ -539,7 +539,12 @@ export default class extends Page {
             return (
               <div className="d-i-b m-b-5" style={{ width: 135 }}>
                 <Checkbox checked={exportInfo.info ? exportInfo.info.indexOf(item.key) >= 0 : false} className="m-r-5" onChange={() => {
-                  exportInfo.info.push(item.key);
+                  const index = exportInfo.info.indexOf(item.key);
+                  if (index >= 0) {
+                    exportInfo.info.splice(index, 1);
+                  } else {
+                    exportInfo.info.push(item.key);
+                  }
                   this.setState({ exportInfo });
                 }} />
                 {item.title}
@@ -559,7 +564,12 @@ export default class extends Page {
               return (
                 <div className="d-i-b m-b-2" style={{ width: 135 }}>
                   <Checkbox checked={exportInfo.info ? exportInfo.info.indexOf(item.key) >= 0 : false} className="m-r-5" onChange={() => {
-                    exportInfo.info.push(item.key);
+                    const index = exportInfo.info.indexOf(item.key);
+                    if (index >= 0) {
+                      exportInfo.info.splice(index, 1);
+                    } else {
+                      exportInfo.info.push(item.key);
+                    }
                     this.setState({ exportInfo });
                   }} />
                   {item.title}

+ 3 - 3
front/project/www/routes/my/error/page.js

@@ -225,7 +225,7 @@ export default class extends Page {
           this.setState({ showVip: true });
           return;
         }
-        if (selectList.length < 0) {
+        if (selectList.length < 10) {
           this.setState({ showWarn: true, warn: { title: '组卷练习', content: '不可小于10题,请重新选择' } });
           return;
         }
@@ -244,8 +244,8 @@ export default class extends Page {
           this.setState({ showVip: true });
           return;
         }
-        if (selectList.length < 0) {
-          this.setState({ showWarn: true, warn: { title: '导出', content: '不可小于10题,请重新选择' } });
+        if (selectList.length < 1) {
+          this.setState({ showWarn: true, warn: { title: '导出', content: '不可小于1题,请重新选择' } });
           return;
         }
         if (selectList.length > 100) {

+ 6 - 3
front/project/www/routes/my/message/page.js

@@ -17,17 +17,19 @@ const columns = [
   {
     title: '消息',
     key: 'title',
+
     render: (text, row) => {
       return <div>
-        {row.isRead ? <span className='dot'>{text}</span> : text}
+        {!row.isRead ? <span className='dot'>{text}</span> : text}
         {row.content && <div className=''>{row.content}{row.link && <a className='m-l-5' href={row.link} target="_blank">查看详情</a>}</div>}
       </div>;
     },
   },
-  { title: '类型', key: 'type' },
+  { title: '类型', key: 'type', width: 120 },
   {
     title: '发送时间',
     key: 'date',
+    width: 180,
     render: (text) => {
       return <div className="sub">
         <div className="t-2 t-s-12">{text.split(' ')[0]}</div>
@@ -68,7 +70,7 @@ export default class extends Page {
     My.message(Object.assign({ read: data.tab === 'unread' ? 0 : null }, this.state.search)).then(result => {
       result.list = result.list.map(row => {
         row.type = MessageTypeMap[row.type];
-        row.date = formatDate(row.createTime, 'YYYY-MM-DDHH:mm:ss');
+        row.date = formatDate(row.createTime, 'YYYY-MM-DD HH:mm:ss');
 
         return row;
       });
@@ -93,6 +95,7 @@ export default class extends Page {
   readAllMessage() {
     My.readAllMessage().then(() => {
       asyncSMessage('操作成功');
+      this.refresh();
     });
   }
 

+ 2 - 2
front/project/www/routes/my/note/page.js

@@ -245,8 +245,8 @@ export default class extends Page {
           this.setState({ showVip: true });
           return;
         }
-        if (selectList.length < 0) {
-          this.setState({ showWarn: true, warn: { title: '导出', content: '不可小于10题,请重新选择' } });
+        if (selectList.length < 1) {
+          this.setState({ showWarn: true, warn: { title: '导出', content: '不可小于1题,请重新选择' } });
           return;
         }
         if (selectList.length > 100) {

+ 9 - 7
front/project/www/routes/my/order/page.js

@@ -52,10 +52,12 @@ export default class extends Page {
         key: 'title',
         render: (text, record) => {
           const actionList = [];
-          // if (record.canInvoice && !record.hasInvoice) {
-          actionList.push({ key: 'invoice', label: '开发票' });
-          // }
-          actionList.push({ key: 'detail', label: '订单详情' });
+          if (record.canInvoice && !record.hasInvoice) {
+            actionList.push({ key: 'invoice', label: '开发票' });
+          }
+          if (record.canInvoice) {
+            actionList.push({ key: 'detail', label: '订单详情' });
+          }
           const onAction = value => {
             const { key } = value;
             switch (key) {
@@ -79,9 +81,9 @@ export default class extends Page {
                   {formatTitle(record.checkouts[0])}
                   <br />等{record.checkouts.length}个商品
                 </div>
-                <More menu={actionList} onClick={onAction}>
+                {actionList.length > 0 && <More menu={actionList} onClick={onAction}>
                   <IconButton type="more" />
-                </More>
+                </More>}
               </div>,
             );
           } else {
@@ -89,7 +91,7 @@ export default class extends Page {
               return (
                 <div className="flex-layout m-b-5">
                   <div className="flex-block">{formatTitle(row)}</div>
-                  {index === 0 && (
+                  {index === 0 && actionList.length > 0 && (
                     <More menu={actionList} onClick={onAction}>
                       <IconButton type="more" />
                     </More>

+ 40 - 5
front/project/www/routes/my/tools/page.js

@@ -13,6 +13,9 @@ import More from '../../../components/More';
 import Button from '../../../components/Button';
 import Switch from '../../../components/Switch';
 import TotalSort from '../../../components/TotalSort';
+import { RealAuth } from '../../../components/OtherModal';
+import Examination from '../../../components/Examination';
+import VipRenew from '../../../components/VipRenew';
 import Modal from '../../../components/Modal';
 import UserTable from '../../../components/UserTable';
 import UserPagination from '../../../components/UserPagination';
@@ -157,6 +160,9 @@ export default class extends Page {
     Main.getService('vip').then(result => {
       this.setState({ service: result });
     });
+    My.getVipInfo().then(result => {
+      this.setState({ data: result });
+    });
   }
 
   recordList({ page, size, service, isUse, isExpire }) {
@@ -339,7 +345,11 @@ export default class extends Page {
       updateTotal,
       maxHeight,
       updateData = {},
+      showExamination,
+      showReal,
+      showVip,
     } = this.state;
+    const { info } = this.props.user;
     return (
       <div className="table-layout">
         <Tabs
@@ -517,6 +527,21 @@ export default class extends Page {
           />
           <div className="b-b m-t-2" />
         </Modal>
+        <Examination
+          show={showExamination}
+          data={info}
+          onConfirm={() => this.setState({ showExamination: false })}
+          onCancel={() => this.setState({ showExamination: false })}
+          onClose={() => this.setState({ showExamination: false })}
+        />
+        <RealAuth show={showReal} data={info} onConfirm={() => this.setState({ showReal: false })} />
+        <VipRenew
+          show={showVip}
+          data={info}
+          onReal={() => this.setState({ showVip: false, showReal: true })}
+          onPrepare={() => this.setState({ showVip: false, showExamination: true })}
+          onClose={() => this.setState({ showVip: false })}
+        />
       </div>
     );
   }
@@ -680,7 +705,9 @@ export default class extends Page {
           <div className="tip-layout">
             <div className="t1">还未购买本月机经</div>
             <div className="desc">¥ {service && service.package && service.package[0].price}</div>
-            <Button radius size="lager" width={150}>
+            <Button radius size="lager" width={150} onClick={() => {
+              this.buyTextbook();
+            }}>
               立即购买
             </Button>
           </div>
@@ -733,7 +760,9 @@ export default class extends Page {
           <div className="tip-layout">
             <div className="t1">未购买</div>
             <div className="desc">¥ {service && service.package && service.package[0].price}</div>
-            <Button radius size="lager" width={150}>
+            <Button radius size="lager" width={150} onClick={() => {
+              this.buyExamination();
+            }}>
               立即购买
             </Button>
           </div>
@@ -782,7 +811,9 @@ export default class extends Page {
         {!data.hasService && !data.unUseRecord && !data.expireTime && (
           <div className="tip-layout">
             <div className="t2">未购买</div>
-            <Button radius size="lager" width={150}>
+            <Button radius size="lager" width={150} onClick={() => {
+              this.setState({ showVip: true });
+            }}>
               立即购买
             </Button>
           </div>
@@ -791,7 +822,9 @@ export default class extends Page {
           <div className="tip-layout">
             <div className="t1">使用中</div>
             <div className="desc">{formatDate(data.expireTime, 'YYYY-MM-DD')} 到期</div>
-            <Button radius size="lager" width={150}>
+            <Button radius size="lager" width={150} onClick={() => {
+              this.setState({ showVip: true });
+            }}>
               续费
             </Button>
           </div>
@@ -802,7 +835,9 @@ export default class extends Page {
             <div className="desc">
               {formatDate(data.startTime, 'YYYY-MM-DD')} ~ {formatDate(data.expireTime, 'YYYY-MM-DD')}
             </div>
-            <Button radius size="lager" width={150}>
+            <Button radius size="lager" width={150} onClick={() => {
+              this.setState({ showVip: true });
+            }}>
               立即购买
             </Button>
           </div>

+ 10 - 0
front/project/www/routes/page/contract/index.js

@@ -0,0 +1,10 @@
+export default {
+  path: '/contract/:key',
+  key: 'contract',
+  title: '协议',
+  needLogin: false,
+  repeat: true,
+  component() {
+    return import('./page');
+  },
+};

+ 3 - 0
front/project/www/routes/page/contract/index.less

@@ -0,0 +1,3 @@
+@charset "utf-8";
+
+#contract {}

+ 30 - 0
front/project/www/routes/page/contract/page.js

@@ -0,0 +1,30 @@
+import React from 'react';
+import './index.less';
+import Page from '@src/containers/Page';
+import { asyncSMessage } from '@src/services/AsyncTools';
+import { Main } from '../../../stores/main';
+
+export default class extends Page {
+  constructor(props) {
+    super(props);
+    this.state = { courseIndex: 0 };
+  }
+
+  initData() {
+    const { key } = this.params;
+    Main.getContract(key)
+      .then(result => {
+        this.setState({ data: result });
+      })
+      .catch(e => {
+        asyncSMessage(e.message, 'error');
+        linkTo('empty');
+      });
+  }
+
+
+  renderView() {
+    const { data = {} } = this.state;
+    return <div dangerouslySetInnerHTML={{ __html: data.content || '' }} />;
+  }
+}

+ 2 - 1
front/project/www/routes/page/index.js

@@ -3,5 +3,6 @@ import login from './login';
 import order from './order';
 import cart from './cart';
 import demo from './demo';
+import contract from './contract';
 
-export default [home, login, order, cart, demo];
+export default [home, login, order, cart, demo, contract];

+ 0 - 10
front/project/www/routes/page/order/index.js

@@ -1,10 +0,0 @@
-export default {
-  path: '/order/:id',
-  key: 'order',
-  title: '订单',
-  needLogin: false,
-  tab: 'main',
-  component() {
-    return import('./page');
-  },
-};

+ 4 - 0
front/project/www/stores/my.js

@@ -34,6 +34,10 @@ export default class MyStore extends BaseStore {
     return this.apiPost('/my/invite/email', { emails });
   }
 
+  getVipInfo() {
+    return this.apiGet('/my/vip/info');
+  }
+
   /**
    * 用户站内信
    * @param {*} page

+ 7 - 3
front/project/www/stores/order.js

@@ -10,6 +10,10 @@ export default class OrderStore extends BaseStore {
     return this.apiPost('/order/checkout/add', { productType, productId, service, param, number });
   }
 
+  changeCheckout(checkoutId, number) {
+    return this.apiDelete('/order/checkout/number', { checkoutId, number });
+  }
+
   removeCheckout(checkoutId) {
     return this.apiDelete('/order/checkout/delete', { checkoutId });
   }
@@ -23,15 +27,15 @@ export default class OrderStore extends BaseStore {
   }
 
   wechatQr(orderId) {
-    return this.apiPost('/order/wechat/qr', { orderId });
+    return this.apiPost('/order/pay/wechat/qr', { orderId });
   }
 
   wechatJs(orderId) {
-    return this.apiPost('/order/wechat/js', { orderId });
+    return this.apiPost('/order/pay/wechat/js', { orderId });
   }
 
   alipayQr(orderId) {
-    return this.apiPost('/order/alipay/qr', { orderId });
+    return this.apiPost('/order/pay/alipay/qr', { orderId });
   }
 
   query(orderId) {

+ 17 - 7
server/gateway-api/src/main/java/com/qxgmat/controller/api/OrderController.java

@@ -14,6 +14,7 @@ import com.qxgmat.data.constants.enums.SettingKey;
 import com.qxgmat.data.constants.enums.module.ProductType;
 import com.qxgmat.data.constants.enums.status.DirectionStatus;
 import com.qxgmat.data.constants.enums.trade.PayChannel;
+import com.qxgmat.data.constants.enums.trade.PayMethod;
 import com.qxgmat.data.constants.enums.trade.PayModule;
 import com.qxgmat.data.dao.entity.*;
 import com.qxgmat.dto.extend.CourseDataExtendDto;
@@ -101,6 +102,15 @@ public class OrderController {
         return ResponseHelp.success(number);
     }
 
+    @RequestMapping(value = "/checkout/number", method = RequestMethod.POST)
+    @ApiOperation(value = "修改购物车", notes = "修改购物车", httpMethod = "POST")
+    public Response<Integer> changeCheckout(@RequestBody @Validated RecordChangeDto dto, HttpServletRequest request)  {
+        User user = (User) shiroHelp.getLoginUser();
+        UserOrderCheckout checkout = Transform.dtoToEntity(dto);
+        int number = orderFlowService.changeCheckout(user.getId(), checkout);
+        return ResponseHelp.success(number);
+    }
+
     @RequestMapping(value = "/checkout/delete", method = RequestMethod.DELETE)
     @ApiOperation(value = "删除购物车", notes = "删除购物车", httpMethod = "DELETE")
     public Response<Integer> deleteCheckout(@RequestParam int checkoutId, HttpServletRequest request) throws Exception {
@@ -124,11 +134,11 @@ public class OrderController {
         User user = (User) shiroHelp.getLoginUser();
         UserOrderCheckout checkout = Transform.dtoToEntity(dto);
         UserOrder order = orderFlowService.makeOrderWithSpeed(user.getId(), checkout);
-
+        orderFlowService.payed(order.getId(), user.getId(), 123123123L, new Date(), PayMethod.WECHAT, "ceshi");
         return ResponseHelp.success(detail(user.getId(), order, null));
     }
 
-    @RequestMapping(value = "/pay/wechat_qr", method = RequestMethod.POST)
+    @RequestMapping(value = "/pay/wechat/qr", method = RequestMethod.POST)
     @ApiOperation(value = "通过微信二维码支付", notes = "通过微信二维码支付", httpMethod = "POST")
     public Response<PayResponseData> wechatQr(@RequestBody @Validated PayOrderDto dto, HttpServletRequest request) throws Exception {
         User user = (User) shiroHelp.getLoginUser();
@@ -139,11 +149,11 @@ public class OrderController {
         if (!order.getUserId().equals(user.getId())){
             throw new ParameterException("订单不存在");
         }
-        PayResponseData data = tradeService.pay(user.getId(), "", "", PayModule.ORDER, order.getId(), BigDecimal.valueOf(0), PayChannel.WECHAT_QR, request);
+        PayResponseData data = tradeService.pay(user.getId(), "千行GMAT", "千行服务购买", PayModule.ORDER, order.getId(), order.getMoney(), PayChannel.WECHAT_QR, request);
         return ResponseHelp.success(data);
     }
 
-    @RequestMapping(value = "/pay/wechat_js", method = RequestMethod.POST)
+    @RequestMapping(value = "/pay/wechat/js", method = RequestMethod.POST)
     @ApiOperation(value = "通过微信内支付", notes = "通过微信内支付", httpMethod = "POST")
     public Response<PayResponseData> wechatJs(@RequestBody @Validated PayOrderDto dto, HttpServletRequest request) throws Exception {
         User user = (User) shiroHelp.getLoginUser();
@@ -154,11 +164,11 @@ public class OrderController {
         if (!order.getUserId().equals(user.getId())){
             throw new ParameterException("订单不存在");
         }
-        PayResponseData data = tradeService.pay(user.getId(), "", "", PayModule.ORDER, order.getId(), BigDecimal.valueOf(0), PayChannel.WECHAT_JS, request);
+        PayResponseData data = tradeService.pay(user.getId(), "千行GMAT", "千行服务购买", PayModule.ORDER, order.getId(), order.getMoney(), PayChannel.WECHAT_JS, request);
         return ResponseHelp.success(data);
     }
 
-    @RequestMapping(value = "/pay/alipay_qr", method = RequestMethod.POST)
+    @RequestMapping(value = "/pay/alipay/qr", method = RequestMethod.POST)
     @ApiOperation(value = "通过支付宝二维码支付", notes = "通过支付宝二维码支付", httpMethod = "POST")
     public Response<PayResponseData> alipayQr(@RequestBody @Validated PayOrderDto dto, HttpServletRequest request) throws Exception {
         User user = (User) shiroHelp.getLoginUser();
@@ -169,7 +179,7 @@ public class OrderController {
         if (!order.getUserId().equals(user.getId())){
             throw new ParameterException("订单不存在");
         }
-        PayResponseData data = tradeService.pay(user.getId(), "", "", PayModule.ORDER, order.getId(), BigDecimal.valueOf(0), PayChannel.ALIPAY_QR, request);
+        PayResponseData data = tradeService.pay(user.getId(), "千行GMAT", "千行服务购买", PayModule.ORDER, order.getId(), order.getMoney(), PayChannel.ALIPAY_QR, request);
         return ResponseHelp.success(data);
     }
 

+ 34 - 12
server/gateway-api/src/main/java/com/qxgmat/dto/extend/UserOrderRecordExtendDto.java

@@ -1,20 +1,26 @@
 package com.qxgmat.dto.extend;
 
 
+import java.math.BigDecimal;
+
 public class UserOrderRecordExtendDto {
+    private Integer id;
+
+    private Integer parentId;
+
     private String productType;
 
-    private String productId;
+    private Integer productId;
 
     private String service;
 
     private String param;
 
-    private String number;
+    private Integer number;
 
-    private String originMoney;
+    private BigDecimal originMoney;
 
-    private String money;
+    private BigDecimal money;
 
     private Integer expireDays;
 
@@ -28,11 +34,11 @@ public class UserOrderRecordExtendDto {
         this.productType = productType;
     }
 
-    public String getProductId() {
+    public Integer getProductId() {
         return productId;
     }
 
-    public void setProductId(String productId) {
+    public void setProductId(Integer productId) {
         this.productId = productId;
     }
 
@@ -52,27 +58,27 @@ public class UserOrderRecordExtendDto {
         this.param = param;
     }
 
-    public String getNumber() {
+    public Integer getNumber() {
         return number;
     }
 
-    public void setNumber(String number) {
+    public void setNumber(Integer number) {
         this.number = number;
     }
 
-    public String getOriginMoney() {
+    public BigDecimal getOriginMoney() {
         return originMoney;
     }
 
-    public void setOriginMoney(String originMoney) {
+    public void setOriginMoney(BigDecimal originMoney) {
         this.originMoney = originMoney;
     }
 
-    public String getMoney() {
+    public BigDecimal getMoney() {
         return money;
     }
 
-    public void setMoney(String money) {
+    public void setMoney(BigDecimal money) {
         this.money = money;
     }
 
@@ -91,4 +97,20 @@ public class UserOrderRecordExtendDto {
     public void setUseExpireDays(Integer useExpireDays) {
         this.useExpireDays = useExpireDays;
     }
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getParentId() {
+        return parentId;
+    }
+
+    public void setParentId(Integer parentId) {
+        this.parentId = parentId;
+    }
 }

+ 27 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/request/RecordChangeDto.java

@@ -0,0 +1,27 @@
+package com.qxgmat.dto.request;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.UserOrderCheckout;
+
+@Dto(entity = UserOrderCheckout.class)
+public class RecordChangeDto {
+    private Integer id;
+
+    private Integer number;
+
+    public Integer getNumber() {
+        return number;
+    }
+
+    public void setNumber(Integer number) {
+        this.number = number;
+    }
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+}

+ 10 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/response/UserOrderDetailDto.java

@@ -19,6 +19,8 @@ public class UserOrderDetailDto {
 
     private String transactionNo;
 
+    private String[] productTypes;
+
     private BigDecimal money;
 
     private BigDecimal originMoney;
@@ -162,4 +164,12 @@ public class UserOrderDetailDto {
     public void setInvoiceMoney(BigDecimal invoiceMoney) {
         this.invoiceMoney = invoiceMoney;
     }
+
+    public String[] getProductTypes() {
+        return productTypes;
+    }
+
+    public void setProductTypes(String[] productTypes) {
+        this.productTypes = productTypes;
+    }
 }

+ 1 - 0
server/gateway-api/src/main/java/com/qxgmat/help/MailHelp.java

@@ -38,6 +38,7 @@ public class MailHelp {
     }
 
     public boolean sendBaseMail(String email, String subject, String body){
+        if (body==null || body.isEmpty()) return false;
         SendCloudMail.Response response = mail.sendMail(email, subject, body, from, fromName, null);
         return response.getResult();
     }

+ 10 - 0
server/gateway-api/src/main/java/com/qxgmat/service/annotation/ChangeCheckout.java

@@ -0,0 +1,10 @@
+package com.qxgmat.service.annotation;
+
+import com.qxgmat.data.dao.entity.UserOrderCheckout;
+
+import java.util.List;
+
+
+public interface ChangeCheckout {
+    UserOrderCheckout callback(UserOrderCheckout checkout);
+}

+ 81 - 12
server/gateway-api/src/main/java/com/qxgmat/service/extend/OrderFlowService.java

@@ -85,6 +85,8 @@ public class OrderFlowService {
 
     private Map<ProductType, InitCheckout> initCheckoutCallback = new HashMap<>();
 
+    private Map<ProductType, ChangeCheckout> changeCheckoutCallback = new HashMap<>();
+
     private Map<ProductType, RemoveCheckout> removeCheckoutCallback = new HashMap<>();
 
     private Map<ProductType, InitOrder> initOrderCallback = new HashMap<>();
@@ -265,6 +267,25 @@ public class OrderFlowService {
             return checkout;
         }));
 
+        changeCheckoutCallback.put(ProductType.COURSE, (checkout->{
+            Course mainCourse = courseService.get(checkout.getProductId());
+
+            // 判断是否是1v1课程
+            if (mainCourse.getCourseModule().equals(CourseModule.VS.key)){
+                checkout.setOriginMoney(mainCourse.getPrice().multiply(BigDecimal.valueOf(checkout.getNumber())));
+                int percent = toolsService.computeVsMoney(checkout);
+                checkout.setMoney(checkout.getOriginMoney().multiply(BigDecimal.valueOf(percent)).divide(BigDecimal.valueOf(100), BigDecimal.ROUND_HALF_UP));
+
+                // 设定有效期
+                checkout.setExpireDays(mainCourse.getExpireDays());
+                checkout.setUseExpireDays(courseExtendService.computeExpire(checkout.getNumber(), mainCourse));
+                return checkout;
+            }else{
+                // 无法修改
+                throw new ParameterException("记录无法修改");
+            }
+        }));
+
         removeCheckoutCallback.put(ProductType.COURSE, ((checkout, userOrderCheckoutList)->{
             // 子项目不允许参数,则无需处理
         }));
@@ -510,6 +531,7 @@ public class OrderFlowService {
             if(record.getService().equals(ServiceKey.VIP.key)){
                 // VIP直接开通
                 record = useRecordCallback.get(ProductType.SERVICE).callback(record);
+                record.setProductType(ProductType.SERVICE.key);
             }else{
                 ServiceKey serviceKey = ServiceKey.ValueOf(record.getService());
                 Date startTime = new Date();
@@ -694,7 +716,7 @@ public class OrderFlowService {
         UserOrder userOrder = userOrderService.get(orderId);
         if (userOrder == null) return false;
         if (userOrder.getPayStatus() > 0) return false;
-        userOrder = userOrderService.edit(UserOrder.builder()
+        userOrderService.edit(UserOrder.builder()
                 .id(userOrder.getId())
                 .payId(payId)
                 .payMethod(payMethod.key)
@@ -702,7 +724,11 @@ public class OrderFlowService {
                 .transactionNo(transactionNo)
                 .payStatus(1)
                 .build());
-
+        userOrder.setPayId(payId);
+        userOrder.setPayMethod(payMethod.key);
+        userOrder.setPayTime(payTime);
+        userOrder.setTransactionNo(transactionNo);
+        userOrder.setPayStatus(1);
         // 转换购物车记录为购买记录
         List<UserOrderCheckout> checkoutList = userOrderCheckoutService.allByUser(userId, orderId);
         List<UserOrderCheckout> pList = checkoutList.stream().filter((checkout)-> checkout.getParentId() == 0).collect(Collectors.toList());
@@ -775,9 +801,9 @@ public class OrderFlowService {
         List<UserOrderCheckout> list = userOrderCheckoutService.allByUser(userId, 0);
         InitCheckout callback = initCheckoutCallback.get(ProductType.ValueOf(checkout.getProductType()));
         checkout = callback.callback(checkout, list);
-        if (checkout == null){
+        if (checkout == null) {
             // 无需操作
-        }else if(checkout.getId() > 0){
+        }else if(checkout.getId() != null && checkout.getId() > 0){
             userOrderCheckoutService.edit(checkout);
         }else{
             userOrderCheckoutService.add(checkout);
@@ -788,6 +814,31 @@ public class OrderFlowService {
     }
 
     /**
+     * 修改购物车
+     * @param userId
+     * @param checkout
+     * @return
+     */
+    @Transactional
+    public int changeCheckout(Integer userId, UserOrderCheckout checkout){
+        ChangeCheckout callback = changeCheckoutCallback.get(ProductType.ValueOf(checkout.getProductType()));
+        if (callback == null){
+            throw new ParameterException("无法修改记录");
+        }
+        checkout = callback.callback(checkout);
+        if (checkout == null){
+            // 无需操作
+        }else{
+            userOrderCheckoutService.edit(checkout);
+        }
+
+        // 计算购物车数量
+        return userOrderCheckoutService.allByUser(userId, 0).size();
+    }
+
+
+
+    /**
      * 删除购物车
      * @param checkoutId
      * @param userId
@@ -848,23 +899,26 @@ public class OrderFlowService {
 
         // 课程: 并包含套餐
         List<UserOrderCheckout> courseCheckout = list.stream().filter((checkout)-> checkout.getProductType().equals(ProductType.COURSE.key) || checkout.getProductType().equals(ProductType.COURSE_PACKAGE.key)).collect(Collectors.toList());
-        callback = initOrderCallback.get(ProductType.COURSE);
-        callback.callback(order, courseCheckout);
+
         if(courseCheckout.size() > 0){
+            callback = initOrderCallback.get(ProductType.COURSE);
+            callback.callback(order, courseCheckout);
             productTypes.add(ProductType.COURSE.key);
         }
         // 资料
         List<UserOrderCheckout> dataCheckout = list.stream().filter((checkout)-> checkout.getProductType().equals(ProductType.DATA.key)).collect(Collectors.toList());
-        callback = initOrderCallback.get(ProductType.DATA);
-        callback.callback(order, courseCheckout);
+
         if (dataCheckout.size() > 0){
+            callback = initOrderCallback.get(ProductType.DATA);
+            callback.callback(order, dataCheckout);
             productTypes.add(ProductType.DATA.key);
         }
         // 服务
         List<UserOrderCheckout> serviceCheckout = list.stream().filter((checkout)-> checkout.getProductType().equals(ProductType.SERVICE.key)).collect(Collectors.toList());
-        callback = initOrderCallback.get(ProductType.SERVICE);
-        callback.callback(order, courseCheckout);
+
         if(serviceCheckout.size() > 0){
+            callback = initOrderCallback.get(ProductType.SERVICE);
+            callback.callback(order, serviceCheckout);
             productTypes.add(ProductType.SERVICE.key);
         }
 
@@ -903,12 +957,24 @@ public class OrderFlowService {
      */
     @Transactional
     public UserOrder makeOrderWithSpeed(Integer userId, UserOrderCheckout checkout){
-        List<UserOrderCheckout> list = new ArrayList<UserOrderCheckout>(){{add(checkout);}};
+        List<UserOrderCheckout> list = new ArrayList<>();
+        checkout.setUserId(userId);
+        InitCheckout callback = initCheckoutCallback.get(ProductType.ValueOf(checkout.getProductType()));
+        checkout = callback.callback(checkout, list);
+        if (checkout == null){
+            throw new ParameterException("创建失败");
+        }
+        list.add(checkout);
+
         UserOrder order = preOrderWithCheckout(userId, list);
         order.setIsSpeed(1);
         order = userOrderService.add(order);
         checkout.setOrderId(order.getId());
-        userOrderCheckoutService.add(checkout);
+        if(checkout.getId() != null && checkout.getId() > 0){
+            userOrderCheckoutService.edit(checkout);
+        }else{
+            userOrderCheckoutService.add(checkout);
+        }
         return order;
     }
 
@@ -968,6 +1034,7 @@ public class OrderFlowService {
     public void giveInvite(Integer userId){
         UserOrderRecord record = UserOrderRecord.builder()
                 .userId(userId)
+                .productType(ProductType.SERVICE.key)
                 .service(ServiceKey.VIP.key)
                 .param(ServiceVipKey.DAY7.key)
                 .source(RecordSource.INVITE.key)
@@ -986,6 +1053,7 @@ public class OrderFlowService {
     public void givePrepare(Integer userId){
         UserOrderRecord record = UserOrderRecord.builder()
                 .userId(userId)
+                .productType(ProductType.SERVICE.key)
                 .service(ServiceKey.VIP.key)
                 .param(ServiceVipKey.DAY7.key)
                 .source(RecordSource.PREPARE.key)
@@ -1004,6 +1072,7 @@ public class OrderFlowService {
     public void giveReal(Integer userId){
         UserOrderRecord record = UserOrderRecord.builder()
                 .userId(userId)
+                .productType(ProductType.SERVICE.key)
                 .service(ServiceKey.VIP.key)
                 .param(ServiceVipKey.MONTH3.key)
                 .source(RecordSource.REAL.key)

+ 8 - 1
server/gateway-api/src/main/java/com/qxgmat/service/extend/TradeService.java

@@ -15,7 +15,9 @@ import com.qxgmat.data.constants.enums.trade.PayMethod;
 import com.qxgmat.data.constants.enums.trade.TradeStatus;
 import com.qxgmat.data.dao.PayMapper;
 import com.qxgmat.data.dao.entity.Pay;
+import com.qxgmat.data.dao.entity.User;
 import com.qxgmat.help.PayHelp;
+import com.qxgmat.service.UsersService;
 import com.qxgmat.service.inline.PayService;
 import com.qxgmat.util.annotation.Callback;
 import org.springframework.stereotype.Service;
@@ -40,6 +42,9 @@ public class TradeService extends AbstractService {
     @Resource
     private PayService payService;
 
+    @Resource
+    private UsersService usersService;
+
     private Map<PayModule, Callback> payCallback = new HashMap<>();
 
     public interface Source {
@@ -53,6 +58,7 @@ public class TradeService extends AbstractService {
         channelMap.put(PayChannel.ALIPAY_QR, Source.ALIPAY);
         channelMap.put(PayChannel.WECHAT_APP, Source.WECHAT);
         channelMap.put(PayChannel.WECHAT_QR, Source.WECHAT);
+        channelMap.put(PayChannel.WECHAT_JS, Source.WECHAT);
     }
 
     public void register(PayModule module, Callback callback){
@@ -243,6 +249,7 @@ public class TradeService extends AbstractService {
         BigDecimal money = pay.getMoney();
         String pid = pay.getPid();
         PayInfo info = null;
+        User user = usersService.get(pay.getUserId());
         switch(channel){
             case ALIPAY_APP:
                 info = payHelp.getAlipay().appTrade(payNo, pid, money, subject, body, notifyUrl, pay.getClientIp());
@@ -257,7 +264,7 @@ public class TradeService extends AbstractService {
                 info = payHelp.getWechatPay().preTrade(payNo, pid, money, subject, body, notifyUrl, pay.getClientIp());
                 break;
             case WECHAT_JS:
-                info = payHelp.getWechatPay().jsTrade(payNo, pid, money, subject, body, notifyUrl, pay.getClientIp());
+                info = payHelp.getWechatPay().jsTrade(payNo, pid, money, subject, body, notifyUrl, pay.getClientIp(), user.getWechatOpenidWechat());
                 break;
             case OFFLINE:
                 // 直接完成支付

+ 2 - 0
server/gateway-api/src/main/profile/dev/application-runtime.yml

@@ -103,6 +103,8 @@ third:
   redirectUrl: http://www.qianxing.com/gateway/oauth
 
 pay:
+  wechat:
+    appId: wx65c7d378b4184bcc
   notifyUrl: http://www.qianxing.com/gateway/pay
 
 video:

+ 2 - 0
server/gateway-api/src/main/profile/prod/application-runtime.yml

@@ -104,6 +104,8 @@ third:
   redirectUrl: http://www.duoshaojiaoyu.com/gateway/oauth
 
 pay:
+  wechat:
+    appId: wxbee75af2ece94ed7
   notifyUrl: http://www.duoshaojiaoyu.com/gateway/pay
 
 video:

+ 2 - 0
server/gateway-api/src/main/profile/test/application-runtime.yml

@@ -104,6 +104,8 @@ third:
   redirectUrl: http://test.duoshaojiaoyu.com/gateway/oauth
 
 pay:
+  wechat:
+    appId: wx65c7d378b4184bcc
   notifyUrl: http://test.duoshaojiaoyu.com/gateway/pay
 
 video:

文件差異過大導致無法顯示
+ 11 - 10
server/gateway-api/src/main/resources/application.yml


+ 5 - 4
server/tools/src/main/java/com/nuliji/tools/pay/WechatPay.java

@@ -120,7 +120,7 @@ public class WechatPay implements PaySource {
     }
 
     public PayInfo appTrade(String payNo, String pid, BigDecimal money, String subject, String body, String notifyUrl, String ip) throws Exception{
-        UnifiedOrderReqData unifiedOrderReqData = new UnifiedOrderReqData(body, "", payNo, (int)(money.floatValue() * 100), "", ip, "", "", "", notifyUrl, "APP");
+        UnifiedOrderReqData unifiedOrderReqData = new UnifiedOrderReqData(body, "", payNo, (int)(money.floatValue() * 100), "", ip, "", "", "", notifyUrl, "APP", null);
         //接受API返回
         String payServiceResponseString;
         UnifiedOrderService unifiedOrderService = new UnifiedOrderService();
@@ -148,7 +148,7 @@ public class WechatPay implements PaySource {
     }
 
     public PayInfo preTrade(String payNo, String pid, BigDecimal money, String subject, String body, String notifyUrl, String ip) throws Exception{
-        UnifiedOrderReqData unifiedOrderReqData = new UnifiedOrderReqData(body, "", payNo, (int)(money.floatValue() * 100), "", ip, "", "", "", notifyUrl, "NATIVE");
+        UnifiedOrderReqData unifiedOrderReqData = new UnifiedOrderReqData(body, "", payNo, (int)(money.floatValue() * 100), "", ip, "", "", "", notifyUrl, "NATIVE", null);
         //接受API返回
         String payServiceResponseString;
         UnifiedOrderService unifiedOrderService = new UnifiedOrderService();
@@ -174,11 +174,12 @@ public class WechatPay implements PaySource {
         return payInfo;
     }
 
-    public PayInfo jsTrade(String payNo, String pid, BigDecimal money, String subject, String body, String notifyUrl, String ip) throws Exception{
-        UnifiedOrderReqData unifiedOrderReqData = new UnifiedOrderReqData(body, "", payNo, (int)(money.floatValue() * 100), "", ip, "", "", "", notifyUrl, "JSAPI");
+    public PayInfo jsTrade(String payNo, String pid, BigDecimal money, String subject, String body, String notifyUrl, String ip, String openId) throws Exception{
+        UnifiedOrderReqData unifiedOrderReqData = new UnifiedOrderReqData(body, "", payNo, (int)(money.floatValue() * 100), "", ip, "", "", "", notifyUrl, "JSAPI", openId);
         //接受API返回
         String payServiceResponseString;
         UnifiedOrderService unifiedOrderService = new UnifiedOrderService();
+        logger.debug(JSON.toJSONString(unifiedOrderReqData));
         payServiceResponseString = unifiedOrderService.request(unifiedOrderReqData);
 
         //将从API返回的XML数据映射到Java对象

+ 1 - 1
server/tools/src/main/java/com/nuliji/tools/third/sendcloud/SendCloudMail.java

@@ -84,7 +84,7 @@ public class SendCloudMail {
             logger.info("SendCloud返回结果:" + result);
             JSONObject a = JSONObject.parseObject(result);
 
-            if (a.getInteger("status") == 0) {
+            if (!a.getBooleanValue("result")) {
                 throw new Exception("SendCloud接口错误: " + result);
             }
             Response response = new Response();

+ 12 - 2
server/tools/src/main/java/com/tencent/protocol/pay_protocol/UnifiedOrderReqData.java

@@ -35,6 +35,7 @@ public class UnifiedOrderReqData {
     private String goods_tag = "";
     private String notify_url = "";
     private String trade_type = "";
+    private String openid = "";
 
     /**
      * @param body 要支付的商品的描述信息,用户会在支付成功页面里看到这个信息
@@ -49,7 +50,7 @@ public class UnifiedOrderReqData {
      * @param notify_url 异步通知接口
      * @param trade_type 交易类型:APP
      */
-    public UnifiedOrderReqData(String body, String attach, String outTradeNo, int totalFee, String deviceInfo, String spBillCreateIP, String timeStart, String timeExpire, String goodsTag, String notify_url, String trade_type){
+    public UnifiedOrderReqData(String body, String attach, String outTradeNo, int totalFee, String deviceInfo, String spBillCreateIP, String timeStart, String timeExpire, String goodsTag, String notify_url, String trade_type, String openid){
 
         //微信分配的公众号ID(开通公众号之后可以获取到)
         setAppid(Configure.getAppid());
@@ -91,6 +92,8 @@ public class UnifiedOrderReqData {
 
         setTrade_type(trade_type);
 
+        setOpenid(openid);
+
         //根据API给的签名规则进行签名
         String sign = Signature.getSign(toMap());
         setSign(sign);//把签名数据设置到Sign这个属性中
@@ -217,6 +220,14 @@ public class UnifiedOrderReqData {
         this.trade_type = trade_type;
     }
 
+    public String getOpenid() {
+        return openid;
+    }
+
+    public void setOpenid(String openid) {
+        this.openid = openid;
+    }
+
     public Map<String,Object> toMap(){
         Map<String,Object> map = new HashMap<String, Object>();
         Field[] fields = this.getClass().getDeclaredFields();
@@ -235,5 +246,4 @@ public class UnifiedOrderReqData {
         }
         return map;
     }
-
 }