配置文件:
package com.***.config;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Component;
/**
* Created by Administrator on 2017/5/21.
*/
@Component
public class AlipayConfig {
private static Log log = LogFactory.getLog(EmailConfig.class);
private static Configuration configs;
static {
AlipayConfig.init("alipayinfo.properties");
}
// 合作身份者ID,签约账号,以2088开头由16位纯数字组成的字符串,查看地址:https://b.alipay.com/order/pidAndKey.htm
public static String partner;
// 收款支付宝账号,以2088开头由16位纯数字组成的字符串,一般情况下收款账号就是签约账号
public static String seller_id;
// MD5密钥,安全检验码,由数字和字母组成的32位字符串,查看地址:https://b.alipay.com/order/pidAndKey.htm
public static String key;
// 服务器异步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
public static String notify_url;
// 页面跳转同步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
public static String return_url;
// 签名方式
public static String sign_type="MD5";
// 调试用,创建TXT日志文件夹路径,见AlipayCore.java类中的logResult(String sWord)打印方法。
public static String log_path ;
// 字符编码格式 目前支持 gbk 或 utf-8
public static String input_charset = "utf-8";
// 支付类型 ,无需修改
public static String payment_type = "1";
// 调用的接口名,无需修改
public static String service = "create_direct_pay_by_user";
// 防钓鱼时间戳 若要使用请调用类文件submit中的query_timestamp函数
public static String anti_phishing_key = "";
private AlipayConfig() {
}
private static synchronized void init(String filePath){
if(configs == null){
try {
configs = new PropertiesConfiguration(filePath);
} catch (ConfigurationException e) {
e.printStackTrace();
}
}
if (configs == null){
throw new IllegalStateException("can`t find file by path:" + filePath);
} else {
partner = configs.getString("partner");
seller_id = configs.getString("seller_id");
key = configs.getString("key");
notify_url = configs.getString("notify_url");
return_url = configs.getString("return_url");
log_path = configs.getString("log_path");
log.info("支付宝支付配置如下: ");
log.info("配置文件名: "+filePath);
log.info(description());
}
}
private static String description() {
StringBuilder sb =new StringBuilder("Configs:{");
sb.append("合作身份者ID: ").append(partner).append("\n");
sb.append("收款支付宝账号: ").append(seller_id).append("\n");
sb.append("服务器异步通知页面路径: ").append(notify_url).append("\n");
sb.append("页面跳转同步通知页面路径: ").append(return_url).append("\n");
sb.append("TXT日志文件夹路径: ").append(log_path).append("\n");
sb.append("}");
return sb.toString();
}
// get and set ...
}
支付宝核心:
package com.*.service.alipay;
import com.*.config.AlipayConfig;
import com.*.dto.OrderDto;
import com.*.service.alipay.sign.MD5;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
/**
* AliPay
* Created by Administrator on 2017/5/21.
* 支付宝主业务
*/
@Service
public class AliPay {
/**
* 支付宝提供给商户的服务接入网关URL(新)
*/
private static final String ALIPAY_GATEWAY_NEW = "https://mapi.alipay.com/gateway.do?";
/**
* 建立请求,以表单HTML形式构造(默认)
* @param strMethod 提交方式。两个值可选:post、get
* @return 提交表单HTML文本
*/
public String buildRequest(OrderDto orderDto, String strMethod ,String cip){
Map<String , String> sParaTemp = new Hashtable<String , String>();
generateParamTemp(orderDto,sParaTemp,cip); //参数生成
Map<String, String> sPara = buildRequestPara(sParaTemp);
List<String> keys = new ArrayList<String>(sPara.keySet());
StringBuffer sbHtml = new StringBuffer();
sbHtml.append("<form id=\"alipaysubmit\" name=\"alipaysubmit\" action=\"" + ALIPAY_GATEWAY_NEW
+ "_input_charset=" + AlipayConfig.input_charset + "\" method=\"" + strMethod
+ "\">");
for (int i = 0; i < keys.size(); i++) {
String name = (String) keys.get(i);
String value = (String) sPara.get(name);
sbHtml.append("<input type=\"hidden\" name=\"" + name + "\" value=\"" + value + "\"/>");
}
//submit按钮控件请不要含有name属性
sbHtml.append("<input type=\"submit\" value=\"确定\" style=\"display:none;\"></form>");
sbHtml.append("<script>document.forms['alipaysubmit'].submit();</script>");
System.out.println("#################### : "+sbHtml);
return sbHtml.toString();
}
/**
* 生成临时参数
*/
private void generateParamTemp(OrderDto orderDto, Map<String, String> sParaTemp, String cip){
sParaTemp.put("service", AlipayConfig.service); //调用的接口名,无需修改
sParaTemp.put("partner",AlipayConfig.partner); //合作者id , 签约账号
sParaTemp.put("seller_id",AlipayConfig.seller_id); //收款支付宝账号
sParaTemp.put("_input_charset",AlipayConfig.input_charset); //字节码
sParaTemp.put("payment_type",AlipayConfig.payment_type); //支付类型,只支持取值为 1
sParaTemp.put("notify_url",AlipayConfig.notify_url); //异步通知
System.out.println(sParaTemp.get("notify_url"));
// sParaTemp.put("return_url",AlipayConfig.return_url); //同步通知
sParaTemp.put("anti_phishing_key",AlipayConfig.anti_phishing_key); //防钓鱼时间戳
sParaTemp.put("exter_invoke_ip",cip); //客户端的IP地址 非局域网的外网IP地址
sParaTemp.put("out_trade_no",orderDto.getOrderNo()); //订单号
sParaTemp.put("subject",orderDto.getSubject()); //主题
sParaTemp.put("total_fee", orderDto.getTotalMoney()); //付款金额
sParaTemp.put("body",orderDto.getBody()); //订单详请
// sParaTemp.put("extra_common_param","你好,这是测试商户的广告。"); //公共回传参数
sParaTemp.put("qr_pay_mode","4"); //前置模式
sParaTemp.put("qrcode_width","250"); //前置模式二维码宽度
buildRequestPara(sParaTemp);
}
/**
* 生成要请求给支付宝的参数数组
* @param sParaTemp 请求前的参数数组
* @return 要请求的参数数组
*/
private static Map<String, String> buildRequestPara(Map<String, String> sParaTemp) {
//除去数组中的空值和签名参数
Map<String, String> sPara = AlipayCore.paraFilter(sParaTemp);
//生成签名结果
String mysign = buildRequestMysign(sPara);
//签名结果与签名方式加入请求提交参数组中
sPara.put("sign", mysign);
sPara.put("sign_type", AlipayConfig.sign_type);
return sPara;
}
/**
* 生成签名结果
* @param sPara 要签名的数组
* @return 签名结果字符串
*/
public static String buildRequestMysign(Map<String, String> sPara) {
//把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
String prestr = AlipayCore.createLinkString(sPara);
String mysign = "";
if(AlipayConfig.sign_type.equals("MD5") ) {
mysign = MD5.sign(prestr, AlipayConfig.key, AlipayConfig.input_charset);
}
return mysign;
}
package com.***.service.alipay;
import com.***.config.AlipayConfig;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.httpclient.methods.multipart.FilePartSource;
import org.apache.commons.httpclient.methods.multipart.PartSource;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.*;
/* *
*类名:AlipayFunction
*功能:支付宝接口公用函数类
*详细:该类是请求、通知返回两个文件所调用的公用函数核心处理文件,不需要修改
*/
public class AlipayCore {
/**
* 除去数组中的空值和签名参数
* @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;
}
/**
* 写日志,方便测试(看网站需求,也可以改成把记录存入数据库)
* @param sWord 要写入日志里的文本内容
*/
public static void logResult(String sWord) {
FileWriter writer = null;
try {
writer = new FileWriter(AlipayConfig.log_path + "alipay_log_" + System.currentTimeMillis()+".txt");
writer.write(sWord);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 生成文件摘要
* @param strFilePath 文件路径
* @param file_digest_type 摘要算法
* @return 文件摘要结果
*/
public static String getAbstract(String strFilePath, String file_digest_type) throws IOException {
PartSource file = new FilePartSource(new File(strFilePath));
if(file_digest_type.equals("MD5")){
return DigestUtils.md5Hex(file.createInputStream());
}
else if(file_digest_type.equals("SHA")) {
return DigestUtils.sha256Hex(file.createInputStream());
}
else {
return "";
}
}
}
消息通知处理类
package com.nroad.service.alipay;
import com.nroad.config.AlipayConfig;
import com.nroad.service.alipay.sign.MD5;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
/* *
*类名:AlipayNotify
*功能:支付宝通知处理类
*详细:处理支付宝各接口通知返回
*/
public class AlipayNotify {
/**
* 支付宝消息验证地址
*/
private static final String HTTPS_VERIFY_URL = "https://mapi.alipay.com/gateway.do?service=notify_verify&";
/**
* 验证消息是否是支付宝发出的合法消息
* @param params 通知返回来的参数数组
* @return 验证结果
*/
public static boolean verify(Map<String, String> params) {
//判断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);
//写日志记录(若要调试,请取消下面两行注释)
//String sWord = "responseTxt=" + responseTxt + "\n isSign=" + isSign + "\n 返回回来的参数:" + AlipayCore.createLinkString(params);
//AlipayCore.logResult(sWord);
if (isSign && responseTxt.equals("true")) {
return true;
} else {
return false;
}
}
/**
* 根据反馈回来的信息,生成签名结果
* @param Params 通知返回来的参数数组
* @param sign 比对的签名结果
* @return 生成的签名结果
*/
private static boolean getSignVeryfy(Map<String, String> Params, String sign) {
//过滤空值、sign与sign_type参数
Map<String, String> sParaNew = AlipayCore.paraFilter(Params);
//获取待签名字符串
String preSignStr = AlipayCore.createLinkString(sParaNew);
//获得签名验证结果
boolean isSign = false;
if(AlipayConfig.sign_type.equals("MD5") ) {
isSign = MD5.verify(preSignStr, sign, AlipayConfig.key, AlipayConfig.input_charset);
}
return isSign;
}
/**
* 获取远程服务器ATN结果,验证返回URL
* @param notify_id 通知校验ID
* @return 服务器ATN结果
* 验证结果集:
* invalid命令参数不对 出现这个错误,请检测返回处理中partner和key是否为空
* true 返回正确信息
* false 请检查防火墙或者是服务器阻止端口问题以及验证时间是否超过一分钟
*/
private 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 请检查防火墙或者是服务器阻止端口问题以及验证时间是否超过一分钟
*/
private 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;
}
}
异步回调地址
/**
* alipayNotify
* @param request
* @param response
* @throws IOException 异步通知
*/
@RequestMapping(value = "/alipay_notify")
public @ResponseBody
String alipayNotify(HttpServletRequest request, HttpServletResponse response) throws IOException {
log.info("支付宝付款异步通知开始");
String message = "success";
HashMap<String, String> params = new HashMap<String, String>();
//取出所有参数验证签名
Enumeration<String> parameterNames = request.getParameterNames();
while (parameterNames.hasMoreElements()) {
String parameterName = parameterNames.nextElement();
params.put(parameterName, request.getParameter(parameterName));
}
if (AlipayNotify.verify(params)) {
if (params.get("trade_status").equals("TRADE_SUCCESS")){
String out_trade_no = params.get("out_trade_no");
save(out_trade_no,"支付宝");
}else {
log.info("交易未处理");
message="fail";
}
}else {
log.info("签名错误,支付失败");
message="fail";
}
System.out.println(message);
return message;
}