cy 3 lat temu
rodzic
commit
2e227cfbf3
14 zmienionych plików z 465 dodań i 9 usunięć
  1. 7 1
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/dao/UserWithdrawalDao.java
  2. 1 1
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/entity/LiveRoom.java
  3. 1 0
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/enums/RoomTypeEnum.java
  4. 5 0
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/LiveRoomService.java
  5. 8 0
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/UserWithdrawalService.java
  6. 1 1
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/impl/CourseScheduleServiceImpl.java
  7. 59 2
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/impl/LiveRoomServiceImpl.java
  8. 10 4
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/impl/UserWithdrawalServiceImpl.java
  9. 7 0
      cooleshow-user/user-biz/src/main/resources/config/mybatis/UserWithdrawalMapper.xml
  10. 6 0
      cooleshow-user/user-student/src/main/java/com/yonge/cooleshow/student/controller/StudentLiveRoomController.java
  11. 6 0
      cooleshow-user/user-teacher/src/main/java/com/yonge/cooleshow/teacher/controller/TeacherLiveRoomController.java
  12. 94 0
      cooleshow-user/user-teacher/src/main/java/com/yonge/cooleshow/teacher/controller/WithdrawController.java
  13. 113 0
      toolset/thirdparty-component/src/main/java/com/yonge/toolset/thirdparty/lingxinpay/Md5EncryptUtils.java
  14. 147 0
      toolset/thirdparty-component/src/main/java/com/yonge/toolset/thirdparty/lingxinpay/Withdraw.java

+ 7 - 1
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/dao/UserWithdrawalDao.java

@@ -1,6 +1,7 @@
 package com.yonge.cooleshow.biz.dal.dao;
 
 import java.util.List;
+import java.util.Map;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -35,5 +36,10 @@ public interface UserWithdrawalDao extends BaseMapper<UserWithdrawal>{
 	 */
 	List<UserWithdrawalVo> selectList(@Param("param") TeacherWithdrawalSearch teacherWithdrawal);
 
-	
+	/**
+	 * @Description: 提现成功回调
+	 * @Author: cy
+	 * @Date: 2022/5/9
+	 */
+    void withdrawSuccess(Map<String, Object> withdrawRecord);
 }

+ 1 - 1
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/entity/LiveRoom.java

