org.redkalex.pay.AliPayService Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of redkale-plugins Show documentation
Show all versions of redkale-plugins Show documentation
Redkale-Plugins -- java framework
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.redkalex.pay;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.security.*;
import java.security.spec.*;
import java.util.*;
import java.util.logging.*;
import javax.annotation.Resource;
import org.redkale.util.*;
import org.redkale.convert.json.*;
import static org.redkalex.pay.Pays.*;
import static org.redkalex.pay.PayRetCodes.*;
import org.redkale.service.Local;
/**
*
* 详情见: https://redkale.org
*
* @author zhangjx
*/
@Local
@AutoLoad(false)
@Comment("支付宝支付服务")
public class AliPayService extends AbstractPayService {
protected static final String format = "%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS"; //yyyy-MM-dd HH:mm:ss
//配置集合
protected Map elements = new HashMap<>();
@Resource(name = "property.pay.alipay.conf") //支付配置文件路径
protected String conf = "config.properties";
@Resource(name = "APP_HOME")
protected File home;
@Resource
protected JsonConvert convert;
@Override
public void init(AnyValue conf) {
if (this.convert == null) this.convert = JsonConvert.root();
if (this.conf != null && !this.conf.isEmpty()) { //存在支付宝支付配置
try {
File file = (this.conf.indexOf('/') == 0 || this.conf.indexOf(':') > 0) ? new File(this.conf) : new File(home, "conf/" + this.conf);
InputStream in = (file.isFile() && file.canRead()) ? new FileInputStream(file) : getClass().getResourceAsStream("/META-INF/" + this.conf);
if (in == null) return;
Properties properties = new Properties();
properties.load(in);
in.close();
this.elements = AliPayElement.create(logger, properties);
} catch (Exception e) {
logger.log(Level.SEVERE, "init alipay conf error", e);
}
}
}
public void setPayElements(Map elements) {
this.elements = elements;
}
public void putPayElements(Map elements) {
this.elements.putAll(elements);
}
public AliPayElement getPayElement(String appid) {
return this.elements.get(appid);
}
public void setPayElement(String appid, AliPayElement element) {
this.elements.put(appid, element);
}
public boolean existsPayElement(String appid) {
return this.elements != null && this.elements.containsKey(appid);
}
@Override
public PayPreResponse prepay(final PayPreRequest request) {
request.checkVaild();
//参数说明: https://doc.open.alipay.com/doc2/detail.htm?spm=a219a.7629140.0.0.lMJkw3&treeId=59&articleId=103663&docType=1
final PayPreResponse result = new PayPreResponse();
try {
final AliPayElement element = elements.get(request.getAppid());
if (element == null) return result.retcode(RETPAY_CONF_ERROR);
result.setAppid(element.appid);
// 签约合作者身份ID
String param = "partner=" + "\"" + element.merchno + "\"";
// 签约卖家支付宝账号(也可用身份ID)
param += "&seller_id=" + "\"" + element.sellerid + "\"";
// 商户网站唯一订单号
param += "&out_trade_no=" + "\"" + request.getPayno() + "\"";
// 商品名称
param += "&subject=" + "\"" + request.getPaytitle() + "\"";
// 商品详情
param += "&body=" + "\"" + request.getPaybody() + "\"";
// 商品金额
param += "&total_fee=" + "\"" + (request.getPaymoney() / 100.0) + "\"";
// 服务器异步通知页面路径
param += "¬ify_url=" + "\"" + ((request.notifyurl != null && !request.notifyurl.isEmpty()) ? request.notifyurl : element.notifyurl) + "\"";
// 服务接口名称, 固定值
param += "&service=\"mobile.securitypay.pay\"";
// 支付类型, 固定值
param += "&payment_type=\"1\"";
// 参数编码, 固定值
param += "&_input_charset=\"utf-8\"";
// 设置未付款交易的超时时间
// 默认30分钟,一旦超时,该笔交易就会自动被关闭。
// 取值范围:1m~15d。 m-分钟,h-小时,d-天,1c-当天(无论交易何时创建,都在0点关闭)。
// 该参数数值不接受小数点,如1.5h,可转换为90m。
param += "&it_b_pay=\"" + request.getTimeoutms() + "m\"";
Signature signature = Signature.getInstance("SHA1WithRSA");
signature.initSign(element.priKey);
signature.update(param.getBytes("UTF-8"));
param += "&sign=\"" + URLEncoder.encode(Base64.getEncoder().encodeToString(signature.sign()), "UTF-8") + "\"";
param += "&sign_type=\"RSA\"";
final Map rmap = new TreeMap<>();
rmap.put("content", param);
result.setResult(rmap);
} catch (Exception e) {
result.setRetcode(RETPAY_PAY_ERROR);
logger.log(Level.WARNING, "prepay_pay_error req=" + request + ", resp=" + result.responsetext, e);
}
return result;
}
//手机支付回调
// https://doc.open.alipay.com/doc2/detail.htm?spm=a219a.7629140.0.0.UywIMY&treeId=59&articleId=103666&docType=1
@Override
public PayNotifyResponse notify(PayNotifyRequest request) {
request.checkVaild();
final PayNotifyResponse result = new PayNotifyResponse();
result.setPaytype(request.getPaytype());
final String rstext = "success";
Map map = request.getMap();
final AliPayElement element = elements.get(request.getAppid());
if (element == null) return result.retcode(RETPAY_CONF_ERROR);
result.setPayno(map.getOrDefault("out_trade_no", ""));
result.setThirdpayno(map.getOrDefault("trade_no", ""));
if (!checkSign(element, map)) return result.retcode(RETPAY_FALSIFY_ERROR);
String state = map.getOrDefault("trade_status", "");
if ("WAIT_BUYER_PAY".equals(state)) return result.retcode(RETPAY_PAY_WAITING);
if (!"TRADE_SUCCESS".equals(state)) return result.retcode(RETPAY_PAY_FAILED);
return result.result(rstext);
}
@Override
public PayCreatResponse create(PayCreatRequest request) {
request.checkVaild();
final PayCreatResponse result = new PayCreatResponse();
try {
final AliPayElement element = elements.get(request.getAppid());
if (element == null) return result.retcode(RETPAY_CONF_ERROR);
final TreeMap map = new TreeMap<>();
map.put("app_id", element.appid);
map.put("method", "alipay.trade.create");
map.put("format", "JSON");
map.put("charset", element.charset);
map.put("sign_type", "RSA");
map.put("timestamp", String.format(format, System.currentTimeMillis()));
map.put("version", "1.0");
if (element.notifyurl != null && !element.notifyurl.isEmpty()) map.put("notify_url", element.notifyurl);
final TreeMap biz_content = new TreeMap<>();
if (request.getMap() != null) biz_content.putAll(request.getMap());
biz_content.put("out_trade_no", request.getPayno());
biz_content.putIfAbsent("scene", "bar_code");
biz_content.put("total_amount", "" + (request.getPaymoney() / 100.0));
biz_content.put("subject", "" + request.getPaytitle());
biz_content.put("body", request.getPaybody());
map.put("biz_content", convert.convertTo(biz_content));
map.put("sign", createSign(element, map));
final String responseText = Utility.postHttpContent("https://openapi.alipay.com/gateway.do", Charset.forName(element.charset), joinMap(map));
//{"alipay_trade_create_response":{"code":"40002","msg":"Invalid Arguments","sub_code":"isv.invalid-signature","sub_msg":"无效签名"},"sign":"xxxxxxxxxxxx"}
result.setResponsetext(responseText);
final InnerCreateResponse resp = convert.convertFrom(InnerCreateResponse.class, responseText);
resp.responseText = responseText; //原始的返回内容
if (!checkSign(element, resp)) return result.retcode(RETPAY_FALSIFY_ERROR);
final Map resultmap = resp.alipay_trade_create_response;
result.setResult(resultmap);
if (!"SUCCESS".equalsIgnoreCase(resultmap.get("msg"))) {
return result.retcode(RETPAY_PAY_ERROR).retinfo(resultmap.get("sub_msg"));
}
result.setThirdpayno(resultmap.getOrDefault("trade_no", ""));
} catch (Exception e) {
result.setRetcode(RETPAY_PAY_ERROR);
logger.log(Level.WARNING, "create_pay_error req=" + request + ", resp=" + result.responsetext, e);
}
return result;
}
@Override
public PayQueryResponse query(PayRequest request) {
request.checkVaild();
final PayQueryResponse result = new PayQueryResponse();
try {
final AliPayElement element = elements.get(request.getAppid());
if (element == null) return result.retcode(RETPAY_CONF_ERROR);
final TreeMap map = new TreeMap<>();
map.put("app_id", element.appid);
map.put("sign_type", "RSA");
map.put("charset", element.charset);
map.put("format", "json");
map.put("version", "1.0");
//if (this.notifyurl != null && !this.notifyurl.isEmpty()) map.put("notify_url", this.notifyurl); // 查询不需要
map.put("timestamp", String.format(format, System.currentTimeMillis()));
map.put("method", "alipay.trade.query");
final TreeMap biz_content = new TreeMap<>();
biz_content.put("out_trade_no", request.getPayno());
map.put("biz_content", convert.convertTo(biz_content));
map.put("sign", createSign(element, map));
final String responseText = Utility.postHttpContent("https://openapi.alipay.com/gateway.do", Charset.forName(element.charset), joinMap(map));
result.setResponsetext(responseText);
final InnerQueryResponse resp = convert.convertFrom(InnerQueryResponse.class, responseText);
resp.responseText = responseText; //原始的返回内容
if (!checkSign(element, resp)) return result.retcode(RETPAY_FALSIFY_ERROR);
final Map resultmap = resp.alipay_trade_query_response;
result.setResult(resultmap);
if (!"SUCCESS".equalsIgnoreCase(resultmap.get("msg"))) {
return result.retcode(RETPAY_PAY_ERROR).retinfo(resultmap.get("sub_msg"));
}
//trade_status 交易状态:WAIT_BUYER_PAY(交易创建,等待买家付款)、TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、TRADE_SUCCESS(交易支付成功)、TRADE_FINISHED(交易结束,不可退款)
short paystatus = PAYSTATUS_PAYNO;
switch (resultmap.get("trade_status")) {
case "TRADE_SUCCESS": paystatus = PAYSTATUS_PAYOK;
break;
case "WAIT_BUYER_PAY": paystatus = PAYSTATUS_UNPAY;
break;
case "TRADE_CLOSED": paystatus = PAYSTATUS_CLOSED;
break;
case "TRADE_FINISHED": paystatus = PAYSTATUS_PAYOK;
break;
}
result.setPaystatus(paystatus);
result.setThirdpayno(resultmap.getOrDefault("trade_no", ""));
result.setPayedmoney((long) (Double.parseDouble(resultmap.get("receipt_amount")) * 100));
} catch (Exception e) {
result.setRetcode(RETPAY_PAY_ERROR);
logger.log(Level.WARNING, "query_pay_error req=" + request + ", resp=" + result.responsetext, e);
}
return result;
}
@Override
public PayResponse close(PayCloseRequest request) {
request.checkVaild();
final PayResponse result = new PayResponse();
try {
final AliPayElement element = elements.get(request.getAppid());
if (element == null) return result.retcode(RETPAY_CONF_ERROR);
final TreeMap map = new TreeMap<>();
map.put("app_id", element.appid);
map.put("sign_type", "RSA");
map.put("charset", element.charset);
map.put("format", "json");
map.put("version", "1.0");
if (element.notifyurl != null && !element.notifyurl.isEmpty()) map.put("notify_url", element.notifyurl);
map.put("timestamp", String.format(format, System.currentTimeMillis()));
map.put("method", "alipay.trade.close");
final TreeMap biz_content = new TreeMap<>();
biz_content.put("out_trade_no", request.getPayno());
map.put("biz_content", convert.convertTo(biz_content));
map.put("sign", createSign(element, map));
final String responseText = Utility.postHttpContent("https://openapi.alipay.com/gateway.do", Charset.forName(element.charset), joinMap(map));
result.setResponsetext(responseText);
final InnerCloseResponse resp = convert.convertFrom(InnerCloseResponse.class, responseText);
resp.responseText = responseText; //原始的返回内容
if (!checkSign(element, resp)) return result.retcode(RETPAY_FALSIFY_ERROR);
final Map resultmap = resp.alipay_trade_close_response;
result.setResult(resultmap);
if (!"SUCCESS".equalsIgnoreCase(resultmap.get("msg"))) {
return result.retcode(RETPAY_PAY_ERROR).retinfo(resultmap.get("sub_msg"));
}
} catch (Exception e) {
result.setRetcode(RETPAY_PAY_ERROR);
logger.log(Level.WARNING, "close_pay_error req=" + request + ", resp=" + result.responsetext, e);
}
return result;
}
//https://doc.open.alipay.com/docs/api.htm?spm=a219a.7629065.0.0.wavZ99&apiId=759&docType=4
@Override
public PayRefundResponse refund(PayRefundRequest request) {
request.checkVaild();
final PayRefundResponse result = new PayRefundResponse();
try {
final AliPayElement element = elements.get(request.getAppid());
if (element == null) return result.retcode(RETPAY_CONF_ERROR);
final TreeMap map = new TreeMap<>();
map.put("app_id", element.appid);
map.put("sign_type", "RSA");
map.put("charset", element.charset);
map.put("format", "json");
map.put("version", "1.0");
map.put("timestamp", String.format(format, System.currentTimeMillis()));
map.put("method", "alipay.trade.refund");
final TreeMap biz_content = new TreeMap<>();
biz_content.put("out_trade_no", request.getPayno());
biz_content.put("refund_amount", "" + (request.getRefundmoney() / 100.0));
map.put("biz_content", convert.convertTo(biz_content));
map.put("sign", createSign(element, map));
final String responseText = Utility.postHttpContent("https://openapi.alipay.com/gateway.do", Charset.forName(element.charset), joinMap(map));
result.setResponsetext(responseText);
final InnerCloseResponse resp = convert.convertFrom(InnerCloseResponse.class, responseText);
resp.responseText = responseText; //原始的返回内容
if (!checkSign(element, resp)) return result.retcode(RETPAY_FALSIFY_ERROR);
final Map resultmap = resp.alipay_trade_close_response;
result.setResult(resultmap);
if (!"SUCCESS".equalsIgnoreCase(resultmap.get("msg"))) {
return result.retcode(RETPAY_PAY_ERROR).retinfo(resultmap.get("sub_msg"));
}
result.setRefundedmoney((long) (Double.parseDouble(resultmap.get("refund_fee")) * 100));
} catch (Exception e) {
result.setRetcode(RETPAY_PAY_ERROR);
logger.log(Level.WARNING, "refund_pay_error req=" + request + ", resp=" + result.responsetext, e);
}
return result;
}
@Override
public PayRefundResponse queryRefund(PayRequest request) {
PayQueryResponse queryResponse = query(request);
final PayRefundResponse response = new PayRefundResponse();
response.setRetcode(queryResponse.getRetcode());
response.setRetinfo(queryResponse.getRetinfo());
response.setResponsetext(queryResponse.getResponsetext());
response.setResult(queryResponse.getResult());
if (queryResponse.isSuccess()) {
response.setRefundedmoney((long) (Double.parseDouble(response.getResult().get("receipt_amount")) * 100));
}
return response;
}
protected boolean checkSign(final PayElement element, InnerResponse response) throws Exception {
if (((AliPayElement) element).pubKey == null) return true;
String text = response.responseText;
text = text.substring(text.indexOf(':') + 1, text.indexOf(",\"sign\""));
Signature signature = Signature.getInstance("SHA1WithRSA");
signature.initVerify(((AliPayElement) element).pubKey);
signature.update(text.getBytes(((AliPayElement) element).charset));
return signature.verify(Base64.getDecoder().decode(response.sign.getBytes()));
}
@Override
protected String createSign(final PayElement element, Map map) throws Exception {
Signature signature = Signature.getInstance("SHA1WithRSA");
signature.initSign(((AliPayElement) element).priKey);
signature.update(joinMap(map).getBytes(((AliPayElement) element).charset));
return URLEncoder.encode(Base64.getEncoder().encodeToString(signature.sign()), "UTF-8");
}
@Override
protected boolean checkSign(final PayElement element, Map map0) { //支付宝玩另类
if (((AliPayElement) element).pubKey == null) return true;
Map map = (Map) map0;
String sign = (String) map.remove("sign");
if (sign == null) return false;
String sign_type = (String) map.remove("sign_type");
String text = joinMap(map);
map.put("sign", sign);
if (sign_type != null) map.put("sign_type", sign_type);
try {
Signature signature = Signature.getInstance("SHA1WithRSA");
signature.initVerify(((AliPayElement) element).pubKey);
signature.update(text.getBytes("UTF-8"));
return signature.verify(Base64.getDecoder().decode(sign.getBytes()));
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public static class InnerCloseResponse extends InnerResponse {
public Map alipay_trade_close_response;
}
public static class InnerQueryResponse extends InnerResponse {
public Map alipay_trade_query_response;
}
public static class InnerCreateResponse extends InnerResponse {
public Map alipay_trade_create_response;
}
public static class InnerResponse {
public String responseText;
public String sign;
@Override
public String toString() {
return JsonFactory.root().getConvert().convertTo(this);
}
}
public static class AliPayElement extends PayElement {
// pay.alipay.[x].merchno
public String merchno = ""; //商户ID 签约的支付宝账号对应的支付宝唯一用户号。以2088开头的16位纯数字组成。
// pay.alipay.[x].sellerid
public String sellerid = ""; //商户账号,为空时则使用 merchno
// pay.alipay.[x].charset
public String charset = "UTF-8"; //字符集
// pay.alipay.[x].appid
public String appid = ""; //APP应用ID
// pay.alipay.[x].notifyurl
//public String notifyurl = ""; //回调url
// pay.alipay.[x].signcertkey
public String signcertkey = ""; //签名算法需要用到的密钥
// pay.alipay.[x].verifycertkey
public String verifycertkey = ""; //公钥
//
protected PrivateKey priKey; //私钥
//
protected PublicKey pubKey; //公钥
public static Map create(Logger logger, Properties properties) {
String def_appid = properties.getProperty("pay.alipay.appid", "").trim();
String def_merchno = properties.getProperty("pay.alipay.merchno", "").trim();
String def_sellerid = properties.getProperty("pay.alipay.sellerid", "").trim();
String def_charset = properties.getProperty("pay.alipay.charset", "UTF-8").trim();
String def_notifyurl = properties.getProperty("pay.alipay.notifyurl", "").trim();
String def_signcertkey = properties.getProperty("pay.alipay.signcertkey", "").trim();
String def_verifycertkey = properties.getProperty("pay.alipay.verifycertkey", "").trim();
final Map map = new HashMap<>();
properties.keySet().stream().filter(x -> x.toString().startsWith("pay.alipay.") && x.toString().endsWith(".appid")).forEach(appid_key -> {
final String prefix = appid_key.toString().substring(0, appid_key.toString().length() - ".appid".length());
String appid = properties.getProperty(prefix + ".appid", def_appid).trim();
String merchno = properties.getProperty(prefix + ".merchno", def_merchno).trim();
String sellerid = properties.getProperty(prefix + ".sellerid", def_sellerid).trim();
String charset = properties.getProperty(prefix + ".charset", def_charset).trim();
String notifyurl = properties.getProperty(prefix + ".notifyurl", def_notifyurl).trim();
String signcertkey = properties.getProperty(prefix + ".signcertkey", def_signcertkey).trim();
String verifycertkey = properties.getProperty(prefix + ".verifycertkey", def_verifycertkey).trim();
if (appid.isEmpty() || merchno.isEmpty() || notifyurl.isEmpty() || signcertkey.isEmpty()) {
logger.log(Level.WARNING, properties + "; has illegal alipay conf by prefix" + prefix);
return;
}
AliPayElement element = new AliPayElement();
element.appid = appid;
element.merchno = merchno;
element.sellerid = sellerid.isEmpty() ? merchno : sellerid;
element.charset = charset;
element.notifyurl = notifyurl;
element.signcertkey = signcertkey;
element.verifycertkey = verifycertkey;
if (element.initElement(logger, null)) {
map.put(appid, element);
if (def_appid.equals(appid)) map.put("", element);
}
});
return map;
}
@Override
public boolean initElement(Logger logger, File home) {
try {
final KeyFactory factory = KeyFactory.getInstance("RSA");
if (this.verifycertkey != null && !this.verifycertkey.isEmpty()) {
this.pubKey = factory.generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(this.verifycertkey)));
} else {
this.pubKey = null;
}
PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(this.signcertkey));
this.priKey = factory.generatePrivate(priPKCS8);
return true;
} catch (Exception e) {
logger.log(Level.SEVERE, "init alipay sslcontext error", e);
return false;
}
}
@Override
public String toString() {
return JsonConvert.root().convertTo(this);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy