微信小程序支付Java服务端开发源码,及那些你不知道的坑(一)
受新冠病毒疫情影响,小程序又被推上风间浪头,曾经的线下实体企业都开始纷纷的转型线上,但目前线上最大的入口莫过于微信。因此小程序成了商家们转型线上的首选。而由于微信自己的生态原因,小程序的在线支付只能使用微信小程序支付。这有让微信支付也越来越火,最近有很多开发者都找我咨询和要微信支付的源码的事情。我今天也再说说这事。
微信小程序支付
说道小程序支付,我要稍稍吐槽一下,微信支付真的搞的很乱。如果你之前项目中已经接入了微信的扫码和App支付,现在要接入小程序支付,大多数人的想法就是,我已经有微信支付的账号了,直接接入使用,那我告诉你,你错了。微信小程序支付是通过申请的小程序开通的微信支付,而微信的扫码和App支付是通过公众号开通的支付,两个不可通用。真是够麻烦,相当让人反感,但我们还得乖乖的用,谁叫人家微信有如此大的生态呢?
不了解微信小程序支付的伙伴们,建议还是先看看开发者文档,知道基础的业务流程。
小程序支付的业务流程图:
一,小程序支付开发源码
首先创建自己的项目,我这里创建的是SpringBoot的项目。
1,在resources下创建application.yml文件,并添加配置如下:
1 spring: 2 profiles: 3 active: dev 4 5 ##pay config 6 payment: 7 ##wechat config 8 wx: 9 ##小程序支付 10 lte: 11 appid: *** 12 mchid: *** 13 key: ***
2,创建获取配置属性类PayConfig,代码如下:
1 @Component 2 @ConfigurationProperties(prefix = "payment") 3 public class PayConfig { 4 5 //微信支付类型 6 //NATIVE--原生支付 7 public static final String TRADE_TYPE_NATIVE = "NATIVE"; 8 //JSAPI--公众号支付-小程序支付 9 public static final String TRADE_TYPE_JSAPI = "JSAPI"; 10 //MWEB--H5支付 11 public static final String TRADE_TYPE_MWEB = "MWEB"; 12 //APP -- app支付 13 public static final String TRADE_TYPE_APP = "APP"; 14 15 16 //小程序支付参数 17 public static String WX_LTE_APP_ID; 18 public static String WX_LTE_MCH_ID; 19 public static String WX_LTE_KEY; 20 21 22 //微信支付API 23 public static final String WX_PAY_UNIFIED_ORDER = "https://api.mch.weixin.qq.com/pay/unifiedorder"; 24 25 26 @Value("${payment.wx.lte.appid}") 27 public void setWxLteAppId(String wxLteAppId) { 28 WX_LTE_APP_ID = wxLteAppId; 29 } 30 @Value("${payment.wx.lte.mchid}") 31 public void setWxLteMchId(String wxLteMchId) { 32 WX_LTE_MCH_ID = wxLteMchId; 33 } 34 @Value("${payment.wx.lte.key}") 35 public void setWxLteKey(String wxLteKey) { 36 WX_LTE_KEY = wxLteKey; 37 } 38 }
3,添加启动类:
4,创建一个常量类,放支付涉及的常量信息
1 public interface PaymentConstants { 2 3 String COMPANY_NAME = "某某某科技有限公司";//用做收款方名称 4 String COMPANY_NICK_NAME = "某某某";//收款方名称简称 5 String COMPANY_PREFIX = "pay_"; 6 7 //项目环境 8 String PROJECT_ENV_DEV = "dev"; 9 String PROJECT_ENV_PRO = "pro"; 10 11 String PAYMENT_TITLE = "商品购买";//支付title信息,也可用真实商品名称 12 13 //支付类型,支付宝和微信 14 int PAY_TYPE_ALI = 1; 15 int PAY_TYPE_WX = 2; 16 17 //微信支付成功后回调url 18 String WX_PAY_CALLBACK_URL = "/api/payment/wxNotify"; 19 20 //扫描支付 21 String PAY_TRADE_TYPE_QR = "QR"; 22 //App支付 23 String PAY_TRADE_TYPE_APP = "APP"; 24 //小程序支付 25 String PAY_TRADE_TYPE_LTE = "LTE"; 26 27 String SUCCESS = "SUCCESS"; 28 String OK = "OK"; 29 }
5,创建服务接口PaymentService;
1 public interface PaymentService { 2 3 /** 4 * 小程序支付 5 * @param openId 6 * @param orderNo 7 * @param money 8 * @return 9 * @throws Exception 10 */ 11 Map<String, String> wxLtePayment(String openId, String orderNo, double money) throws Exception; 12 13 /** 14 * 微信支付回调 15 * @param map 16 * @return 17 * @throws Exception 18 */ 19 int wxNotify(Map<String, Object> map) throws Exception; 20 21 22 PaymentRecord queryPaymentStatusById(String orderNo); 23 24 }
6,创建服务接口PaymentServiceImpl;
1 @Service 2 public class PaymentServiceImpl implements PaymentService { 3 4 private static Logger LOGGER = LoggerFactory.getLogger(PaymentServiceImpl.class); 5 6 @Value("${spring.profiles.active}") 7 private String PROJECT_ENV; 8 9 @Value("${server.domain}") 10 private String SERVER_DOMAIN; 11 12 @Autowired 13 private PaymentRecordMapper paymentRecordMapper; 14 15 16 @Override 17 @Transactional(readOnly=false,rollbackFor={Exception.class}) 18 public Map<String, String> wxLtePayment(String openId, String orderNo, double money) throws Exception { 19 LOGGER.info("【小程序支付】 统一下单开始, 订单编号="+orderNo); 20 SortedMap<String, String> resultMap = new TreeMap<String, String>(); 21 //生成支付金额 22 double payAmount = PayUtil.getPayAmountByEnv(PROJECT_ENV, money); 23 //添加或更新支付记录 24 int flag = this.addOrUpdatePaymentRecord(orderNo, payAmount, PaymentConstants.PAY_TYPE_WX, PaymentConstants.PAY_TRADE_TYPE_LTE, false, null); 25 if(flag < 0){ 26 resultMap.put("returnCode", "FAIL"); 27 resultMap.put("returnMsg", "此订单已支付!"); 28 LOGGER.info("【小程序支付】 此订单已支付!"); 29 }else if(flag == 0){ 30 resultMap.put("returnCode", "FAIL"); 31 resultMap.put("returnMsg", "支付记录生成或更新失败!"); 32 LOGGER.info("【小程序支付】 支付记录生成或更新失败!"); 33 }else{ 34 Map<String,String> resMap = this.wxUnifieldOrder(orderNo, PayConfig.TRADE_TYPE_JSAPI, payAmount,false, openId); 35 if(PaymentConstants.SUCCESS.equals(resMap.get("return_code")) && PaymentConstants.SUCCESS.equals(resMap.get("result_code"))){ 36 resultMap.put("appId", PayConfig.WX_LTE_APP_ID); 37 resultMap.put("timeStamp", PayUtil.getCurrentTimeStamp()); 38 resultMap.put("nonceStr", PayUtil.makeUUID(32)); 39 resultMap.put("package", "prepay_id="+resMap.get("prepay_id")); 40 resultMap.put("signType", "MD5"); 41 resultMap.put("sign", PayUtil.createSign(resultMap,PayConfig.WX_LTE_KEY)); 42 resultMap.put("returnCode", "SUCCESS"); 43 resultMap.put("returnMsg", "OK"); 44 LOGGER.info("【小程序支付】统一下单成功,返回参数:"+resultMap); 45 }else{ 46 resultMap.put("returnCode", resMap.get("return_code")); 47 resultMap.put("returnMsg", resMap.get("return_msg")); 48 LOGGER.info("【小程序支付】统一下单失败,失败原因:"+resMap.get("return_msg")); 49 } 50 } 51 return resultMap; 52 } 53 54 @Override 55 @Transactional(readOnly=false,rollbackFor={Exception.class}) 56 public int wxNotify(Map<String,Object> map) throws Exception{ 57 Integer flag = 0; 58 //支付订单编号 59 String orderNo = (String)map.get("out_trade_no"); 60 //检验是否需要再次回调刷新数据 61 if(this.isNotifyAgain(orderNo)){ 62 PaymentRecordExample example = new PaymentRecordExample(); 63 example.createCriteria().andOrderNoEqualTo(orderNo); 64 example.setOrderByClause("id DESC"); 65 List<PaymentRecord> list = paymentRecordMapper.selectByExample(example); 66 if(list!=null && list.size()>0){ 67 //当前时间 68 Date currentTime = new Date(); 69 PaymentRecord record = list.get(0); 70 record.setTradeNo(String.valueOf(map.get("transaction_id"))); 71 record.setStatus(Boolean.TRUE); 72 record.setUpdateTime(currentTime); 73 //更新条件 74 PaymentRecordExample where = new PaymentRecordExample(); 75 where.createCriteria().andRecordIdEqualTo(record.getRecordId()).andStatusEqualTo(Boolean.FALSE); 76 flag = paymentRecordMapper.updateByExampleSelective(record,where); 77 LOGGER.info("【微信充值回调】 记录更新成功,订单值ID="+orderNo); 78 if(flag > 0){ 79 PaymentNotify paymentNotify = new PaymentNotify(); 80 paymentNotify.setRecordId(record.getRecordId()); 81 paymentNotify.setOrderNo(record.getOrderNo()); 82 paymentNotify.setTradeNo(record.getTradeNo()); 83 paymentNotify.setCreateTime(new Date()); 84 paymentNotify.setStatus(true); 85 if(paymentNotifyMapper.insert(paymentNotify) > 0){ 86 LOGGER.info("【微信支付回调】 提醒信息生成成功!"); 87 } 88 }else{ 89 PaymentNotify paymentNotify = new PaymentNotify(); 90 paymentNotify.setRecordId(record.getRecordId()); 91 paymentNotify.setOrderNo(record.getOrderNo()); 92 paymentNotify.setTradeNo(record.getTradeNo()); 93 paymentNotify.setCreateTime(new Date()); 94 paymentNotify.setStatus(false); 95 if(paymentNotifyMapper.insert(paymentNotify) > 0){ 96 LOGGER.info("【微信支付回调】 提醒信息生成成功!"); 97 } 98 } 99 LOGGER.info("【微信支付回调】 订单支付成功,订单号:"+orderNo); 100 } 101 } 102 return flag; 103 } 104 105 106 @Override 107 @Transactional(readOnly=true,rollbackFor={Exception.class}) 108 public PaymentRecord queryPaymentStatusByNo(String OrderNo){ 109 PaymentRecordExample example = new PaymentRecordExample(); 110 example.createCriteria().andOrderNoEqualTo(OrderNo); 111 example.setOrderByClause("id DESC"); 112 List<PaymentRecord> list = paymentRecordMapper.selectByExample(example); 113 if(list != null && list.size()>0 && list.get(0).getStatus()){ 114 return list.get(0); 115 } 116 return null; 117 } 118 /** 119 * <p>微信支付统一下单</p> 120 * 121 * @param orderNo 订单编号 122 * @param tradeType 支付类型 123 * @param payAmount 支付类型 124 * @param noLtePay 非小程序支付 125 * @return 126 * @throws Exception 127 */ 128 private Map<String,String> wxUnifieldOrder(String orderNo, String tradeType, double payAmount, boolean noLtePay, String openid) throws Exception{ 129 //封装参数 130 SortedMap<String,String> paramMap = new TreeMap<String,String>(); 131 String appId = noLtePay?PayConfig.WX_APP_ID:PayConfig.WX_LTE_APP_ID; 132 String mchId = noLtePay?PayConfig.WX_MCH_ID:PayConfig.WX_LTE_MCH_ID; 133 paramMap.put("appid", appId); 134 paramMap.put("mch_id", mchId); 135 paramMap.put("nonce_str", PayUtil.makeUUID(32)); 136 paramMap.put("body", PaymentConstants.COMPANY_NAME); 137 paramMap.put("out_trade_no", orderNo); 138 paramMap.put("total_fee", PayUtil.moneyToIntegerStr(payAmount)); 139 paramMap.put("spbill_create_ip", PayUtil.getLocalIp()); 140 paramMap.put("notify_url", this.getNotifyUrl(PaymentConstants.PAY_TYPE_WX)); 141 paramMap.put("trade_type", tradeType); 142 if (!noLtePay) { 143 paramMap.put("openid",openid); 144 } 145 String payKey = noLtePay?PayConfig.WX_KEY:PayConfig.WX_LTE_KEY; 146 paramMap.put("sign", PayUtil.createSign(paramMap,payKey)); 147 //转换为xml 148 String xmlData = PayUtil.mapToXml(paramMap); 149 //请求微信后台,获取预支付ID 150 String resXml = HttpUtils.postData(PayConfig.WX_PAY_UNIFIED_ORDER, xmlData); 151 LOGGER.info("【微信支付】 统一下单响应:\n"+resXml); 152 return PayUtil.xmlStrToMap(resXml); 153 } 154 155 /** 156 * <p>添加或更新支付记录</p> 157 * 158 * @param orderNo 159 * @param payAmount 160 * @param payType 161 * @return 162 * @throws Exception 163 */ 164 private int addOrUpdatePaymentRecord(String orderNo, double payAmount, int payType, String tradeType, boolean isPayment, String tradeNo) throws Exception{ 165 //添加或更新数据库的支付记录逻辑 166 // 写自己的实现代码 167 } 168 }
7,支付工具栏PayUtil
1 public class PayUtil { 2 static Logger log = LogManager.getLogger(PayUtil.class.getName()); 3 /** 4 * 获取当前机器的ip 5 * 6 * @return String 7 */ 8 public static String getLocalIp(){ 9 InetAddress ia=null; 10 String localip = null; 11 try { 12 ia=ia.getLocalHost(); 13 localip=ia.getHostAddress(); 14 } catch (Exception e) { 15 e.printStackTrace(); 16 } 17 return localip; 18 19 } 20 21 /** 22 * Map转换为 Xml 23 * 24 * @param map 25 * @return Xml 26 * @throws Exception 27 */ 28 public static String mapToXml(SortedMap<String, String> map) throws Exception { 29 DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); 30 //防止XXE攻击 31 documentBuilderFactory.setXIncludeAware(false); 32 documentBuilderFactory.setExpandEntityReferences(false); 33 DocumentBuilder documentBuilder= documentBuilderFactory.newDocumentBuilder(); 34 org.w3c.dom.Document document = documentBuilder.newDocument(); 35 org.w3c.dom.Element root = document.createElement("xml"); 36 document.appendChild(root); 37 for (String key: map.keySet()) { 38 String value = map.get(key); 39 if (value == null) { 40 value = ""; 41 } 42 value = value.trim(); 43 org.w3c.dom.Element filed = document.createElement(key); 44 filed.appendChild(document.createTextNode(value)); 45 root.appendChild(filed); 46 } 47 TransformerFactory tf = TransformerFactory.newInstance(); 48 Transformer transformer = tf.newTransformer(); 49 DOMSource source = new DOMSource(document); 50 transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); 51 transformer.setOutputProperty(OutputKeys.INDENT, "yes"); 52 StringWriter writer = new StringWriter(); 53 StreamResult result = new StreamResult(writer); 54 transformer.transform(source, result); 55 String output = writer.getBuffer().toString(); 56 try { 57 writer.close(); 58 } 59 catch (Exception ex) { 60 } 61 return output; 62 } 63 64 65 /** 66 * 创建签名Sign 67 * 68 * @param key 69 * @param parameters 70 * @return 71 */ 72 public static String createSign(SortedMap<String,String> parameters,String key){ 73 StringBuffer sb = new StringBuffer(); 74 Set es = parameters.entrySet(); 75 Iterator<?> it = es.iterator(); 76 while(it.hasNext()) { 77 Map.Entry entry = (Map.Entry)it.next(); 78 String k = (String)entry.getKey(); 79 if(entry.getValue() != null || !"".equals(entry.getValue())) { 80 String v = String.valueOf(entry.getValue()); 81 if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) { 82 sb.append(k + "=" + v + "&"); 83 } 84 } 85 } 86 sb.append("key=" + key); 87 String sign = MD5Util.MD5Encode(sb.toString(), "UTF-8").toUpperCase(); 88 return sign; 89 } 90 91 92 /** 93 * XML转换为Map 94 * 95 * @param strXML 96 * @return Map 97 * @throws Exception 98 */ 99 public static Map<String, Object> getMapFromXML(String strXML) throws Exception { 100 try { 101 Map<String, Object> data = new HashMap<String, Object>(); 102 DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); 103 //防止XXE攻击 104 documentBuilderFactory.setXIncludeAware(false); 105 documentBuilderFactory.setExpandEntityReferences(false); 106 DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); 107 InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8")); 108 org.w3c.dom.Document doc = documentBuilder.parse(stream); 109 doc.getDocumentElement().normalize(); 110 NodeList nodeList = doc.getDocumentElement().getChildNodes(); 111 for (int idx = 0; idx < nodeList.getLength(); ++idx) { 112 Node node = nodeList.item(idx); 113 if (node.getNodeType() == Node.ELEMENT_NODE) { 114 org.w3c.dom.Element element = (org.w3c.dom.Element) node; 115 data.put(element.getNodeName(), element.getTextContent()); 116 } 117 } 118 try { 119 stream.close(); 120 } catch (Exception ex) { 121 ex.printStackTrace(); 122 } 123 return data; 124 } catch (Exception ex) { 125 throw ex; 126 } 127 } 128 129 /** 130 * 生成随机数 131 * 132 * @return 133 */ 134 public static String makeUUID(int len) { 135 return UUID.randomUUID().toString().replaceAll("-", "").substring(0, len); 136 } 137 138 /** 139 * 获取当前的Timestamp 140 * 141 * @return 142 */ 143 public static String getCurrentTimeStamp() { 144 return Long.toString(System.currentTimeMillis()/1000); 145 } 146 147 /** 148 * 获取当前的时间 149 * @return 150 */ 151 public static long getCurrentTimestampMs() { 152 return System.currentTimeMillis(); 153 } 154 155 /** 156 * 生成订单号 157 * 158 * @return 159 */ 160 public static String generateOrderNo() { 161 SimpleDateFormat sdf = new SimpleDateFormat("yyMMdd"); 162 return sdf.format(new Date())+makeUUID(16); 163 } 164 165 /** 166 * 获取当前工程url 167 * 168 * @param request 169 * @return 170 */ 171 public static String getCurrentUrl(HttpServletRequest request){ 172 return request.getScheme() +"://" + request.getServerName() + ":" +request.getServerPort() +request.getContextPath(); 173 } 174 175 /** 176 * Xml字符串转换为Map 177 * 178 * @param xmlStr 179 * @return 180 */ 181 public static Map<String,String> xmlStrToMap(String xmlStr){ 182 Map<String,String> map = new HashMap<String,String>(); 183 Document doc; 184 try { 185 doc = DocumentHelper.parseText(xmlStr); 186 Element root = doc.getRootElement(); 187 List children = root.elements(); 188 if(children != null && children.size() > 0) { 189 for(int i = 0; i < children.size(); i++) { 190 Element child = (Element)children.get(i); 191 map.put(child.getName(), child.getTextTrim()); 192 } 193 } 194 } catch (DocumentException e) { 195 e.printStackTrace(); 196 } 197 return map; 198 } 199 200 public static String testXml(){ 201 return "<xml><appid><![CDATA[wx2421b1c4370ec43b]]></appid><attach><![CDATA[支付测试]]></attach><bank_type><![CDATA[CFT]]></bank_type><fee_type><![CDATA[CNY]]></fee_type> <is_subscribe><![CDATA[Y]]></is_subscribe><mch_id><![CDATA[10000100]]></mch_id><nonce_str><![CDATA[5d2b6c2a8db53831f7eda20af46e531c]]></nonce_str><openid><![CDATA[oUpF8uMEb4qRXf22hE3X68TekukE]]></openid> <out_trade_no><![CDATA[WeChat Pay test]]></out_trade_no><result_code><![CDATA[SUCCESS]]></result_code> <return_code><![CDATA[SUCCESS]]></return_code><sign><![CDATA[B552ED6B279343CB493C5DD0D78AB241]]></sign><sub_mch_id><![CDATA[10000100]]></sub_mch_id> <time_end><![CDATA[20140903131540]]></time_end><total_fee>1</total_fee><trade_type><![CDATA[JSAPI]]></trade_type><transaction_id><![CDATA[1004400740201409030005092168]]></transaction_id></xml>"; 202 } 203 204 205 public static String getSceneInfo(String wapUrl,String name){ 206 Map<String,Map<String,String>> map = new HashMap<String, Map<String,String>>(); 207 if(!StringUtils.isEmpty(wapUrl) && !StringUtils.isEmpty(name)){ 208 /*{"h5_info": {"type":"Wap","wap_url": "https://pay.qq.com","wap_name": "腾讯充值"}}*/ 209 Map<String,String> childmap = new TreeMap<String, String>(); 210 childmap.put("type", "Wap"); 211 childmap.put("wap_url",wapUrl); 212 childmap.put("wap_name", name); 213 map.put("h5_info", childmap); 214 return JSON.toJSONString(map); 215 } 216 return null; 217 } 218 219 220 /** 221 * 转换金额型到整型 222 * @param money 223 * @return 224 */ 225 public static String moneyToIntegerStr(Double money){ 226 BigDecimal decimal = new BigDecimal(money); 227 int amount = decimal.multiply(new BigDecimal(100)) 228 .setScale(0, BigDecimal.ROUND_HALF_UP).intValue(); 229 return String.valueOf(amount); 230 } 231 232 /** 233 * 除去数组中的空值和签名参数 234 * @param sArray 签名参数组 235 * @return 去掉空值与签名参数后的新签名参数组 236 */ 237 public static Map<String, String> paraFilter(Map<String, String> sArray) { 238 239 Map<String, String> result = new HashMap<String, String>(); 240 241 if (sArray == null || sArray.size() <= 0) { 242 return result; 243 } 244 245 for (String key : sArray.keySet()) { 246 String value = sArray.get(key); 247 if (value == null || value.equals("") || key.equalsIgnoreCase("sign") 248 || key.equalsIgnoreCase("sign_type")) { 249 continue; 250 } 251 result.put(key, value); 252 } 253 254 return result; 255 } 256 257 /** 258 * 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串 259 * @param params 需要排序并参与字符拼接的参数组 260 * @return 拼接后字符串 261 */ 262 public static String createLinkString(Map<String, String> params) { 263 List<String> keys = new ArrayList<String>(params.keySet()); 264 Collections.sort(keys); 265 String prestr = ""; 266 for (int i = 0; i < keys.size(); i++) { 267 String key = keys.get(i); 268 String value = params.get(key); 269 if (i == keys.size() - 1) {//拼接时,不包括最后一个&字符 270 prestr = prestr + key + "=" + value; 271 } else { 272 prestr = prestr + key + "=" + value + "&"; 273 } 274 } 275 return prestr; 276 } 277 278 /** 279 * 根据不同环境生成支付金额 280 * 281 * @param env 282 * @param money 283 * @param money 284 * @return 285 */ 286 public static double getPayAmountByEnv(String env, Double money){ 287 double pay_money = 0.01; 288 //测试环境 289 if("dev".equals(env)){ 290 return 0.01; 291 }else{ 292 //生成环境 293 return money; 294 } 295 } 296 }
8,Controller接口编写
@RestController @RequestMapping(value = "/api/payment/") public class PaymentController { private static Logger logger = LoggerFactory.getLogger(PaymentController.class); @Value("${server.domain}") private String serverDomain; @Autowired private PaymentService paymentService; /** * <p>微信小程序支付接口</p> * 必传参数:openId, orderNo, payAmount * * @param request * @param paymentBo * @return * @throws Exception */ @ResponseBody @RequestMapping(value="ltePay", method=RequestMethod.POST, produces = {"application/json;charset=UTF-8"}) public ResultData toPay(HttpServletRequest request, @RequestBody PaymentBo paymentBo) throws Exception { if (paymentBo == null || paymentBo.getOpenId() == null || paymentBo.getOrderNo() == null || paymentBo.getPayAmount() == null) { throw new ParamException(); } else if (paymentBo.getPayAmount() < 0.01) { return ResultData.fail("订单金额有误,请确认!"); }else{ logger.info("【小程序支付服务】请求订单编号: ["+paymentBo.getOrderNo()+"]"); Map<String, String> resMap = paymentService.wxLtePayment(paymentBo.getOpenId(), paymentBo.getOrderNo(), paymentBo.getPayAmount()); if("SUCCESS".equals(resMap.get("returnCode")) && "OK".equals(resMap.get("returnMsg"))){ //统一下单成功 resMap.remove("returnCode"); resMap.remove("returnMsg"); logger.info("【小程序支付服务】支付下单成功!"); return ResultData.ok(resMap); }else{ logger.info("【小程序支付服务】支付下单失败!原因:"+resMap.get("returnMsg")); return ResultData.fail(resMap.get("returnMsg")); } } } /** * 微信支付完成回调Api * * @param request * @param response * @throws Exception */ @RequestMapping(value="wxNotify") public void wxNotify(HttpServletRequest request, HttpServletResponse response) throws Exception { InputStream inputStream = request.getInputStream(); //获取请求输入流 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len = 0; while ((len=inputStream.read(buffer))!=-1){ outputStream.write(buffer,0,len); } outputStream.close(); inputStream.close(); Map<String,Object> map = BeanToMap.getMapFromXML(new String(outputStream.toByteArray(),"utf-8")); logger.info("【微信支付回调】 回调数据: \n"+map); String resXml = ""; String returnCode = (String) map.get("return_code"); if ("SUCCESS".equalsIgnoreCase(returnCode)) { String returnmsg = (String) map.get("result_code"); if("SUCCESS".equals(returnmsg)){ //支付成功 resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>"+"</xml>"; }else{ resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]></return_msg>" + "</xml>"; logger.info("支付失败:"+resXml); } }else{ resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]></return_msg>" + "</xml>"; logger.info("【订单支付失败】"); } logger.info("【微信支付回调响应】 响应内容:\n"+resXml); //做出响应 response.getWriter().print(resXml); } /** * <p>查询订单支付是否完成</p> * * @param orderNo * @return * @throws Exception */ @ResponseBody @GetMapping(value="queryPayStatus") public ResultData queryPayStatus(String orderNo) throws Exception { if (StringUtils.isEmpty(orderNo)) { throw new ParamException(); } PaymentRecord record = paymentService.queryPaymentStatusById(orderNo); if(record != null){ Map<String,Object> map = new HashMap<String,Object>(); map.put("companyName", PaymentConstants.COMPANY_NAME); map.put("OrderNo", record.getOrderNo()); map.put("payAmount",record.getPayAmont()); map.put("payMethod",PaymentConstants.getPayMethod(String.valueOf(record.getPayMethod()))); map.put("tradeNo",record.getTradeNo()); map.put("payTime",record.getUpdateTime()); map.put("payStatus",record.getStatus()); return ResultData.ok(map); }else{ return ResultData.fail("未支付!"); } } }
9,Controller接收参数Bo
public class PaymentBo { /**支付类型:1-支付宝;2-微信*/ private Integer payType; /**客户openId*/ private String openId; /**订单编号*/ private String orderNo; /**支付金额*/ private Double payAmount; public Integer getPayType() { return payType; } public String getOpenId() { return openId; } public String getOrderNo() { return orderNo; } public Double getPayAmount() { return payAmount; } public void setPayType(Integer payType) { this.payType = payType; } public void setOpenId(String openId) { this.openId = openId; } public void setOrderNo(String orderNo) { this.orderNo = orderNo; } public void setPayAmount(Double payAmount) { this.payAmount = payAmount; } }
10,Controller接口返回类封装:
1 public class ResultData<T> implements Serializable { 2 3 private static final long serialVersionUID = 1L; 4 5 private Integer code; 6 private String message; 7 private T data; 8 9 public Integer getCode() { 10 return code; 11 } 12 public String getMessage() { 13 return message; 14 } 15 public T getData() { 16 return data; 17 } 18 public void setCode(Integer code) { 19 this.code = code; 20 } 21 public void setMessage(String message) { 22 this.message = message; 23 } 24 public void setData(T data) { 25 this.data = data; 26 } 27 28 public ResultData() {} 29 30 public ResultData(ResultCodeEnums status, T data) { 31 this(status.getCode(), status.getMessage(), data); 32 } 33 34 public ResultData(Integer code, String message, T data) { 35 this.code = code; 36 this.message = message; 37 this.data = data; 38 } 39 40 public static <T> ResultData<T> ok(String message){ 41 return new ResultData<T>(ResultCodeEnums.SUCCESS, null); 42 } 43 44 public static <T> ResultData<T> ok(T data){ 45 return new ResultData<T>(ResultCodeEnums.SUCCESS,data); 46 } 47 48 public static <T> ResultData<T> ok(String message,T data){ 49 return new ResultData<T>(ResultCodeEnums.SUCCESS.getCode(), message, data); 50 } 51 52 public static <T> ResultData<T> fail(String message){ 53 return fail(null); 54 } 55 56 public static <T> ResultData<T> fail(T data){ 57 return new ResultData<T>(ResultCodeEnums.FAIL, data); 58 } 59 60 public static <T> ResultData<T> fail(String message,T data){ 61 return new ResultData<T>(ResultCodeEnums.FAIL.getCode(), message, data); 62 } 63 }
二,小程序端(获取统一下单返回参数发起支付)
在小程序端,发起支付请求到,Java后台的统一下单接口返回prepay_id等参数,然后封装调起微信的js方法:wx.requestPayment(OBJECT),具体参考文档:官方文档
测试一把:
本篇代码是我们运行两年电商项目的源码,目前做的已经很完整健壮,由于依赖和工具类等很多,不能全部贴出来,还请谅解。如果需要,请扫描添加公众号,联系本人获取其他代码或支付项目。