初始版本

This commit is contained in:
zzs
2017-02-17 18:24:33 +08:00
parent efc0210c21
commit 027c84299a
80 changed files with 7642 additions and 0 deletions

View File

@@ -0,0 +1,63 @@
package in.egan.pay.ali.api;
import in.egan.pay.common.api.BasePayConfigStorage;
/**
* 支付客户端配置存储
* @author egan
* @email egzosn@gmail.com
* @date 2016-5-18 14:09:01
*/
public class AliPayConfigStorage extends BasePayConfigStorage {
// 商户PID
public volatile String partner ;
// 商户收款账号
public volatile String seller;
//公钥
private volatile String aliPublicKey;
public String getAliPublicKey() {
return aliPublicKey;
}
public void setAliPublicKey(String aliPublicKey) {
setKeyPublic(aliPublicKey);
this.aliPublicKey = aliPublicKey;
}
@Override
public String getSecretKey() {
return aliPublicKey;
}
@Override
public String getAppid() {
return null;
}
@Override
public String getPartner() {
return partner;
}
public void setPartner(String partner) {
this.partner = partner;
}
public String getSeller() {
return seller;
}
public void setSeller(String seller) {
this.seller = seller;
}
}

View File

@@ -0,0 +1,427 @@
package in.egan.pay.ali.api;
import in.egan.pay.ali.bean.AliTransactionType;
import in.egan.pay.ali.util.SimpleGetRequestExecutor;
import in.egan.pay.common.api.PayConfigStorage;
import in.egan.pay.common.api.PayService;
import in.egan.pay.common.api.RequestExecutor;
import in.egan.pay.common.bean.MethodType;
import in.egan.pay.common.bean.PayOrder;
import in.egan.pay.common.bean.PayOutMessage;
import in.egan.pay.common.bean.result.PayError;
import in.egan.pay.common.exception.PayErrorException;
import in.egan.pay.common.util.sign.SignUtils;
import in.egan.pay.common.util.str.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
/**
* 支付宝支付通知
* @author egan
* @email egzosn@gmail.com
* @date 2016-5-18 14:09:01
*/
public class AliPayService implements PayService {
protected final Log log = LogFactory.getLog(AliPayService.class);
protected PayConfigStorage payConfigStorage;
protected CloseableHttpClient httpClient;
protected HttpHost httpProxy;
private int retrySleepMillis = 1000;
private int maxRetryTimes = 5;
private String httpsReqUrl = "https://mapi.alipay.com/gateway.do";
public String getHttpsVerifyUrl() {
return httpsReqUrl + "?service=notify_verify";
}
@Override
public boolean verify(Map<String, String> params) {
if (params.get("sign") == null || params.get("notify_id") == null) {
log.debug("支付宝支付异常params" + params);
return false;
}
try {
return getSignVerify(params, params.get("sign")) && "true".equals(verifyUrl(params.get("notify_id")));
} catch (PayErrorException e) {
e.printStackTrace();
}
return false;
}
/**
* 根据反馈回来的信息,生成签名结果
* @param params 通知返回来的参数数组
* @param sign 比对的签名结果
* @return 生成的签名结果
*/
public boolean getSignVerify(Map<String, String> params, String sign) {
return SignUtils.valueOf(payConfigStorage.getSignType()).verify(params, sign, payConfigStorage.getKeyPublic(), payConfigStorage.getInputCharset());
}
@Override
public String verifyUrl(String notify_id) throws PayErrorException {
return execute(new SimpleGetRequestExecutor(), getHttpsVerifyUrl(), "partner=" + payConfigStorage.getPartner() + "&notify_id=" + notify_id);
}
/**
* 向支付宝端发送请求在这里执行的策略是当发生access_token过期时才去刷新然后重新执行请求而不是全局定时请求
*
* @param executor
* @param uri
* @param data
* @return
* @throws PayErrorException
*/
@Override
public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws PayErrorException {
int retryTimes = 0;
do {
try {
return executeInternal(executor, uri, data);
} catch (PayErrorException e) {
PayError error = e.getError();
if (error.getErrorCode() == 404) {
int sleepMillis = retrySleepMillis * (1 << retryTimes);
try {
log.debug(String.format("支付宝系统繁忙,(%s)ms 后重试(第%s次)", sleepMillis, retryTimes + 1));
Thread.sleep(sleepMillis);
} catch (InterruptedException e1) {
throw new RuntimeException(e1);
}
} else {
throw e;
}
}
} while (++retryTimes < maxRetryTimes);
throw new RuntimeException("支付宝服务端异常,超出重试次数");
}
/**
* 返回创建的订单信息
*
* @param order 支付订单
* @return
* @see in.egan.pay.common.bean.PayOrder
*/
@Override
public Map<String, Object> orderInfo(PayOrder order) {
Map<String, Object> orderInfo = getOrder(order);
String sign = createSign( SignUtils.parameterText(orderInfo, "&"), "UTF-8");
try {
sign = URLEncoder.encode(sign, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
orderInfo.put("sign", sign);
orderInfo.put("sign_type", payConfigStorage.getSignType());
return orderInfo;
}
/**
* 支付宝创建订单信息
* create the order info
*
* @param order 支付订单
* @return
* @see in.egan.pay.common.bean.PayOrder
*/
@Deprecated
private String getOrderInfo(PayOrder order) {
StringBuilder orderInfo = new StringBuilder();
// 签约合作者身份ID
orderInfo.append( "partner=").append( "\"").append( payConfigStorage.getPartner() ).append("\"");
// 签约卖家支付宝账号
orderInfo.append("&seller_id=" ) .append("\"" ) .append(payConfigStorage.getSeller() ) .append("\"");
// 商户网站唯一订单号
orderInfo.append("&out_trade_no=" ) .append("\"" ).append(order.getTradeNo() ) .append("\"");
// 商品名称
orderInfo.append("&subject=" ) .append("\"" ) .append(order.getSubject() ) .append("\"");
// 商品详情
orderInfo.append("&body=" ) .append("\"" ) .append(order.getBody() ) .append("\"");
// 商品金额
orderInfo.append("&total_fee=" ) .append("\"" ) .append(order.getPrice().setScale(2, BigDecimal.ROUND_HALF_UP) ) .append("\"");
// 服务器异步通知页面路径
orderInfo.append("&notify_url=" ) .append("\"" ).append( payConfigStorage.getNotifyUrl() ) .append("\"");
// 服务接口名称, 固定值
orderInfo.append("&service=\"" ).append( order.getTransactionType().getType() ).append("\"");
// 支付类型, 固定值
orderInfo.append("&payment_type=\"1\"");
// 参数编码, 固定值
orderInfo.append("&_input_charset=\"utf-8\"");
// 设置未付款交易的超时时间
// 默认30分钟一旦超时该笔交易就会自动被关闭。
// 取值范围1m15d。
// m-分钟h-小时d-天1c-当天无论交易何时创建都在0点关闭
// 该参数数值不接受小数点如1.5h可转换为90m。
orderInfo.append("&it_b_pay=\"30m\"");
// extern_token为经过快登授权获取到的alipay_open_id,带上此参数用户将使用授权的账户进行支付
// orderInfo.append("&extern_token=" ) .append("\"" ) extern_token ) .append("\"");
// 支付宝处理完请求后,当前页面跳转到商户指定页面的路径,可空
if (AliTransactionType.APP == order.getTransactionType()){
orderInfo.append("&return_url=\"m.alipay.com\"");
}
// 调用银行卡支付,需配置此参数,参与签名, 固定值 (需要签约《无线银行卡快捷支付》才能使用)
// if (order.getTransactionType().getType())
// orderInfo.append("&paymethod=\"expressGateway\"");
return orderInfo.toString();
}
/**
* 支付宝创建订单信息
* create the order info
*
* @param order 支付订单
* @return
* @see in.egan.pay.common.bean.PayOrder
*/
private Map<String, Object> getOrder(PayOrder order) {
Map<String, Object> orderInfo = new HashMap<>();
// StringBuilder orderInfo = new StringBuilder();
// 签约合作者身份ID
orderInfo.put("partner", payConfigStorage.getPartner());
// orderInfo.append( "partner=").append( "\"").append( payConfigStorage.getPartner() ).append("\"");
// 签约卖家支付宝账号
orderInfo.put("seller_id", payConfigStorage.getSeller());
// orderInfo.append("&seller_id=" ) .append("\"" ) .append(payConfigStorage.getSeller() ) .append("\"");
// 商户网站唯一订单号
orderInfo.put("out_trade_no", order.getTradeNo());
// orderInfo.append("&out_trade_no=" ) .append("\"" ).append(order.getTradeNo() ) .append("\"");
// 商品名称
orderInfo.put("subject", order.getSubject());
// orderInfo.append("&subject=" ) .append("\"" ) .append(order.getSubject() ) .append("\"");
// 商品详情
orderInfo.put("body", order.getBody());
// orderInfo.append("&body=" ) .append("\"" ) .append(order.getBody() ) .append("\"");
// 商品金额
orderInfo.put("total_fee", order.getPrice().setScale(2, BigDecimal.ROUND_HALF_UP).toString() );
// orderInfo.append("&total_fee=" ) .append("\"" ) .append(order.getPrice().setScale(2, BigDecimal.ROUND_HALF_UP) ) .append("\"");
// 服务器异步通知页面路径
orderInfo.put("notify_url", payConfigStorage.getNotifyUrl() );
// orderInfo.append("&notify_url=" ) .append("\"" ).append( payConfigStorage.getNotifyUrl() ) .append("\"");
// 服务接口名称, 固定值
orderInfo.put("service", order.getTransactionType().getType() );
// orderInfo.append("&service=\"" ).append( order.getTransactionType().getType() ).append("\"");
// 支付类型, 固定值
orderInfo.put("payment_type", "1" );
// orderInfo.append("&payment_type=\"1\"");
// 参数编码, 固定值
orderInfo.put("_input_charset", payConfigStorage.getInputCharset());
// orderInfo.append("&_input_charset=\"utf-8\"");
// 设置未付款交易的超时时间
// 默认30分钟一旦超时该笔交易就会自动被关闭。
// 取值范围1m15d。
// m-分钟h-小时d-天1c-当天无论交易何时创建都在0点关闭
// 该参数数值不接受小数点如1.5h可转换为90m。
// TODO 2017/2/6 11:05 author: egan 目前写死,这一块建议配置
orderInfo.put("it_b_pay", "30m");
// orderInfo.append("&it_b_pay=\"30m\"");
// extern_token为经过快登授权获取到的alipay_open_id,带上此参数用户将使用授权的账户进行支付
// orderInfo.append("&extern_token=" ) .append("\"" ) extern_token ) .append("\"");
// 支付宝处理完请求后,当前页面跳转到商户指定页面的路径,可空
orderInfo.put("return_url", payConfigStorage.getReturnUrl());
// orderInfo.append("&return_url=\"m.alipay.com\"");
// 调用银行卡支付,需配置此参数,参与签名, 固定值 (需要签约《无线银行卡快捷支付》才能使用)
// if (order.getTransactionType().getType())
// orderInfo.append("&paymethod=\"expressGateway\"");
return orderInfo;
}
@Override
public String createSign(String content, String characterEncoding) {
return SignUtils.valueOf(payConfigStorage.getSignType()).createSign(content, payConfigStorage.getKeyPrivate(),characterEncoding);
}
@Override
public Map<String, String> getParameter2Map(Map<String, String[]> parameterMap, InputStream is) {
Map<String,String> params = new TreeMap<String,String>();
for (Iterator iter = parameterMap.keySet().iterator(); iter.hasNext();) {
String name = (String) iter.next();
String[] values = parameterMap.get(name);
String valueStr = "";
for (int i = 0,len = values.length; i < len; i++) {
valueStr += (i == len - 1) ? values[i]
: values[i] + ",";
}
//乱码解决这段代码在出现乱码时使用。如果mysign和sign不相等也可以使用这段代码转化
//valueStr = new String(valueStr.getBytes("ISO-8859-1"), "gbk");
params.put(name, valueStr);
}
return params;
}
@Override
public PayOutMessage getPayOutMessage(String code, String message) {
return PayOutMessage.TEXT().content(code.toLowerCase()).build();
}
@Override
public String buildRequest(Map<String, Object> orderInfo, MethodType method) {
StringBuffer formHtml = new StringBuffer();
formHtml.append("<form id=\"_alipaysubmit_\" name=\"alipaysubmit\" action=\"" )
.append( httpsReqUrl)
.append( "?_input_charset=" )
.append( payConfigStorage.getInputCharset())
.append( "\" method=\"")
.append( method.name().toLowerCase()) .append( "\">");
for (String key: orderInfo.keySet()) {
Object o = orderInfo.get(key);
if (null == o ||"null".equals(o) || "".equals(o) ){
continue;
}
formHtml.append("<input type=\"hidden\" name=\"" + key + "\" value=\"" + orderInfo.get(key) + "\"/>");
}
//submit按钮控件请不要含有name属性
// formHtml.append("<input type=\"submit\" value=\"\" style=\"display:none;\">");
formHtml.append("</form>");
formHtml.append("<script>document.forms['_alipaysubmit_'].submit();</script>");
return formHtml.toString();
}
/**
* 生成二维码支付
* 暂未实现或无此功能
* @param orderInfo 发起支付的订单信息
* @return
*/
@Override
public BufferedImage genQrPay(Map<String, Object> orderInfo) {
throw new UnsupportedOperationException();
}
protected <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data) throws PayErrorException {
try {
return executor.execute(getHttpClient(), httpProxy, uri, data);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public HttpHost getHttpProxy() {
return httpProxy;
}
public CloseableHttpClient getHttpClient() {
return httpClient;
}
public void setPayConfigStorage(PayConfigStorage payConfigStorage) {
this.payConfigStorage = payConfigStorage;
String http_proxy_host = payConfigStorage.getHttpProxyHost();
int http_proxy_port = payConfigStorage.getHttpProxyPort();
String http_proxy_username = payConfigStorage.getHttpProxyUsername();
String http_proxy_password = payConfigStorage.getHttpProxyPassword();
if (StringUtils.isNotBlank(http_proxy_host)) {
// 使用代理服务器
if (StringUtils.isNotBlank(http_proxy_username)) {
// 需要用户认证的代理服务器
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(
new AuthScope(http_proxy_host, http_proxy_port),
new UsernamePasswordCredentials(http_proxy_username, http_proxy_password));
httpClient = HttpClients
.custom()
.setDefaultCredentialsProvider(credsProvider)
.build();
} else {
// 无需用户认证的代理服务器
httpClient = HttpClients.createDefault();
}
httpProxy = new HttpHost(http_proxy_host, http_proxy_port);
} else {
httpClient = HttpClients.createDefault();
}
}
@Override
public PayConfigStorage getPayConfigStorage() {
return payConfigStorage;
}
public AliPayService() {
}
public AliPayService(PayConfigStorage payConfigStorage) {
setPayConfigStorage(payConfigStorage);
}
}

View File

@@ -0,0 +1,210 @@
package in.egan.pay.ali.bean;
import in.egan.pay.common.bean.PayCallMessage;
import java.math.BigDecimal;
import java.util.Date;
/**
* 阿里支付回调消息
* @author egan
* @email egzosn@gmail.com
* @date 2016-5-18 14:09:01
*/
public class AliPayCallMessage extends PayCallMessage {
private Date notify_time;
private String notify_type;
private String notify_id ;
private String subject;
private String payment_type;
private String trade_no;
private String trade_status;
private String seller_id;
private String seller_email;
private String buyer_id;
private String buyer_email;
private Integer quantity;
private BigDecimal price;
private String body;
private Date gmt_create;
private Date gmt_payment;
private String is_total_fee_adjust;
private String use_coupon;
private String discount;
private String refund_status;
private String gmt_refund;
public Date getNotify_time() {
return notify_time;
}
public void setNotify_time(Date notify_time) {
this.notify_time = notify_time;
}
public String getNotify_type() {
return notify_type;
}
public void setNotify_type(String notify_type) {
this.notify_type = notify_type;
}
public String getNotify_id() {
return notify_id;
}
public void setNotify_id(String notify_id) {
this.notify_id = notify_id;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getPayment_type() {
return payment_type;
}
public void setPayment_type(String payment_type) {
this.payment_type = payment_type;
}
public String getTrade_no() {
return trade_no;
}
public void setTrade_no(String trade_no) {
this.trade_no = trade_no;
}
public String getTrade_status() {
return trade_status;
}
public void setTrade_status(String trade_status) {
this.trade_status = trade_status;
}
public String getSeller_id() {
return seller_id;
}
public void setSeller_id(String seller_id) {
this.seller_id = seller_id;
}
public String getSeller_email() {
return seller_email;
}
public void setSeller_email(String seller_email) {
this.seller_email = seller_email;
}
public String getBuyer_id() {
return buyer_id;
}
public void setBuyer_id(String buyer_id) {
this.buyer_id = buyer_id;
}
public String getBuyer_email() {
return buyer_email;
}
public void setBuyer_email(String buyer_email) {
this.buyer_email = buyer_email;
}
public Integer getQuantity() {
return quantity;
}
public void setQuantity(Integer quantity) {
this.quantity = quantity;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public Date getGmt_create() {
return gmt_create;
}
public void setGmt_create(Date gmt_create) {
this.gmt_create = gmt_create;
}
public Date getGmt_payment() {
return gmt_payment;
}
public void setGmt_payment(Date gmt_payment) {
this.gmt_payment = gmt_payment;
}
public String getIs_total_fee_adjust() {
return is_total_fee_adjust;
}
public void setIs_total_fee_adjust(String is_total_fee_adjust) {
this.is_total_fee_adjust = is_total_fee_adjust;
}
public String getUse_coupon() {
return use_coupon;
}
public void setUse_coupon(String use_coupon) {
this.use_coupon = use_coupon;
}
public String getDiscount() {
return discount;
}
public void setDiscount(String discount) {
this.discount = discount;
}
public String getRefund_status() {
return refund_status;
}
public void setRefund_status(String refund_status) {
this.refund_status = refund_status;
}
public String getGmt_refund() {
return gmt_refund;
}
public void setGmt_refund(String gmt_refund) {
this.gmt_refund = gmt_refund;
}
public AliPayCallMessage() {
}
}

View File

@@ -0,0 +1,25 @@
package in.egan.pay.ali.bean;
import in.egan.pay.common.bean.TransactionType;
/**
* 阿里交易类型
* @author egan
* @email egzosn@gmail.com
* @date 2016/10/19 22:58
*/
public enum AliTransactionType implements TransactionType {
//即时到帐 //移动支付 //手机网站支付
DIRECT("create_direct_pay_by_user"),APP("mobile.securitypay.pay"),WAP("alipay.wap.create.direct.pay.by.user");
private String type;
private AliTransactionType(String type) {
this.type = type;
}
@Override
public String getType() {
return type;
}
}

View File

@@ -0,0 +1,52 @@
package in.egan.pay.ali.util;
/**
* @author egan
* @email egzosn@gmail.com
* @date 2016-5-24
*/
import in.egan.pay.common.api.RequestExecutor;
import in.egan.pay.common.bean.result.PayError;
import in.egan.pay.common.exception.PayErrorException;
import in.egan.pay.common.util.http.Utf8ResponseHandler;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import java.io.IOException;
/**
* 简单的GET请求执行器请求的参数是String, 返回的结果也是String
* @author Daniel Qian
*
*/
public class SimpleGetRequestExecutor implements RequestExecutor<String, String> {
@Override
public String execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, String queryParam) throws IOException, PayErrorException {
if (queryParam != null) {
if (uri.indexOf('?') == -1) {
uri += '?';
}
uri += uri.endsWith("?") ? queryParam : '&' + queryParam;
}
HttpGet httpGet = new HttpGet(uri);
if (httpProxy != null) {
RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build();
httpGet.setConfig(config);
}
try (CloseableHttpResponse response = httpclient.execute(httpGet)) {
String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response);
if ("true".equals(responseContent)){ return responseContent; }
throw new PayErrorException(new PayError(100101, responseContent));
}
}
}