SpringBoot开发银行聚合支付扫码业务,学到就是赚到,不用花钱可以学到精髓

一、文档需求

这是开始的第一步,也是很重要的一步,如果你连文档都看不懂,就别想着继续下一步操作了。

其中还会涉及到与银行技术交接这一过程,话不多说

返回结果:

订单接口:这是轮循时候使用到的,当用户在支付中或者待支付,不断查询订单,直到成功或者超时

返回信息:

字段分析:商户号、终端号、商户key是银行提供的,其中商户key文档没出现,这些测试的数据都是需要对方技术提供的

批次号、系统跟踪号、商户订单号、交易金额、二维码信息、随机字符串、签名,根据文档,这是必须要传的,不要传的像

收银员编号、订单备注、缴费类型、缴费说明、经度、纬度,可以不传

商户订单号:文档没有描述,随机生成32唯一不重复的订单号即可,这些肯定是唯一的

随机字符串:32位不重复的,网上有很多种生成方式

交易金额:银行那边是以分为单位的,我们使用postman测试,输入1,其实他们那边进行了转换,也就是0.1元

二维码信息:我们可以打开支付宝或微信,输入有效的支付码进行付款,必须是有效的,否则按照业务逻辑,也是返回二维码一些错误信息

下面有三个字段是需要注意的,文档中有说明!

批次号:批次号,文档有说明,如下图,使用商户号和终端号调用此接口,在返回结果中会有批次号

系统跟踪号:如下图,详细参考代码

签名:签名如下图,具体什么是签名,根据文档一步一步操作,结合代码会慢慢理解

二、代码部分

1、controller层

