支付接口——WeChat / Alipay
1.微信支付
a.微信支付官方文档地址:https://pay.weixin.qq.com/wiki/doc/api/index.html
b.在官网下载SDK
c.使用IDEA打开 java_sdk_v3.0.9,修改 WXPayConfig 类,将访问修饰符都改为 public
package com.github.wxpay.sdk; import java.io.InputStream; public abstract class WXPayConfig { /** * 获取 App ID * * @return App ID */ public abstract String getAppID(); /** * 获取 Mch ID * * @return Mch ID */ public abstract String getMchID(); /** * 获取 API 密钥 * * @return API密钥 */ public abstract String getKey(); /** * 获取商户证书内容 * * @return 商户证书内容 */ public abstract InputStream getCertStream(); /** * HTTP(S) 连接超时时间,单位毫秒 * * @return */ public int getHttpConnectTimeoutMs() { return 6*1000; } /** * HTTP(S) 读数据超时时间,单位毫秒 * * @return */ public int getHttpReadTimeoutMs() { return 8*1000; } /** * 获取WXPayDomain, 用于多域名容灾自动切换 * @return */ public abstract IWXPayDomain getWXPayDomain(); /** * 是否自动上报。 * 若要关闭自动上报,子类中实现该函数返回 false 即可。 * * @return */ public boolean shouldAutoReport() { return true; } /** * 进行健康上报的线程的数量 * * @return */ public int getReportWorkerNum() { return 6; } /** * 健康上报缓存消息的最大数量。会有线程去独立上报 * 粗略计算:加入一条消息200B,10000消息占用空间 2000 KB,约为2MB,可以接受 * * @return */ public int getReportQueueMaxSize() { return 10000; } /** * 批量上报,一次最多上报多个数据 * * @return */ public int getReportBatchSize() { return 10; } }
d.使用 maven 将项目 install 到本地仓库
e.pom文件中引入导入的SDK依赖
<dependency> <groupId>com.github.wxpay</groupId> <artifactId>wxpay-sdk</artifactId> <version>3.0.9</version> </dependency>
f.新建 MyConfig 类继承 WXPayConfig
public class MyConfig extends WXPayConfig{ //公众账号ID public static final String APP_ID = ""; //商户号 public static final String MCH_ID = ""; //API密钥 public static final String KEY = ""; //异步支付回调地址(外网) public static final String NOTIFY_URL = ""; //异步退款回调地址(外网) public static final String REFUND_NOTIFY_URL = ""; //--------------------------------------- private byte[] certData; public MyConfig() throws Exception { String certPath = "/path/to/apiclient_cert.p12"; File file = new File(certPath); InputStream certStream = new FileInputStream(file); this.certData = new byte[(int) file.length()]; certStream.read(this.certData); certStream.close(); } public String getAppID() { return APP_ID; } public String getMchID() { return MCH_ID; } public String getKey() { return KEY; } public InputStream getCertStream() { ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData); return certBis; } public IWXPayDomain getWXPayDomain() { // 这个方法需要这样实现, 否则无法正常初始化WXPay IWXPayDomain iwxPayDomain = new IWXPayDomain() { public void report(String domain, long elapsedTimeMillis, Exception ex) { } public DomainInfo getDomain(WXPayConfig config) { return new IWXPayDomain.DomainInfo(WXPayConstants.DOMAIN_API, true); } }; return iwxPayDomain; } public int getHttpConnectTimeoutMs() { return 8000; } public int getHttpReadTimeoutMs() { return 10000; } }
g.统一下单、查询订单、退款
public class WechatUtil { /** * 统一下单(预支付) * @param outTradeNo 外部订单号 * @param title 标题 * @param totalFee 总金额 * @param spbillCreateIp 客户端IP * @return * @throws Exception */ public static Map<String, String> prepay(String outTradeNo, String title, int totalFee, String spbillCreateIp) throws Exception { MyConfig config = new MyConfig(); WXPay wxpay = new WXPay(config); Map<String, String> data = new HashMap<String, String>(); data.put("body", title); data.put("out_trade_no", outTradeNo); data.put("fee_type", "CNY"); data.put("total_fee", String.valueOf(totalFee)); data.put("spbill_create_ip", spbillCreateIp); data.put("notify_url", MyConfig.NOTIFY_URL); data.put("trade_type", "NATIVE"); // 此处指定为扫码支付 data.put("product_id", "12"); //扫码支付时必传参数 Map<String, String> result = new HashMap<>(); try { Map<String, String> resp = wxpay.unifiedOrder(data); System.out.println(resp); if("SUCCESS".equals(resp.get("return_code"))){ if("SUCCESS".equals(resp.get("result_code"))){ String prepay_id = resp.get("prepay_id"); String code_url = resp.get("code_url"); HashMap<String, String> signMap = new HashMap<String, String>(); signMap.put("appid", MyConfig.APP_ID); signMap.put("partnerid", MyConfig.MCH_ID); signMap.put("prepayid", prepay_id); signMap.put("package", "Sign=WXPay"); signMap.put("noncestr", WXPayUtil.generateNonceStr()); signMap.put("timestamp", String.valueOf(System.currentTimeMillis()/1000)); String sign = WXPayUtil.generateSignature(signMap, MyConfig.KEY); result.put("prepay_id", prepay_id); result.put("code_url", code_url); result.put("sign", sign); result.putAll(signMap); return result; }else{ System.out.println(resp.get("err_code_des")); } }else{ System.out.println(resp.get("return_msg")); } } catch (Exception e) { e.printStackTrace(); } return result; } /** * 查询订单 * @param outTradeNo 外部订单号 * @throws Exception */ public static void tradeQuery(String outTradeNo) throws Exception { MyConfig config = new MyConfig(); WXPay wxpay = new WXPay(config); Map<String, String> data = new HashMap<String, String>(); data.put("out_trade_no", outTradeNo); try { Map<String, String> resp = wxpay.orderQuery(data); System.out.println(resp); if("SUCCESS".equals(resp.get("return_code"))){ if("SUCCESS".equals(resp.get("result_code"))){ //TODO 根据response中的结果继续业务逻辑处理 }else{ System.out.println(resp.get("err_code_des")); } }else{ System.out.println(resp.get("return_msg")); } } catch (Exception e) { e.printStackTrace(); } } /** * 退款 * @param outTradeNo 外部订单号 * @param outRefundNo 外部退款订单号 * @param totalFee 总金额 * @param refundFee 退款金额 * @throws Exception */ public static void refund(String outTradeNo, String outRefundNo, int totalFee, int refundFee) throws Exception { MyConfig config = new MyConfig(); WXPay wxpay = new WXPay(config); Map<String, String> data = new HashMap<String, String>(); data.put("out_trade_no", outTradeNo); data.put("out_trade_no", outTradeNo); data.put("out_refund_no", outRefundNo); data.put("total_fee", String.valueOf(totalFee)); data.put("refund_fee", String.valueOf(refundFee)); data.put("notify_url", MyConfig.REFUND_NOTIFY_URL); try { Map<String, String> resp = wxpay.refund(data); System.out.println(resp); if("SUCCESS".equals(resp.get("return_code"))){ if("SUCCESS".equals(resp.get("result_code"))){ //TODO 根据response中的结果继续业务逻辑处理 }else{ System.out.println(resp.get("err_code_des")); } }else{ System.out.println(resp.get("return_msg")); } } catch (Exception e) { e.printStackTrace(); } } }
h.支付/退款 回调
@RestController public class WechatController { @RequestMapping("/payment/wechatResult") public String wechatResult(HttpServletRequest request){ try{ InputStream is = request.getInputStream(); //将InputStream转换成String BufferedReader reader = new BufferedReader(new InputStreamReader(is)); StringBuilder sb = new StringBuilder(); String line = null; try { while ((line = reader.readLine()) != null) { sb.append(line); } } catch (IOException e) { e.printStackTrace(); } finally { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } String resXml=sb.toString(); System.out.println(resXml); //------------------------------------------------------------------- MyConfig config = new MyConfig(); WXPay wxpay = new WXPay(config); Map<String, String> notifyMap = WXPayUtil.xmlToMap(resXml); // 转换成map if (wxpay.isPayResultNotifySignatureValid(notifyMap)) { // 签名正确 // 进行处理。 // 注意特殊情况:订单已经退款,但收到了支付结果成功的通知,不应把商户侧订单状态从退款改成支付成功 String return_code = notifyMap.get("return_code");//状态 String out_trade_no = notifyMap.get("out_trade_no");//订单号 if (out_trade_no == null) { return ""; } //TODO 根据notifyMap中的结果继续业务逻辑处理 return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml> "; } }catch (Exception e){ e.printStackTrace(); } return ""; } @RequestMapping("/payment/wechatRefundResult") public String wechatRefundResult(HttpServletRequest request){ try { BufferedReader reader = request.getReader(); StringBuilder sb = new StringBuilder(); String line = null; try { while ((line = reader.readLine()) != null) { sb.append(line); } } catch (IOException e) { e.printStackTrace(); } finally { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } String resXml=sb.toString(); System.out.println(resXml); //--------------------------------------------------- Map<String, String> notifyMap = WXPayUtil.xmlToMap(resXml); if ("SUCCESS".equals(notifyMap.get("return_code"))) { String req_info = notifyMap.get("req_info"); /** * 解密方式 * 解密步骤如下: * (1)对加密串A做base64解码,得到加密串B * (2)对商户key做md5,得到32位小写key* ( key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置 ) * (3)用key*对加密串B做AES-256-ECB解密(PKCS7Padding) */ byte[] byteArray = (new BASE64Decoder()).decodeBuffer(req_info); String key = DigestUtils.md5Hex(MyConfig.KEY.getBytes("UTF-8")); SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), "AES"); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, secretKey); String resultStr = new String(cipher.doFinal(byteArray)); Map<String, String> aesMap = WXPayUtil.xmlToMap(resultStr); //TODO 根据aesMap中的结果继续业务逻辑处理 return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml> "; } }catch (Exception e){ e.printStackTrace(); } return ""; } }
2.支付宝支付
a.官方文档地址:https://docs.open.alipay.com/204/105297/
b.在pom文件中引入SDK依赖
<dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-sdk-java</artifactId> <version>3.7.89.ALL</version> </dependency>
c.统一下单、交易查询、交易退款
public class AlipayUtil { //HTTPS请求地址 public static final String URL = "https://openapi.alipay.com/gateway.do"; //异步回调地址(外网) public static final String NOTIFY_URL = ""; //应用ID public static final String APP_ID = ""; //应用私钥 public static final String APP_PRIVATE_KEY = ""; //支付宝公钥 public static final String ALIPAY_PUBLIC_KEY = ""; //编码格式 public static final String CHARSET = "utf-8"; /** * 生成APP支付订单信息 * @param outTradeNo 外部订单号 * @param title 标题 * @param totalAmount 总金额 * @return */ public static String getOrderStr(String outTradeNo,String title,String totalAmount){ //实例化客户端 AlipayClient alipayClient = new DefaultAlipayClient(URL, APP_ID, APP_PRIVATE_KEY, "json", CHARSET, ALIPAY_PUBLIC_KEY, "RSA2"); //实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest(); //SDK已经封装掉了公共参数,这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。 AlipayTradeAppPayModel model = new AlipayTradeAppPayModel(); model.setSubject(title); model.setOutTradeNo(outTradeNo); model.setTotalAmount(totalAmount); model.setProductCode("QUICK_MSECURITY_PAY"); request.setBizModel(model); request.setNotifyUrl(NOTIFY_URL); try { //这里和普通的接口调用不同,使用的是sdkExecute AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request); if(response.isSuccess()){ String orderStr = response.getBody();//就是orderString 可以直接给客户端请求,无需再做处理。 System.out.println(orderStr); return orderStr; }else{ System.out.println(response.getSubCode()); } } catch (AlipayApiException e) { e.printStackTrace(); } return null; } /** * 交易查询 * @param outTradeNo 外部订单号 */ public static void tradeQuery(String outTradeNo){ AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", APP_ID, APP_PRIVATE_KEY, "json", CHARSET, ALIPAY_PUBLIC_KEY, "RSA2"); //获得初始化的AlipayClient AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();//创建API对应的request类 request.setBizContent("{" + " \"out_trade_no\":\"" + outTradeNo + "\"," + " }");//设置业务参数 try { AlipayTradeQueryResponse response = alipayClient.execute(request);//通过alipayClient调用API,获得对应的response类 if(response.isSuccess()){ System.out.print(response.getBody()); //TODO 根据response中的结果继续业务逻辑处理 }else{ System.out.println(response.getSubCode()); } } catch (AlipayApiException e) { e.printStackTrace(); } } /** * 交易退款 * @param outTradeNo 外部订单号 * @param refundAmount 退款金额 */ public static void tradeRefund(String outTradeNo, String refundAmount){ AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", APP_ID, APP_PRIVATE_KEY, "json", CHARSET, ALIPAY_PUBLIC_KEY, "RSA2"); //获得初始化的AlipayClient AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();//创建API对应的request类 request.setBizContent("{" + " \"out_trade_no\":\"" + outTradeNo + "\"," + " \"refund_amount\":\"" + refundAmount + "\"" + " }");//设置业务参数 try { AlipayTradeRefundResponse response = alipayClient.execute(request);//通过alipayClient调用API,获得对应的response类 if(response.isSuccess()){ System.out.print(response.getBody()); //TODO 根据response中的结果继续业务逻辑处理 }else{ System.out.println(response.getSubCode()); } } catch (AlipayApiException e) { e.printStackTrace(); } } }
d.支付回调
@RestController public class AlipayController { @RequestMapping("/payment/alipayResult") public String alipayResult(HttpServletRequest request){ //获取支付宝POST过来反馈信息 Map<String,String> params = new HashMap<String,String>(); Map requestParams = request.getParameterMap(); for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext();) { String name = (String) iter.next(); String[] values = (String[]) requestParams.get(name); String valueStr = ""; for (int i = 0; i < values.length; i++) { valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ","; } //乱码解决,这段代码在出现乱码时使用。 //valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8"); params.put(name, valueStr); } //切记alipaypublickey是支付宝的公钥,请去open.alipay.com对应应用下查看。 //boolean AlipaySignature.rsaCheckV1(Map<String, String> params, String publicKey, String charset, String sign_type) try { boolean flag = AlipaySignature.rsaCheckV1(params, AlipayUtil.ALIPAY_PUBLIC_KEY, AlipayUtil.CHARSET,"RSA2"); if(flag){ //TODO 根据params中的结果继续业务逻辑处理 return "success"; }else{ return "failure"; } } catch (AlipayApiException e) { e.printStackTrace(); } return "failure"; } }