@@ -63,7 +63,7 @@ public class LiveRoom implements Serializable {
     private Integer roomState;
 
     @TableField("type_")
-    @ApiModelProperty(value = "房间类型 live直播课  temp临时直播间")
+    @ApiModelProperty(value = "房间类型 live直播课  temp临时直播间 practice陪练课")
     private String type;
 
     @TableField("cover_pic_")

+ 1 - 0
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/enums/RoomTypeEnum.java

@@ -7,6 +7,7 @@ import java.util.stream.Collectors;
 public enum RoomTypeEnum {
 
     LIVE("LIVE", "直播课"),
+    PRACTICE("PRACTICE", "陪练课"),
     TEMP("TEMP", "临时直播间");
 
     private String code;

+ 5 - 0
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/LiveRoomService.java

@@ -39,6 +39,11 @@ public interface LiveRoomService extends IService<LiveRoom> {
     void destroyExpiredLiveRoom();
 
     /**
+     * 定时任务-清理过期的房间-陪练课
+     */
+    void destroyExpiredPracticeRoom();
+
+    /**
      * 创建临时房间-直播间
      */
     String createTempLiveRoom(Map<String, Object> param);

+ 8 - 0
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/UserWithdrawalService.java

@@ -12,6 +12,7 @@ import com.yonge.cooleshow.common.entity.HttpResponseResult;
 import com.yonge.toolset.utils.easyexcel.ExcelDataReaderProperty;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * 用户账户提现表 服务类
@@ -67,4 +68,11 @@ public interface UserWithdrawalService extends IService<UserWithdrawal>  {
 	 * @return: com.yonge.cooleshow.common.entity.HttpResponseResult<java.util.List<com.yonge.toolset.utils.easyexcel.ErrMsg>>
 	 */
     void importExcel(List<ExcelDataReaderProperty<UserWithdrawalExport>> dataList, Long userId);
+
+	/**
+	 * @Description: 提现成功回调
+	 * @Author: cy
+	 * @Date: 2022/5/9
+	 */
+	void withdrawSuccess(Map<String, Object> withdrawRecord);
 }

+ 1 - 1
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/impl/CourseScheduleServiceImpl.java

@@ -845,7 +845,7 @@ public class CourseScheduleServiceImpl extends ServiceImpl<CourseScheduleDao, Co
 
         String orderNo = orderReqInfo.getOrderNo();
         scheduleDto.setType(CourseScheduleEnum.PRACTICE.getCode());
-        scheduleDto.setStatus(CourseGroupEnum.NOT_SALE.getCode());
+        scheduleDto.setStatus(CourseGroupEnum.ING.getCode());
         scheduleDto.setMixStudentNum(1);
         scheduleDto.setStudentId(studentId);
 

+ 59 - 2
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/impl/LiveRoomServiceImpl.java

@@ -33,8 +33,7 @@ import java.util.function.BiFunction;
 import java.util.stream.Collectors;
 
 import static com.yonge.cooleshow.biz.dal.constant.LiveRoomConstant.*;
-import static com.yonge.cooleshow.common.constant.SysConfigConstant.DESTROY_EXPIRED_LIVE_ROOM_MINUTE;
-import static com.yonge.cooleshow.common.constant.SysConfigConstant.PRE_CREATE_LIVE_ROOM_MINUTE;
+import static com.yonge.cooleshow.common.constant.SysConfigConstant.*;
 
 /**
  * 直播房间与课程的关系表表(LiveRoom)表服务实现类
@@ -340,6 +339,64 @@ public class LiveRoomServiceImpl extends ServiceImpl<LiveRoomDao, LiveRoom> impl
     }
 
     /**
+     * 定时任务-清理过期的房间-陪练课
+     */
+    public void destroyExpiredPracticeRoom() {
+        //查询房间过期时间
+        String expiredMinuteStr = sysConfigService.findConfigValue(PRE_CREATE_PRACTICE_ROOM_MINUTE);
+        if (StringUtils.isEmpty(expiredMinuteStr)) {
+            log.info("roomDestroy>>>> 未查询到配置:{}", PRE_CREATE_PRACTICE_ROOM_MINUTE);
+            return;
+        }
+        Date now = new Date();
+        //查询已经开始并且没有删除及销毁的直播间
+        List<LiveRoom> list = this.list(Wrappers.<LiveRoom>lambdaQuery()
+                .eq(LiveRoom::getRoomState, 0)
+                .eq(LiveRoom::getLiveState, 1)
+                .eq(LiveRoom::getType, RoomTypeEnum.PRACTICE.getCode())
+                .le(LiveRoom::getLiveEndTime, now));
+        if (CollectionUtils.isEmpty(list)) {
+            return;
+        }
+        list.forEach(room -> {
+            Date expiredDate = DateUtil.addMinutes(room.getLiveEndTime(), Integer.parseInt(expiredMinuteStr));
+            //当前时间 大于(结束播时间 + 设置的过期分钟数)
+            if (now.getTime() >= expiredDate.getTime()) {
+                //删除房间
+                destroyLiveRoom(room.getRoomUid());
+                //查询老师分润表
+                CourseScheduleTeacherSalary salary = courseScheduleTeacherSalaryService.getOne(Wrappers.<CourseScheduleTeacherSalary>lambdaQuery()
+                        .eq(CourseScheduleTeacherSalary::getCourseScheduleId, room.getCourseId())
+                );
+                if (Objects.isNull(salary)) {
+                    return;
+                }
+                //查询该学生及课程id 对应的支付订单号
+                CourseScheduleStudentPayment payment = courseScheduleStudentPaymentService.getOne(Wrappers.<CourseScheduleStudentPayment>lambdaQuery()
+                        .eq(CourseScheduleStudentPayment::getCourseId, room.getCourseId())
+                        .eq(CourseScheduleStudentPayment::getUserId, salary.getStudentId())
+                );
+                if (Objects.isNull(payment)) {
+                    return;
+                }
+                //获取教师课酬写入到金额变更表
+                UserAccountRecordDto userAccountRecord = new UserAccountRecordDto();
+                userAccountRecord.setUserId(room.getSpeakerId());
+                userAccountRecord.setInOrOut(InOrOutEnum.IN);
+                userAccountRecord.setBizType(AccountBizTypeEnum.PRACTICE);
+                userAccountRecord.setBizId(room.getCourseId());
+                userAccountRecord.setBizName(room.getRoomTitle());
+                userAccountRecord.setTransAmount(salary.getActualSalary());//扣除手续费后所得金额
+                userAccountRecord.setOrderNo(payment.getOrderNo());
+                userAccountService.accountChange(userAccountRecord);
+                //修改老师课酬表
+                salary.setStatus(TeacherSalaryEnum.COMPLETE.getCode());
+                courseScheduleTeacherSalaryService.updateById(salary);
+            }
+        });
+    }
+
+    /**
      * 销毁房间-聊天室
      *
      * @param roomId 房间Uid

+ 10 - 4
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/impl/UserWithdrawalServiceImpl.java

@@ -39,10 +39,7 @@ import org.springframework.util.DigestUtils;
 
 import java.math.BigDecimal;
 import java.math.RoundingMode;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.List;
+import java.util.*;
 
 
 @Service
@@ -282,4 +279,13 @@ public class UserWithdrawalServiceImpl extends ServiceImpl<UserWithdrawalDao, Us
         return subtract;
     }
 
+    /**
+     * @Description: 提现成功回调
+     * @Author: cy
+     * @Date: 2022/5/9
+     */
+    @Override
+    public void withdrawSuccess(Map<String, Object> withdrawRecord) {
+        baseMapper.withdrawSuccess(withdrawRecord);
+    }
 }

+ 7 - 0
cooleshow-user/user-biz/src/main/resources/config/mybatis/UserWithdrawalMapper.xml

@@ -81,4 +81,11 @@
     <select id="selectList" resultType="com.yonge.cooleshow.biz.dal.vo.UserWithdrawalVo">
         <include refid="selectSql"/>
     </select>
+
+    <insert id="withdrawSuccess" parameterType="java.util.Map">
+        INSERT INTO user_withdrawal_history
+        VALUES (#{outMemberNo},#{outerOrderNo},#{orderNo},#{name},#{mobile},#{certificateNo},
+                #{status},#{payType},#{payAccount},#{predictAmount},#{actualAmount},#{endTime})
+    </insert>
+
 </mapper>

+ 6 - 0
cooleshow-user/user-student/src/main/java/com/yonge/cooleshow/student/controller/StudentLiveRoomController.java

@@ -80,6 +80,12 @@ public class StudentLiveRoomController extends BaseController {
         liveRoomService.destroyExpiredLiveRoom();
     }
 
+    @ApiOperation("定时任务-销毁房间-直播间-陪练课")
+    @GetMapping("/destroyExpiredPracticeRoom")
+    public void destroyExpiredPracticeRoom() {
+        liveRoomService.destroyExpiredPracticeRoom();
+    }
+
     /**
      * 同步融云用户状态变更
      *

+ 6 - 0
cooleshow-user/user-teacher/src/main/java/com/yonge/cooleshow/teacher/controller/TeacherLiveRoomController.java

@@ -80,6 +80,12 @@ public class TeacherLiveRoomController extends BaseController {
         liveRoomService.destroyExpiredLiveRoom();
     }
 
+    @ApiOperation("定时任务-销毁房间-直播间-陪练课")
+    @GetMapping("/destroyExpiredPracticeRoom")
+    public void destroyExpiredPracticeRoom() {
+        liveRoomService.destroyExpiredPracticeRoom();
+    }
+
     /**
      * 同步融云用户状态变更
      *

+ 94 - 0
cooleshow-user/user-teacher/src/main/java/com/yonge/cooleshow/teacher/controller/WithdrawController.java

@@ -0,0 +1,94 @@
+package com.yonge.cooleshow.teacher.controller;
+
+import com.alibaba.fastjson.JSONObject;
+import com.yonge.cooleshow.biz.dal.service.UserWithdrawalService;
+import com.yonge.cooleshow.common.controller.BaseController;
+import com.yonge.toolset.thirdparty.lingxinpay.RSA;
+import com.yonge.toolset.thirdparty.lingxinpay.Withdraw;
+import io.swagger.annotations.Api;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * @Description: 提现回调
+ * @Author: cy
+ * @Date: 2022/5/9
+ */
+@RestController
+@RequestMapping("/withdraw")
+@Api(value = "提现回调", tags = "提现回调")
+public class WithdrawController extends BaseController {
+    private final static Logger log = LoggerFactory.getLogger(WithdrawController.class);
+
+    //这里需要填写商户自己生成的私钥
+    private String privateKey = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANf/X1s2raYQAmY5VY929XqOIRESFlpLzWbltA08EI2i0jnJY3/kcCpo1sCHqkyWpDFGRgM1WYE90ayzEpS6EdZpyJ2/N5JFJzx4wMil5KHLdtQUmVv9si+xuYNOKfJW6Xn6zI/Wh81J1+hMlUY4WigU5Qci7DjdOjg5OD6e5DO3AgMBAAECgYEAor9ENhII3SsK48MneKWFaQZWW+po9ThQV8uT0rUDM/UOuYPIeMDC0vGTfhW6d2K57Haqohg8jGjr51g2E+HvNV+fARaBfCwy00DGcxjI6N8gEInj2AppsGV6a0ZtzGBh3BxGhEFV7x3NmTylDk3WkGnkGDqDNyrBUgK0BzCJEmECQQD+gYckYRevVfrZEHDQHRNzzMUlV9/ljA9x8dt0LoCNbd/wFvs0Ekjhas/2lUBkewEd4Kr0jaFcYzlUO/qihcpNAkEA2UP5W44yLuIo8ttPpdsfkH/8Ax64IywQHrXWq+thH7I91VwY2vomTduw8x0PafZtp8xryF3LixTZwQ7gsYbwEwJAQbb8SB5x2SogPVALcREw5qOm+/92pnTFwCws+BDRzLLkMcAdWNKn0tybmhXrrIY+QZKzUbYIRiywrtlV3AUjuQJBAJbnFnfX4NUdchGT79Mjyd2kdxZ3rK+JOD0MUWkhWFkahMX/bKgTXK1xLIr/ISiY53rHigkl1Gzqc4Aa5EeJkI8CQFlzlBOfoVnq3fPMZCCZcZSm97L12MgHho0AzoNj9sw9YYn9WPM7bw2HT8GUheiU3aiZGCyGGpYuVHMiBGa7l9U=";
+
+    @Autowired
+    private UserWithdrawalService userWithdrawalService;
+
+    /**
+     * 异步回调接收
+     *
+     * @param content
+     * @param request
+     * @return
+     */
+    @PostMapping("/callback")
+    public String test(@RequestBody String content, HttpServletRequest request) {
+        log.info("交易回调请求地址:{} 请求参数:{}", request.getRemoteAddr(), content);
+        try {
+            if (StringUtils.isBlank(content)) {
+                throw new Exception();
+            }
+            Map<String, Object> map = JSONObject.parseObject(content);
+            String jsonStr = RSA.decryptPri((String) map.get("sign"), privateKey);
+            log.info("jsonStr:{}", jsonStr);
+
+//            Map<String, Object> withdrawRecord = JSONObject.parseObject(jsonStr);
+//            userWithdrawalService.withdrawSuccess(withdrawRecord);
+            return jsonStr;
+        } catch (Exception e) {
+            log.error("解密失败e:{}", e);
+            return "failed";
+        }
+    }
+    @GetMapping("/test")
+    public String a(){
+        Withdraw withdraw = new Withdraw();
+
+        //输入商户订单号
+        String outerOrderNo = UUID.randomUUID().toString().substring(0, 12);
+        //输入收款人手机号
+        String name = "何亮";
+        //输入收款人姓名
+        String mobile = "17600220933";
+        //输入收款人身份证号
+        String certificateNo = "130423199206192818";
+        //输入转账金额(单位分)
+        String predictAmount = "1";
+        //输入收款人账号
+        String payAccount = "6228480018864836772";
+        //输入卡类型:DC借记卡,CC信用卡(暂不支持)
+        String cardType = "DC";
+        //输入发放类型(0:工资,1:奖金,2:绩效,3:劳务,4:个人经营所得,5:其他)
+        String salaryType = "4";
+        //输入项目名称
+        String projectName = "测试";
+        //输入支付类型(1:银行卡)
+        String payType = "1";
+        //输入卡属性:(C:对私 ,B:对公)暂时不支持对公
+        String cardAttribute = "C";
+
+        String requestParam = withdraw.withdraw(outerOrderNo, name, mobile, certificateNo, predictAmount, payAccount, cardType,
+                salaryType, projectName, payType, cardAttribute);
+        log.info("单笔请求返回参数:{}", requestParam);
+        return requestParam;
+    }
+}

+ 113 - 0
toolset/thirdparty-component/src/main/java/com/yonge/toolset/thirdparty/lingxinpay/Md5EncryptUtils.java

@@ -0,0 +1,113 @@
+package com.yonge.toolset.thirdparty.lingxinpay;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+
+/**
+ * MD5加密算法
+ * @author xumum
+ *
+ */
+public class Md5EncryptUtils {
+	/**
+	 * Used building output as Hex
+	 */
+	private static final char[] DIGITS = { '0', '1', '2', '3', '4', '5', '6',
+			'7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
+	/**
+	 * 对字符串进行MD5加密
+	 * @param text 明文
+	 * @return 密文
+	 */
+	public static String md5(String text) {
+		MessageDigest msgDigest = null;
+		try {
+			msgDigest = MessageDigest.getInstance("MD5");
+		} catch (NoSuchAlgorithmException e) {
+			throw new IllegalStateException(
+					"System doesn't support MD5 algorithm.");
+		}
+		try {
+			msgDigest.update(text.getBytes("UTF-8"));
+		} catch (UnsupportedEncodingException e) {
+			throw new IllegalStateException(
+					"System doesn't support your  EncodingException.");
+		}
+		byte[] bytes = msgDigest.digest();
+		String md5Str = new String(encodeHex(bytes));
+		return md5Str;
+	}
+
+	public static char[] encodeHex(byte[] data) {
+		int l = data.length;
+		char[] out = new char[l << 1];
+		for (int i = 0, j = 0; i < l; i++) {
+			out[j++] = DIGITS[(0xF0 & data[i]) >>> 4];
+			out[j++] = DIGITS[0x0F & data[i]];
+		}
+		return out;
+	}
+	
+	
+	/** 
+     * 方法描述:签名字符串 
+     * @author xumum
+     * @param params 需要签名的参数 
+     * @param key 签名密钥 
+     * @return 
+     */  
+    public static String sign(Map<String, Object> params, String key) {  
+        StringBuilder valueSb = new StringBuilder();  
+       
+        // 将参数以参数名的字典升序排序  
+        Map<String, Object> sortParams = new TreeMap<String, Object>(params);  
+        Set<Entry<String, Object>> entrys = sortParams.entrySet();  
+        // 参数以参数名的字典升序排序 ,并拼接param1=value1&param2=value2...&key=value格式  
+        for (Entry<String, Object> entry : entrys) {  
+        	valueSb.append(entry.getKey());
+        	valueSb.append("=");
+            valueSb.append(entry.getValue());  
+            valueSb.append("&");  
+        }  
+        String paramStr = valueSb.toString();
+        paramStr = paramStr.substring(0, paramStr.length()-1);
+        return MD5sign(paramStr,key);  
+    }  
+    
+    /** 
+     * MD5生成签名字符串 
+     * @param parmas 需签名参数
+     * @param key MD5key 
+     * @return 
+     */  
+    public static String MD5sign(String parmas, String key) {  
+        String sign = "";
+        try {  
+        	parmas += "&key=" + key;  
+        	sign = md5(parmas);  
+        } catch (Exception e) {  
+            e.printStackTrace();  
+        }  
+        return sign;  
+    }  
+    
+    public static void main(String[] args) {  
+        Map<String ,Object> map = new HashMap<String,Object>();  
+        map.put("companyNo", "1504a229bb034f19b6bf21738976981d");  
+        map.put("salaryOrderCode", "2018040353359360");  
+        map.put("predictNumber", 1);  
+        map.put("predictTotalSum", "1.00");  
+        /***MD5签名与验签**/  
+        String key="7d50522f57df480c80c16703493bf259";  
+        String sign= sign(map,key);  
+        System.out.println("生成的MD5签名:"+sign);  
+    }  
+
+}

+ 147 - 0
toolset/thirdparty-component/src/main/java/com/yonge/toolset/thirdparty/lingxinpay/Withdraw.java

@@ -0,0 +1,147 @@
+package com.yonge.toolset.thirdparty.lingxinpay;
+
+import com.alibaba.fastjson.JSONObject;
+import com.yonge.toolset.utils.http.HttpUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * @author: cy
+ * @date: 2022/5/9 11:24
+ */
+@Service
+public class Withdraw {
+    private static final Logger logger = LoggerFactory.getLogger(Withdraw.class);
+
+    private String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDX/19bNq2mEAJmOVWPdvV6jiEREhZaS81m5bQNPBCNotI5yWN/5HAqaNbAh6pMlqQxRkYDNVmBPdGssxKUuhHWacidvzeSRSc8eMDIpeShy3bUFJlb/bIvsbmDTinyVul5+syP1ofNSdfoTJVGOFooFOUHIuw43To4OTg+nuQztwIDAQAB";
+    private String md5Key = "0fd42370bad6485e46718b97f3dd1536";
+    private String notifyUrl = "http://47.114.1.200:8000/teacher-server/withdraw/callback";//回调地址
+    private String memberNo = "1491663782974988288";//商户号
+    private String apiUrl = "http://39.107.15.64:8090";//第三方url
+
+    /**
+     * 单笔提现
+     *
+     * @param outerOrderNo  商户唯一订单号
+     * @param name          收款方姓名(银行预留姓名等)
+     * @param mobile        收款方电话
+     * @param certificateNo 收款方身份证号
+     * @param predictAmount 应发金额(单位为:分,范围: 1~10000000000)
+     * @param payAccount    收款方账号(银行卡号/支付宝账号 /open_id)以实际业务为准
+     * @param cardType      卡类型:DC 借记卡
+     * @param salaryType    发放类型(0:个人经营所得)
+     * @param projectName   项目名称
+     * @param payType       支付类型(1:银行卡,2:支付宝,4:微信) 以实际业务为准
+     * @param cardAttribute 卡属性:(C:对私)
+     * @return
+     */
+    public String withdraw(String outerOrderNo, String name, String mobile, String certificateNo, String predictAmount,
+                           String payAccount, String cardType, String salaryType, String projectName, String payType, String cardAttribute) {
+        Map<String, Object> map = new HashMap<>();
+        map.put("outMemberNo", memberNo);
+        map.put("outerOrderNo", outerOrderNo);
+        map.put("name", name);
+        map.put("certificateNo", certificateNo);
+        map.put("predictAmount", predictAmount);
+        String signs = Md5EncryptUtils.sign(map, md5Key);
+
+        map.put("charset", "UTF-8");
+        map.put("mobile", mobile);
+        map.put("version", "1.1");
+        map.put("service", "bpotop.zx.pay.order");
+        map.put("Md5Key", signs);
+        map.put("notifyUrl", notifyUrl);
+        map.put("cardType", cardType);
+        map.put("salaryType", salaryType);
+        map.put("projectName", projectName);
+        map.put("payType", payType);
+        map.put("cardAttribute", cardAttribute);
+        map.put("payAccount", payAccount);
+        String jsonStr = JSONObject.toJSONString(map);
+
+        //签名
+        JSONObject mapParam = new JSONObject();
+        try {
+            //使用公钥加密
+            String encryptStr = RSA.encryptPub(jsonStr, publicKey);
+            mapParam.put("outMemberNo", memberNo);
+            mapParam.put("signType", "RSA");
+            mapParam.put("sign", encryptStr);
+            logger.info("单笔请求请求参数:{}", JSONObject.toJSONString(mapParam));
+        } catch (Exception e) {
+            logger.info("加密失败:{}", e);
+        }
+
+        //发送
+        try {
+            String resultJsonStr = HttpUtil.postForHttp(apiUrl + "/bpotop_trade/single", JSONObject.toJSONString(mapParam), null);
+            logger.info("单笔请求返回参数:{}", resultJsonStr);
+            return resultJsonStr;
+        } catch (IOException e) {
+            logger.info("发送失败:{}", e);
+        }
+        return null;
+    }
+
+    /**
+     * 查询接口
+     *
+     * @param outerOrderNo 商户唯一订单号
+     * @throws Exception
+     */
+    public void query(String outerOrderNo) throws Exception {
+        Map<String, Object> requestMap = new HashMap<>();
+        requestMap.put("outMemberNo", memberNo);
+        requestMap.put("outerOrderNo", outerOrderNo);
+        requestMap.put("service", "bpotop.zx.pay.order");
+        requestMap.put("version", "1.0");
+        requestMap.put("signType", "RSA");
+        requestMap.put("charset", "UTF-8");
+        String jsonStr = JSONObject.toJSONString(requestMap);
+        String encryptStr = RSA.encryptPub(jsonStr, publicKey);
+
+        Map<String, Object> requestMap2 = new HashMap<>();
+        requestMap2.put("outMemberNo", memberNo);
+        requestMap2.put("sign", encryptStr);
+        logger.info("单笔查询请求参数:{}", JSONObject.toJSONString(requestMap2));
+        String resultJsonStr = HttpUtil.postForHttp(apiUrl + "/bpotop_trade/order_query", JSONObject.toJSONString(requestMap2), null);
+        logger.info("单笔查询响应参数:{}", resultJsonStr);
+    }
+
+    public static void main(String[] args) throws Exception {
+        Withdraw withdraw = new Withdraw();
+
+        //输入商户订单号
+        String outerOrderNo = UUID.randomUUID().toString().substring(0, 12);
+        //输入收款人手机号
+        String name = "何亮";
+        //输入收款人姓名
+        String mobile = "17600220933";
+        //输入收款人身份证号
+        String certificateNo = "130423199206192818";
+        //输入转账金额(单位分)
+        String predictAmount = "1";
+        //输入收款人账号
+        String payAccount = "6228480018864836772";
+        //输入卡类型:DC借记卡,CC信用卡(暂不支持)
+        String cardType = "DC";
+        //输入发放类型(0:工资,1:奖金,2:绩效,3:劳务,4:个人经营所得,5:其他)
+        String salaryType = "4";
+        //输入项目名称
+        String projectName = "测试";
+        //输入支付类型(1:银行卡)
+        String payType = "1";
+        //输入卡属性:(C:对私 ,B:对公)暂时不支持对公
+        String cardAttribute = "C";
+
+        String requestParam = withdraw.withdraw(outerOrderNo, name, mobile, certificateNo, predictAmount, payAccount, cardType,
+                salaryType, projectName, payType, cardAttribute);
+        logger.info("单笔请求返回参数:{}", requestParam);
+    }
+}