说实话 对于初学者来说支付来说确实要花很大的精力去看文档
提前说明一下 支付宝和微信手续费 0.6% 按每比计算 退款想要原金额退回 0.6%由商家承担
这里我只分享后端支付这一块,好了直接上代码
APPID=**** PARTNER=**** APP_PUBLIC_KEY=*** APP_PRIVATE_KEY=*** APP_PRODUCT_CODE=QUICK_MSECURITY_PAY APP_METHOD=alipay.trade.app.pay APP_FORMAT=json APP_CHARSET=utf-8
#编码格式 APP_SIGN_TYPE=RSA2
#版本号 APP_VERSION=1.0
#异步通知 APP_NOTIFY_URL=http:
#辅助api统一请求 APP_ALIPAY_URL=https://openapi.alipay.com/gateway.do
这个是支付宝配置文件
支付宝工具类
package com.yuanxinxinxi.yuanxinbuluo.alipay.util; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.security.KeyFactory; import java.security.PrivateKey; import java.security.spec.PKCS8EncodedKeySpec; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import com.alipay.api.AlipayApiException; import com.alipay.api.internal.util.AlipaySignature; import com.yuanxinxinxi.yuanxinbuluo.alipay.dto.AlipayConfig; /** * 支付宝工具类*/ public class AlipayUtil { private static final String ALGORITHM = "RSA"; private static final String SIGN_SHA256RSA_ALGORITHMS = "SHA256WithRSA"; private static final String DEFAULT_CHARSET = "UTF-8"; /** * 支付宝消息验证地址 */ private static final String HTTPS_VERIFY_URL = "https://mapi.alipay.com/gateway.do?service=notify_verify&"; /** * 获取远程服务器ATN结果,验证返回URL * @param notify_id 通知校验ID * @return 服务器ATN结果 * 验证结果集: * invalid命令参数不对 出现这个错误,请检测返回处理中partner和key是否为空 * true 返回正确信息 * false 请检查防火墙或者是服务器阻止端口问题以及验证时间是否超过一分钟 */ public static String verifyResponse(String notify_id) { //获取远程服务器ATN结果,验证是否是支付宝服务器发来的请求 String partner = AlipayConfig.PARTNER; String veryfy_url = HTTPS_VERIFY_URL + "partner=" + partner + "¬ify_id=" + notify_id; return checkUrl(veryfy_url); } /** * 获取远程服务器ATN结果 * @param urlvalue 指定URL路径地址 * @return 服务器ATN结果 * 验证结果集: * invalid命令参数不对 出现这个错误,请检测返回处理中partner和key是否为空 * true 返回正确信息 * false 请检查防火墙或者是服务器阻止端口问题以及验证时间是否超过一分钟 */ public static String checkUrl(String urlvalue) { String inputLine = ""; try { URL url = new URL(urlvalue); HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection .getInputStream())); inputLine = in.readLine().toString(); } catch (Exception e) { e.printStackTrace(); inputLine = ""; } return inputLine; } /** * 根据时间转换成yyyy-MM-dd HH:mm:ss格式的时间 * @param time * @return */ public static String getDateTime(Date time){ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return sdf.format(time); } /** * 对支付参数信息进行签名 * * @param map * 待签名授权信息 * * @return */ public static String getSign(Map<String, Object> map, String rsaKey) { List<String> keys = new ArrayList<String>(map.keySet()); // key排序 Collections.sort(keys); StringBuilder authInfo = new StringBuilder(); for (int i = 0; i < keys.size() - 1; i++) { String key = keys.get(i); String value = String.valueOf(map.get(key)); authInfo.append(buildKeyValue(key, value, false)); authInfo.append("&"); } String tailKey = keys.get(keys.size() - 1); String tailValue = String.valueOf(map.get(tailKey)); authInfo.append(buildKeyValue(tailKey, tailValue, false)); String oriSign = sign(authInfo.toString(), rsaKey); String encodedSign = ""; try { encodedSign = URLEncoder.encode(oriSign, DEFAULT_CHARSET); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return "sign="+encodedSign; } /** * 对支付宝参数编码 * @param * @return */ public static String getReqestMap(Map<String,Object> map){ List<String> keys = new ArrayList<String>(map.keySet()); Collections.sort(keys); StringBuffer buffer = new StringBuffer(); for (int i = 0; i < keys.size(); i++) { String key = keys.get(i); String value = String.valueOf(map.get(key)); buffer.append(buildKeyValue(key, value, true)); buffer.append("&"); } return buffer.toString(); } /** * 拼接键值对 * * @param key * @param value * @param isEncode * @return */ private static String buildKeyValue(String key, String value, boolean isEncode) { StringBuilder sb = new StringBuilder(); sb.append(key); sb.append("="); if (isEncode) { try { sb.append(URLEncoder.encode(value, DEFAULT_CHARSET)); } catch (UnsupportedEncodingException e) { sb.append(value); } } else { sb.append(value); } return sb.toString(); } /** * 对支付参数信息进行签名配合getSign方法 */ public static String sign(String content, String privateKey) { try { PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec( Base64.decode(privateKey)); KeyFactory keyf = KeyFactory.getInstance(ALGORITHM); PrivateKey priKey = keyf.generatePrivate(priPKCS8); java.security.Signature signature = java.security.Signature .getInstance(SIGN_SHA256RSA_ALGORITHMS); signature.initSign(priKey); signature.update(content.getBytes(DEFAULT_CHARSET)); byte[] signed = signature.sign(); return Base64.encode(signed); } catch (Exception e) { e.printStackTrace(); } return null; } /** * 把空值去掉 * @param map * @return */ public static Map<String,Object> isVlaueNull(Map<String,Object> map){ Iterator<Map.Entry<String,Object>> iterator = map.entrySet().iterator(); while(iterator.hasNext()) { Map.Entry<String,Object> entry = iterator.next(); String key = entry.getKey(); if(map.get(key)==null){ iterator.remove(); } } return map; } /** * 验证消息是否是支付宝发出的合法消息 * * @param params * 通知返回来的参数数组 * @return 验证结果 * @throws AlipayApiException */ public static boolean verify(Map<String, String> params) throws AlipayApiException { // 判断responsetTxt是否为true,isSign是否为true // responsetTxt的结果不是true,与服务器设置问题、合作身份者ID、notify_id一分钟失效有关 // isSign不是true,与安全校验码、请求时的参数格式(如:带自定义参数等)、编码格式有关 String responseTxt = "false"; if (params.get("notify_id") != null) { String notify_id = params.get("notify_id"); responseTxt = verifyResponse(notify_id); } String sign = ""; if (params.get("sign") != null) { sign = params.get("sign"); } boolean isSign = getSignVeryfy(params, sign); if (isSign && responseTxt.equals("true")) { return true; } else { return false; } } /** * 根据反馈回来的信息,生成签名结果 * * @param Params * 通知返回来的参数数组 * @param sign * 比对的签名结果 * @return 生成的签名结果 * @throws AlipayApiException */ private static boolean getSignVeryfy(Map<String, String> Params, String sign) throws AlipayApiException { // 过滤空值、sign与sign_type参数 Map<String, String> sParaNew = paraFilter(Params); // 获取待签名字符串 String preSignStr = createLinkString(sParaNew); // 获得签名验证结果 boolean isSign = false; isSign = AlipaySignature.rsaCheck(preSignStr, sign, AlipayConfig.PUBLIC_KEY, DEFAULT_CHARSET, AlipayConfig.APP_SIGN_TYPE); return isSign; } /** * 除去数组中的空值和签名参数 * * @param sArray * 签名参数组 * @return 去掉空值与签名参数后的新签名参数组 */ public static Map<String, String> paraFilter(Map<String, String> sArray) { Map<String, String> result = new HashMap<String, String>(); if (sArray == null || sArray.size() <= 0) { return result; } for (String key : sArray.keySet()) { String value = sArray.get(key); if (value == null || value.equals("") || key.equalsIgnoreCase("sign") || key.equalsIgnoreCase("sign_type")) { continue; } result.put(key, value); } return result; } /** * 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串 * * @param params * 需要排序并参与字符拼接的参数组 * @return 拼接后字符串 */ public static String createLinkString(Map<String, String> params) { List<String> keys = new ArrayList<String>(params.keySet()); Collections.sort(keys); String prestr = ""; for (int i = 0; i < keys.size(); i++) { String key = keys.get(i); String value = params.get(key); if (i == keys.size() - 1) {// 拼接时,不包括最后一个&字符 prestr = prestr + key + "=" + value; } else { prestr = prestr + key + "=" + value + "&"; } } return prestr; } }
/*** * 对支付宝参数封装 * @throws UnsupportedEncodingException * */ public String encapData(AlipayDto alipayDto) throws UnsupportedEncodingException { Map<String,Object> map = new HashMap<String, Object>(); /** * biz_content数据 */ Map<String,Object> biz_map = new HashMap<String, Object>(); /** * 业务参数 */ biz_map.put("body", alipayDto.getBody()); biz_map.put("subject", alipayDto.getSubject()); biz_map.put("out_trade_no", alipayDto.getOut_trade_no()); biz_map.put("timeout_express", alipayDto.getTimeout_express()); biz_map.put("total_amount", alipayDto.getTotal_amount()); biz_map.put("product_code", AlipayConfig.APP_PRODUCT_CODE); biz_map.put("passback_params", alipayDto.getPassback_params()); JSONObject bizcontentJson= JSONObject.fromObject(AlipayUtil.isVlaueNull(biz_map)); /** * 公共参数 */ map.put("app_id", alipayDto.getApp_id()); map.put("method", AlipayConfig.APP_METHOD); map.put("format", AlipayConfig.APP_FORMAT); map.put("charset", AlipayConfig.APP_CHARSET); map.put("sign_type", AlipayConfig.APP_SIGN_TYPE); map.put("timestamp", AlipayUtil.getDateTime(new Date())); map.put("version", AlipayConfig.APP_VERSION); map.put("notify_url", AlipayConfig.APP_NOTIFY_URL); map.put("biz_content", bizcontentJson.toString()); //签名 String sign = AlipayUtil.getSign(AlipayUtil.isVlaueNull(map),AlipayConfig.PRIVATE_KEY); //编码 String result = AlipayUtil.getReqestMap(AlipayUtil.isVlaueNull(map)); return result + sign; }
AlipayDto 是我封装的支付宝参数类
package com.yuanxinxinxi.yuanxinbuluo.alipay.dto; /** * * 支付宝下单参数 */ public class AlipayDto { private String app_id; //支付宝分配给开发者的应用ID private String method;// 接口名称 private String format; // 仅支持JSON private String charset;//请求使用的编码格式,如utf-8,gbk,gb2312等 private String sign_type;//商户生成签名字符串所使用的签名算法类型 private String timestamp;//发送请求的时间 private String version;//调用的接口版本,固定为:1.0 private String notify_url;//支付宝服务器主动通知商户服务器url private String biz_content;//业务请求参数的集合,最大长度不限 private String body;//描述信息。 private String subject;//商品的标题 private String out_trade_no;//商户网站唯一订单号 private String timeout_express;//交易的超时时间 private String total_amount;//订单总金额 private String product_code;//销售产品码 QUICK_MSECURITY_PAY private String passback_params;//公用回传参数 public String getApp_id() { return app_id; } public void setApp_id(String app_id) { this.app_id = app_id; } public String getMethod() { return method; } public void setMethod(String method) { this.method = method; } public String getFormat() { return format; } public void setFormat(String format) { this.format = format; } public String getCharset() { return charset; } public void setCharset(String charset) { this.charset = charset; } public String getSign_type() { return sign_type; } public void setSign_type(String sign_type) { this.sign_type = sign_type; } public String getTimestamp() { return timestamp; } public void setTimestamp(String timestamp) { this.timestamp = timestamp; } public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } public String getNotify_url() { return notify_url; } public void setNotify_url(String notify_url) { this.notify_url = notify_url; } public String getBiz_content() { return biz_content; } public void setBiz_content(String biz_content) { this.biz_content = biz_content; } public String getBody() { return body; } public void setBody(String body) { this.body = body; } public String getSubject() { return subject; } public void setSubject(String subject) { this.subject = subject; } public String getOut_trade_no() { return out_trade_no; } public void setOut_trade_no(String out_trade_no) { this.out_trade_no = out_trade_no; } public String getTimeout_express() { return timeout_express; } public void setTimeout_express(String timeout_express) { this.timeout_express = timeout_express; } public String getTotal_amount() { return total_amount; } public void setTotal_amount(String total_amount) { this.total_amount = total_amount; } public String getProduct_code() { return product_code; } public void setProduct_code(String product_code) { this.product_code = product_code; } public String getPassback_params() { return passback_params; } public void setPassback_params(String passback_params) { this.passback_params = passback_params; } }
encapData方法返回结果给app起调就行了
支付完成后异步通知处理 将支付请求中拿到参数
/** * 支付宝获取请求参数 * @param request * @return Map<String,String> */ public static Map<String,String> alipay_notify_para(HttpServletRequest request){ Map<String,String> map = new HashMap<String, String>(); Map<String,String[]> resultMap = request.getParameterMap(); for (Iterator<String> iter = resultMap.keySet().iterator(); iter.hasNext();) { String name = (String) iter.next(); String[] values = (String[]) resultMap.get(name); String valueStr = ""; for (int i = 0; i < values.length; i++) { valueStr = (i == values.length - 1) ? valueStr + values[i]:valueStr + values[i] + ","; } log.debug("支付宝回调参数:key="+name+"value:"+valueStr); map.put(name,valueStr); } return map; }
public Map<String, Object> notifyAlipayPayment(Map<String,String> map) { Map<String,Object> resultMap = new HashMap<String, Object>(); // 商户订单号 try { String notify_id = String.valueOf(map.get("notify_id")); String trade_status = String.valueOf(map.get("trade_status")); String sign_type = String.valueOf(map.get("sign_type")); /** * 是否有返回ID */ if(notify_id != null && !notify_id.equals("")){ /** * 验证签名 */ try { if(AlipaySignature.rsaCheckV1(map, AlipayConfig.PUBLIC_KEY ,AlipayConfig.APP_CHARSET,sign_type)){ /** * 支付是否成功 */ if(trade_status.equals("TRADE_SUCCESS") || trade_status.equals("TRADE_FINISHED")){ /** * 拿到参数数据 */ //金额 BigDecimal total_fee = new BigDecimal(String.valueOf(map.get("total_amount"))); //支付宝订单号 String trade_no = String.valueOf(map.get("trade_no")); // 商户订单号 String out_trade_no = String.valueOf(map.get("out_trade_no")); //数据包 String passback_params = String.valueOf(map.get("passback_params")); //支付方Id String buyer_id = String.valueOf(map.get("buyer_id"));
---------------- 业务--------------------------
}else{ resultMap.put("msg","支付失败"); resultMap.put("success", false); //总撤销 } }else{ resultMap.put("msg","签名错误"); resultMap.put("success", false); //总撤销 } } catch (AssertionError e){ resultMap.put("msg",e.getMessage()); resultMap.put("success", false); } catch (AlipayApiException e) { resultMap.put("msg","签名错误"); resultMap.put("success", false); log.error(e.getMessage()); //总撤销 } }else{ resultMap.put("msg","返回url有误"); resultMap.put("success", false); //总撤销 } } catch (Exception e) { resultMap.put("msg","服务器未响应"); resultMap.put("success", false); //总撤销 }
支付包退款很简单 官网有代码我这里就不提供了,支付宝撤销接口好像会报错 ,一直报isv权限不足,后来撤销我改成退款
后台回调在这里说下如何通知客户端
1.回调后参数拿到,返回过来商户订单号查询,看看表中是否有这个支付订单,如果有不做插入,如果没有插入表数据 最后返回通知支付宝
2.通知支付宝后,客户端可以请求后台,看看数据库是否有该订单号存在,如果存在说明支付完成,如果没有主动查询支付宝支付订单,查询返回结果插入数据,
3.插入数据失败,请求支付宝退款