package com.example.parkingfees.controller;import com.alibaba.fastjson.JSONObject;
import com.example.parkingfees.service.MicroPayService;
import com.example.parkingfees.service.SignService;
import com.example.parkingfees.service.TraceNoService;
import com.example.parkingfees.utils.HttpsUtil;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.*;/*** @author 刘磊* @date 2020/9/3 13:47*/
@RestController
@RequestMapping(value = "/micropay")
public class MicroPayController {@Autowiredprivate SignService signService;@Autowiredprivate MicroPayService microPayService;@Autowiredprivate TraceNoService traceNoService;/*** 需求:商家扫取客户微信、支付宝、银联向客户收款(批次号是必须的属性)* @param cashierNo    收银员编号--否* @param transAmount  交易⾦额--必须* @param payCode      ⼆维码信息--必须* @param remark       订单备注--否* @param ymisFlag     缴费类型--否* @param ymisRemark   缴费说明--否* @param longitude    经度--否* @param latitude     纬度--否*/@RequestMapping(value = "/queryMicropay",method = RequestMethod.POST)public Object microPayMethod(String cashierNo, Integer transAmount, String payCode,String remark, String ymisFlag, String ymisRemark, String longitude, String latitude){Map params = new HashMap();Map map = new HashMap<>();// 银行提供:商户号、终端号、商户keyMap bank = microPayService.getBankMation();String newMerchantNo =  bank.get("merchantNo").toString(); // map的value是Object类型,转StringString newTerminalNo = bank.get("terminalNo").toString();String newBussinessKe = bank.get("bussinessKey").toString();// 商户订单号(生成一个32位的随机数。防止重复,保留唯一性)UUID uuid = UUID.randomUUID();String OutTradeNo = uuid.toString().replace("-","");// 随机字符串String nonceStr = RandomStringUtils.randomAlphanumeric(32);// 系统跟踪号(1、必须为六位,数字不⾜时,在前补0。。2、系统跟踪号的值从1到999999循环使⽤。。3、同⼀天,同终端下系统跟踪号不可重复)Integer t = traceNoService.getTrace();// 查询系统跟踪号此时是多少,默认为0if (t==999999){t=0;traceNoService.updateResetTrace(t);}traceNoService.updateTraceNo(t+1); // 每访问一次接口加1Integer trc= traceNoService.getTrace(); // 再次查询String traceNo = String.format("%06d",trc); // 转成6位,数字不足时在前补0// 查询sce_sign表中是否存在当天批次号String existenceBatchNo  = signService.querySignBatchNo();if ("".equals(existenceBatchNo) || existenceBatchNo==null){params.put("merchantNo",newMerchantNo);params.put("terminalNo",newTerminalNo);// 签到urlString url = "https://银行接口ip:端口号/cposp/pay/signIn";// 请求签到url,获取批次号,每天定时访问一次String result = HttpsUtil.httpJsonMethodPost(url, JSONObject.toJSONString(params),"");if (!"".equals(result) && result !=null){// 获取到key为batchNo的值(batchNo:批次号,当日可以共用批次号)existenceBatchNo = JSONObject.parseObject(result).getString("batchNo");map.put("batchNo",existenceBatchNo);// 将此批次号存到表中signService.addSign(map);}else{map.put("signInResult", HttpStatus.NO_CONTENT); // 签到结果无内容返回return map;}}// 二维码信息Map res  = microPayService.microPayMethod(newMerchantNo,newTerminalNo,newBussinessKe,cashierNo,existenceBatchNo,traceNo,OutTradeNo,transAmount,payCode,nonceStr,remark,ymisFlag,ymisRemark,longitude,latitude);return res;}
}

2、service,我备注都写了,查询的功能,每个service对应的作用是不同的,实现的service就不写了,直接写实现类了

MicroPayServiceImpl层:

package com.example.parkingfees.service;import com.alibaba.fastjson.JSONObject;
import com.example.parkingfees.mapper.BankMapper;
import com.example.parkingfees.utils.HttpsUtil;
import com.example.parkingfees.utils.Md5Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import java.io.UnsupportedEncodingException;
import java.util.*;/*** @author 刘磊* @date 2020/9/7 10:01*/
@Service
public class MicroPayServiceImpl implements MicroPayService{@Autowiredprivate BankMapper bankMapper;public Map microPayMethod(String merchantNo,String terminalNo,String bussinessKey,String cashierNo,String batchNo,String traceNo,String OutTradeNo,Integer transAmount,String payCode,String nonceStr,String remark,String ymisFlag,String ymisRemark,String longitude,String latitude){Map map = new HashMap<>();SortedMap params = new TreeMap<>();// 使用if判断的都是必须的条件if (!"".equals(merchantNo) && merchantNo!=null){params.put("merchantNo",merchantNo); // 商户号}if (!"".equals(terminalNo) && terminalNo!=null){params.put("terminalNo",terminalNo); // 终端号}if (!"".equals(batchNo) && batchNo!=null){params.put("batchNo",batchNo); // 批次号}if (!"".equals(traceNo) && traceNo!=null){params.put("traceNo",traceNo); // 系统跟踪号}if (!"".equals(OutTradeNo) && OutTradeNo!=null){params.put("outTradeNo",OutTradeNo); // 商户订单号}if (transAmount!=0 && transAmount!=null){params.put("transAmount",transAmount.toString()); // 交易金额}if (!"".equals(payCode) && payCode!=null){params.put("payCode",payCode); // 二维码信息}if (!"".equals(nonceStr) && nonceStr!=null){params.put("nonceStr", nonceStr);  // 随机字符串}params.put("cashierNo",cashierNo); // 收银员编号params.put("remark",remark); // 订单备注params.put("ymisFlag",ymisFlag); // 缴费类型params.put("ymisRemark",ymisRemark); // 缴费说明params.put("longitude",longitude); // 经度params.put("latitude",latitude);   // 纬度try {params.put("sign", Md5Util.generateSign(params,bussinessKey)); // 签名信息}catch (UnsupportedEncodingException e){e.printStackTrace();}String payresult = HttpsUtil.httpJsonMethodPost("https://对接方ip:端口号/cposp/pay/microPay", JSONObject.toJSONString(params),"UTF-8");// 支付状态返回信息String payStatus = JSONObject.parseObject(payresult).getString("orderStatus");String payMessage = JSONObject.parseObject(payresult).getString("resultMessage");if (!"".equals(payresult) && payresult!=null && "3".equals(payStatus)){ // 判断http结果是否为空map.put("payResult",payresult);map.put("payStatus",payStatus);map.put("payMessage",payMessage);return map;}else if("".equals(payStatus) || payStatus == null){map.put("payresult",payresult);map.put("resultCode",JSONObject.parseObject(payresult).getString("resultCode"));map.put("resultMessage",JSONObject.parseObject(payresult).getString("resultMessage"));map.put("orderQueryResult",HttpStatus.NO_CONTENT); // 订单查询接口无内容返回return map;}else{String zcposOrderId = JSONObject.parseObject(payresult).getString("cposOrderId");//云收单订单号String zreferNo = JSONObject.parseObject(payresult).getString("referNo");//检索参考号String zitpOrderId = JSONObject.parseObject(payresult).getString("itpOrderId");//信e付订单号String zmerchantNo = JSONObject.parseObject(payresult).getString("merchantNo");// 商户号String zterminalNo = JSONObject.parseObject(payresult).getString("terminalNo");//终端号String ztraceNo = JSONObject.parseObject(payresult).getString("traceNo");// 系统跟踪号String zbatchNo = JSONObject.parseObject(payresult).getString("batchNo");// 批次号SortedMap orderMap = new TreeMap<>();orderMap.put("merchantNo",zmerchantNo); // 商户号orderMap.put("terminalNo",zterminalNo);//终端号orderMap.put("batchNo",zbatchNo);//批次号orderMap.put("traceNo",ztraceNo);//系统跟踪号orderMap.put("cposOrderId",zcposOrderId);//云收单订单号orderMap.put("nonceStr",nonceStr);//随机字符串orderMap.put("referNo",zreferNo);//检索参考号orderMap.put("itpOrderId",zitpOrderId);//信e付订单号orderMap.put("outTradeNo",OutTradeNo); // 商户订单号orderMap.put("cashierNo",cashierNo);// 收银员编号orderMap.put("remark",remark); // 订单备注orderMap.put("longitude",longitude); // 经度orderMap.put("latitude",latitude); // 纬度try {orderMap.put("sign", Md5Util.generateSign(orderMap,bussinessKey)); // 签名信息}catch (UnsupportedEncodingException e){e.printStackTrace();}String orderStatus = null; // 订单装填String orderMessage = null; // 订单信息String orderqueryresult = null; // 订单查询结果if ("1".equals(payStatus) || "2".equals(payStatus)){// 待支付和已支付,查询订单接口,进入轮循for (int i = 1;i <= 20 ; i++){ // 20次,5秒一次try {Thread.sleep(5000); //设置暂停的时间 5 秒} catch (InterruptedException e) {e.printStackTrace();}String orderUrl = "https://ip端口号:端口号/cposp/pay/orderQuery";orderqueryresult = HttpsUtil.httpJsonMethodPost(orderUrl,JSONObject.toJSONString(orderMap),"");if (!"".equals(orderqueryresult) && orderqueryresult!=null ){// 待支付 || 支付中orderStatus = JSONObject.parseObject(orderqueryresult).getString("orderStatus"); orderMessage = JSONObject.parseObject(orderqueryresult).getString("resultMessage");if("3".equals(orderStatus)){break;}if (i==20){map.put("orderTimeout",HttpStatus.REQUEST_TIMEOUT); // 超时break;}}else{map.put("orderQueryResult",HttpStatus.NO_CONTENT);return map;}}}map.put("orderqueryresult",orderqueryresult);map.put("orderStatus",orderStatus);map.put("orderMessage",orderMessage);return map;}}@Overridepublic Map getBankMation() {return bankMapper.getBankMation();}
}

其中此Service中使用到的两个工具类,Md5Util和HttpsUtil

Md5Util:

package com.example.parkingfees.utils;import org.apache.commons.lang3.ObjectUtils;
import org.springframework.util.DigestUtils;
import java.io.UnsupportedEncodingException;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedMap;/*** @author 刘磊* @date 2020/9/3 19:20*/
public class Md5Util {// 对传过来的params参数进行md5加密public static String generateSign(SortedMap parameterMap,String bussinessKey) throws UnsupportedEncodingException {ObjectUtils.allNotNull(parameterMap);String url = "";int i = 0;for(Iterator var5 = parameterMap.entrySet().iterator(); var5.hasNext(); ++i) {Map.Entry entry = (Map.Entry)var5.next();String key = entry.getKey();String value = entry.getValue();// 参数的值若为空,则不参与签名if(!"".equals(value) && value!=null){if (i == 0) {url = url + key + "=" + value;} else {url = url + "&" + key + "=" + value;}}}url = url + "&key="+bussinessKey;  //对应文档sign第二步骤 拼接keyreturn DigestUtils.md5DigestAsHex(url.getBytes()).toUpperCase();}
}

HttpsUtil:

/*** HTTP协议POST请求方法*/
public static String httpJsonMethodPost(String url, String params, String gb) {if (null == gb || "".equals(gb)) {gb = "UTF-8";}StringBuffer sb = new StringBuffer();URL urls;HttpsURLConnection uc = null;BufferedReader in = null;try {SSLContext sc = SSLContext.getInstance("SSL");sc.init(null, new TrustManager[]{new MyTrustManager()}, new java.security.SecureRandom());urls = new URL(url);uc = (HttpsURLConnection) urls.openConnection();uc.setSSLSocketFactory(sc.getSocketFactory());uc.setHostnameVerifier(new MyHostnameVerifier());uc.setRequestMethod("POST");uc.setDoOutput(true);uc.setDoInput(true);uc.setUseCaches(false);uc.setRequestProperty("Content-Type", "application/json");uc.connect();DataOutputStream out = new DataOutputStream(uc.getOutputStream());out.write(params.getBytes(gb));out.flush();out.close();in = new BufferedReader(new InputStreamReader(uc.getInputStream(), gb));String readLine = "";while ((readLine = in.readLine()) != null) {sb.append(readLine);}if (in != null) {in.close();}if (uc != null) {uc.disconnect();}} catch (Exception e) {log.error(e.getMessage(), e);} finally {if (uc != null) {uc.disconnect();}}return sb.toString();
}

 

SignServiceImpl层:

package com.example.parkingfees.service;import com.example.parkingfees.mapper.SignMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Map;/*** @author 刘磊* @date 2020/9/4 10:43*/
@Service
public class SignServiceImpl implements SignService{@AutowiredSignMapper signMapper;@Overridepublic void addSign(Map map) {signMapper.addSign(map);}@Overridepublic String querySignBatchNo() {return signMapper.querySignBatchNo();}
}

TraceNoServiceImpl层:

package com.example.parkingfees.service;import com.example.parkingfees.mapper.TraceNoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;/*** @author 刘磊* @date 2020/9/8 9:41*/
@Service
public class TraceNoServiceImpl implements TraceNoService{@Autowiredprivate TraceNoMapper traceNoMapper;@Overridepublic Integer getTrace() {return traceNoMapper.getTrace();}@Overridepublic void updateTraceNo(Integer traceNo) {traceNoMapper.updateTraceNo(traceNo);}@Overridepublic void updateResetTrace(Integer t) {traceNoMapper.updateResetTrace(t);}
}

3、所有Mapper层

BankMapper层,查询银行提供的商户号,终端号,商户key:

package com.example.parkingfees.mapper;import org.springframework.stereotype.Repository;
import java.util.Map;/*** @author 刘磊* @date 2020/9/7 15:03*/
@Repository
public interface BankMapper {Map getBankMation();
}

SignMapper层:

package com.example.parkingfees.mapper;import org.springframework.stereotype.Repository;
import java.util.Map;/*** @author 刘磊* @date 2020/9/4 10:39*/
@Repository
public interface SignMapper {void addSign(Map map);String querySignBatchNo();
}

TraceNoMapper层:

package com.example.parkingfees.mapper;import org.springframework.stereotype.Repository;/*** @author 刘磊* @date 2020/9/8 9:41*/
@Repository
public interface TraceNoMapper {Integer getTrace();void updateTraceNo(Integer traceNo);void updateResetTrace(Integer t);
}

4、所有xml层

BankMapper.xml层:





SignMapper.xml层:




insert into sce_sign(batchNo,nowtime)values(#{batchNo},now())

TraceNoMapper.xml层:




UPDATE sce_stnumSET traceNo = #{traceNo};UPDATE sce_stnumSET traceNo = #{t};

5、SQL,至于建表,都是根据业务需求来的,动态自己想要的数据

sce_bank表:

CREATE TABLE `sce_bank` (
  `id` int(11) NOT NULL,
  `merchantNo` varchar(15) NOT NULL COMMENT '商户号',
  `terminalNo` varchar(8) NOT NULL COMMENT '终端号',
  `bussinessKey` varchar(100) NOT NULL COMMENT '商户key',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

 

sce_sign表:

CREATE TABLE `sce_sign` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `batchNo` varchar(8) NOT NULL,
  `nowtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8;

 

sce_stnum表:
CREATE TABLE `sce_stnum` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `traceNo` int(6) NOT NULL COMMENT '系统跟踪号',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

这些就是从头至尾的所有代码部分,看不懂,给我留言吧。主要是思想,看懂文档才是最重要的,下面目前给出两三种结果

付款成功,返回的结果:

{"payResult": "{\"resultCode\":\"00\",\"resultMessage\":\"交易成功\",\"merchantNo\":\"94734160742A1WD\",\"terminalNo\":\"72001845\",\"batchNo\":\"000071\",\"traceNo\":\"000093\",\"transAmount\":1,\"transTime\":\"20200912105806\",\"cposOrderId\":\"1304617647610544128\",\"referNo\":\"123013051530\",\"itpOrderId\":\"12002020091299301552\",\"chnOrderId\":\"4200000689202009122710474193\",\"orderStatus\":\"3\",\"tradeChannel\":\"01\",\"errorDesc\":\"\",\"outletName\":\"070测试\",\"payUserInfo\":\"070测试\",\"paidTime\":\"2020-09-12\"}", "payStatus": "3", "payMessage": "交易成功"
}

不付款之后返回的结果:

{"payresult": "{\"resultCode\":\"9999\",\"resultMessage\":\"操作失败\"}", "resultCode": "9999", "orderQueryResult": "NO_CONTENT", "resultMessage": "操作失败"
}

不存在的支付码返回的结果:

{"payresult": "{\"resultCode\":\"9999\",\"resultMessage\":\"付款码不可用,请重新支付\"}", "resultCode": "9999", "orderQueryResult": "NO_CONTENT", "resultMessage": "付款码不可用,请重新支付"
}


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部