一、业务需求
实现APP微信、支付宝支付,后端需要做生成预支付单,响应支付结果;微信商户采用子商户模式
二、参考官方文档
微信普通商户:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1
微信服务商:https://pay.weixin.qq.com/wiki/doc/api/app/app_sl.php?chapter=9_1
支付宝API开发文档:https://docs.open.alipay.com/api_1/alipay.trade.app.pay
三、准备工作
在公众平台完成入驻等操作,拿到以下信息
普通商户:
参数 | API参数名 | 详细说明 |
APPID | appid | appid是微信公众账号或开放平台APP的唯一标识,在公众平台申请公众账号或者在开放平台申请APP账号后,微信会自动分配对应的appid,用于标识该应用。可在微信公众平台-->开发者中心查看,商户的微信支付审核通过邮件中也会包含该字段值。 |
微信支付商户号 | mch_id | 商户申请微信支付后,由微信支付分配的商户收款账号。 |
API密钥 | key | 交易过程生成签名的密钥,仅保留在商户系统和微信支付后台,不会在网络中传播。商户妥善保管该Key,切勿在网络中传输,不能在其他客户端中存储,保证key不会被泄漏。商户可根据邮件提示登录微信商户平台进行设置。也可按以下路径设置:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置 |
Appsecret | secret | AppSecret是APPID对应的接口密码,用于获取接口调用凭证access_token时使用。 |
服务商:
参数 | API参数名 | 详细说明 |
受理商户的APPID | appid | appid是微信公众账号或开放平台APP的唯一标识,在公众平台申请公众账号或者在开放平台申请APP账号后,微信会自动分配对应的appid,用于标识该应用。可在微信公众平台-->开发者中心查看,商户的微信支付审核通过邮件中也会包含该字段值。 |
受理商户的商户号 | mch_id | 受理商户申请微信支付后,由微信支付分配的商户账号。 |
受理商户的API密钥 | key | 交易过程生成签名的密钥,仅保留在商户系统和微信支付后台,不会在网络中传播。商户妥善保管该Key,切勿在网络中传输,不能在其他客户端中存储,保证key不会被泄漏。商户可根据邮件提示登录微信商户平台进行设置。也可按一下路径设置:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置 |
子商户商户号 | sub_mch_id | 子商户申请的商户号或商户识别码 |
子商户应用APPID | sub_appid | 子商户在开放平台申请的应用APPID |
商户号应用的Appsecret | secret | AppSecret是子商户应用APPID对应的接口密码,用于获取接口调用凭证access_token时使用。 |
如使用子商户,需要先进行授权
支付宝参考文档获取相关参数
四、代码
--微信
API常量类
public class TransferConstants {
/** =============================================微信================================================*/
/**统一下单API*/
public static final String UNIFIEDORDER_PAY = "https://api.mch.weixin.qq.com/pay/unifiedorder";
/**企业付款API*/
public static final String TRANSFERS_PAY = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";
/**申请退款API*/
public static final String REFUND_PAY = "https://api.mch.weixin.qq.com/secapi/pay/refund";
/**企业付款查询API*/
public static final String TRANSFERS_PAY_QUERY = "https://api.mch.weixin.qq.com/mmpaymkttransfers/gettransferinfo";
/**公众账号appid*/
public static final String APP_ID = "" ;
/**商户号*/
public static final String MCH_ID = "";
/**API密钥 填服务商的*/
public static final String API_SECRET = "";
/**子商户应用ID*/
public static final String SUB_APPID = "";
/**子商户号 */
public static final String SUB_MCH_ID = "";
/** =============================================支付宝================================================*/
/**支付宝网关(固定)*/
public static final String URL = "https://openapi.alipay.com/gateway.do";
/**APPID 即创建应用后生成*/
public static final String APPID = "";
/**开发者私钥,由开发者自己生成*/
public static final String APP_PRIVATE_KEY = "";
/**参数返回格式,只支持 json*/
public static final String FORMAT = "json";
/**编码集,支持 GBK/UTF-8*/
public static final String CHARSET = "UTF-8";
/**支付宝公钥,由支付宝生成)*/
public static final String ALIPAY_PUBLIC_KEY = "";
/**商户生成签名字符串所使用的签名算法类型,目前支持 RSA2 和 RSA,推荐使用 RSA2*/
public static final String SIGN_TYPE = "RSA2";
}
在线上支付时,遇到了支付后微信那边不能回调,后来写了一个主动回调的方法
首先在application.yml配置支付回调地址
wxAndAli:
aliNotifyUrl: http://***:8082/app/appPay/aliPayNotify
wxNotifyUrl: http://***:8082/app/appPay/wxPayNotify
获取支付回调地址
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* 支付回调地址
*/
@Component
public class PayConstants {
public static String aliNotifyUrl;
public static String wxNotifyUrl;
@Value("${wxAndAli.aliNotifyUrl}")
public void setAliNotifyUrl(String aliNotifyUrl) {
this.aliNotifyUrl = aliNotifyUrl;
}
@Value("${wxAndAli.wxNotifyUrl}")
public void setWxNotifyUrl(String wxNotifyUrl) {
this.wxNotifyUrl = wxNotifyUrl;
}
}
JSON数据处理类
public class JSONObject extends LinkedHashMap<String, Object>
{
private static final long serialVersionUID = 1L;
private static final Pattern arrayNamePattern = Pattern.compile("(\\w+)((\\[\\d+\\])+)");
private static final ObjectMapper objectMapper = new ObjectMapper();
public static String valueAsStr(Object value)
{
if (value instanceof String)
{
return (String) value;
}
else if (value != null)
{
return value.toString();
}
else
{
return null;
}
}
}
支付工具类
import com.github.pagehelper.util.StringUtil;
import com.spa.common.utils.security.Md5Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;
/**
* @author JC
* @version 创建时间:2019/5/6 14:13
*/
public class PayUtil {
private static final Logger logger = LoggerFactory.getLogger(PayUtil.class);
/**
* 生成订单号
*
* @return
*/
public static String getOrderNo() {
// 自增8位数 00000001
return "ONO" + DateUtils.dateTimeNow() + "00000001";
}
/**
* 退款单号
*
* @return
*/
public static String getRefundNo() {
// 自增8位数 00000001
return "RNO" + DateUtils.dateTimeNow() + "00000001";
}
/**
* 交易单号
*
* @return
*/
public static String getTransferNo() {
// 自增8位数 00000001
return "TNO" + DateUtils.dateTimeNow() + "00000001";
}
/**
* 支付单号
*
* @return
*/
public static String getPayNo() {
// 自增8位数 00000001
return "PNO" + DateUtils.dateTimeNow() + "00000001";
}
/**
* 门店进账单号
*
* @return
*/
public static String getReceiptNo() {
// 自增8位数 00000001
return "SRNO" + DateUtils.dateTimeNow() + "00000001";
}
/**
* 参团编号
*
* @return
*/
public static String getGroupNo() {
// 自增8位数 00000001
return "GNO" + DateUtils.dateTimeNow() + "00000001";
}
/**
* 返回客户端ip
*
* @param request
* @return
*/
public static String getRemoteAddrIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (StringUtil.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
// 多次反向代理后会有多个ip值,第一个ip才是真实ip
int index = ip.indexOf(",");
if (index != -1) {
return ip.substring(0, index);
} else {
return ip;
}
}
ip = request.getHeader("X-Real-IP");
if (StringUtil.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
return ip;
}
return request.getRemoteAddr();
}
/**
* 获取服务器的ip地址
*
* @param request
* @return
*/
public static String getLocalIp(HttpServletRequest request) {
return request.getLocalAddr();
}
/**
* 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序)
* @param params
* @return
*/
public static Map<Object,Object> formatASCIIMap(Map<String, String> params){
Map<String, String> tmpMap = params;
Map<Object, Object> params1 = new LinkedHashMap<>();
List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>(tmpMap.entrySet());
//对所有传入参数按照字段名的ASCII码从小到大排序(字典序)
Collections.sort(infoIds, new Comparator<Map.Entry<String, String>>() {
public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) {
return (o1.getKey()).toString().compareTo(o2.getKey());
}
});
for (Map.Entry<String, String> item : infoIds) {
if (StringUtils.isNotBlank((CharSequence) item.getKey())) {
String key = item.getKey();
String value = item.getValue();
params1.put(key,value);
}
}
return params1;
}
public static String getSign(Map<Object, Object> params, String paternerKey) throws UnsupportedEncodingException {
return Md5Utils.MD5Encode(createSign(params, false) + "&key=" + paternerKey).toUpperCase();
}
/**
* 退款时生成签名
* @param params
* @param paternerKey
* @return
* @throws UnsupportedEncodingException
*/
public static String getRefundSign(Map<Object, Object> params, String paternerKey) throws UnsupportedEncodingException {
return Md5Utils.MD5Encode(createRefundSign(params, false) + "&key=" + paternerKey).toUpperCase();
}
/**
* 构造签名
*
* @param params
* @param encode
* @return
* @throws UnsupportedEncodingException
*/
public static String createSign(Map<Object, Object> params, boolean encode) throws UnsupportedEncodingException {
Set<Object> keysSet = params.keySet();
Object[] keys = keysSet.toArray();
Arrays.sort(keys);
StringBuffer temp = new StringBuffer();
boolean first = true;
for (Object key : keys) {
if (key == null || StringUtils.isNull(params.get(key))) // 参数为空不参与签名
continue;
if (first) {
first = false;
} else {
temp.append("&");
}
temp.append(key).append("=");
Object value = params.get(key);
String valueStr = "";
if (null != value) {
valueStr = value.toString();
}
if (encode) {
temp.append(URLEncoder.encode(valueStr, "UTF-8"));
} else {
temp.append(valueStr);
}
}
return temp.toString();
}
/**
* 构造退款时签名
*
* @param params
* @param encode
* @return
* @throws UnsupportedEncodingException
*/
public static String createRefundSign(Map<Object, Object> params, boolean encode) throws UnsupportedEncodingException {
Set<Object> keysSet = params.keySet();
Object[] keys = keysSet.toArray();
Arrays.sort(keys);
StringBuffer temp = new StringBuffer();
boolean first = true;
for (Object key : keys) {
if (key == null || StringUtils.isNull(params.get(key))) // 参数为空不参与签名
continue;
if (first) {
first = false;
} else {
temp.append("&");
}
temp.append(key).append("=");
Object value = params.get(key);
String valueStr = "";
if (null != value) {
valueStr = value.toString();
}
if (encode) {
temp.append(URLEncoder.encode(valueStr, "UTF-8"));
} else {
temp.append(valueStr);
}
}
return temp.toString();
}
/**
* 创建支付随机字符串
*
* @return
*/
public static String getNonceStr() {
return StringUtils.getRandomString(32);
}
/**
* 支付时间戳
*
* @return
*/
public static String payTimestamp() {
return Long.toString(System.currentTimeMillis() / 1000);
}
}
xml、map转换工具类
public class XmlUtil {
private static final String PREFIX_XML = "<xml>";
private static final String SUFFIX_XML = "</xml>";
private static final String PREFIX_CDATA = "<![CDATA[";
private static final String SUFFIX_CDATA = "]]>";
/**
* 转化成xml, 单层无嵌套
*
* @param parm
* @param isAddCDATA
* @return
*/
public static String xmlFormat(Map<String, String> parm, boolean isAddCDATA) {
StringBuffer strbuff = new StringBuffer(PREFIX_XML);
if (CollectionUtil.isNotEmpty(parm)) {
for (Entry<String, String> entry : parm.entrySet()) {
strbuff.append("<").append(entry.getKey()).append(">");
if (isAddCDATA) {
strbuff.append(PREFIX_CDATA);
if (StringUtil.isNotEmpty(entry.getValue())) {
strbuff.append(entry.getValue());
}
strbuff.append(SUFFIX_CDATA);
} else {
if (StringUtil.isNotEmpty(entry.getValue())) {
strbuff.append(entry.getValue());
}
}
strbuff.append("</").append(entry.getKey()).append(">");
}
}
return strbuff.append(SUFFIX_XML).toString();
}
/**
* Object,转化成xml
* @param parm
* @param isAddCDATA
* @return
*/
public static String xmlFormatObject(Map<Object, Object> parm, boolean isAddCDATA) {
StringBuffer strbuff = new StringBuffer(PREFIX_XML);
if (CollectionUtil.isNotEmpty(parm)) {
for (Entry<Object, Object> entry : parm.entrySet()) {
strbuff.append("<").append(entry.getKey()).append(">");
if (isAddCDATA) {
strbuff.append(PREFIX_CDATA);
if (StringUtils.isNotNull(entry.getValue())) {
strbuff.append(entry.getValue());
}
strbuff.append(SUFFIX_CDATA);
} else {
if (StringUtils.isNotNull(entry.getValue())) {
strbuff.append(entry.getValue());
}
}
strbuff.append("</").append(entry.getKey()).append(">");
}
}
return strbuff.append(SUFFIX_XML).toString();
}
/**
* 解析xml
*
* @param xml
* @return
* @throws XmlPullParserException
* @throws IOException
*/
public static Map<String, String> xmlParse(String xml) throws XmlPullParserException, IOException {
Map<String, String> map = null;
if (StringUtil.isNotEmpty(xml)) {
InputStream inputStream = new ByteArrayInputStream(xml.getBytes());
XmlPullParser pullParser = XmlPullParserFactory.newInstance().newPullParser();
pullParser.setInput(inputStream, "UTF-8"); // 为xml设置要解析的xml数据
int eventType = pullParser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
switch (eventType) {
case XmlPullParser.START_DOCUMENT:
map = new HashMap<String, String>();
break;
case XmlPullParser.START_TAG:
String key = pullParser.getName();
if (key.equals("xml"))
break;
String value = pullParser.nextText().trim();
map.put(key, value);
break;
case XmlPullParser.END_TAG:
break;
}
eventType = pullParser.next();
}
}
return map;
}
/**
* Object,解析xml
* @param xml
* @return
* @throws XmlPullParserException
* @throws IOException
*/
public static Map<Object, Object> xmlParseObject(String xml) throws XmlPullParserException, IOException {
Map<Object, Object> map = null;
if (StringUtil.isNotEmpty(xml)) {
InputStream inputStream = new ByteArrayInputStream(xml.getBytes());
XmlPullParser pullParser = XmlPullParserFactory.newInstance().newPullParser();
pullParser.setInput(inputStream, "UTF-8"); // 为xml设置要解析的xml数据
int eventType = pullParser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
switch (eventType) {
case XmlPullParser.START_DOCUMENT:
map = new HashMap<Object, Object>();
break;
case XmlPullParser.START_TAG:
String key = pullParser.getName();
if (key.equals("xml"))
break;
String value = pullParser.nextText().trim();
map.put(key, value);
break;
case XmlPullParser.END_TAG:
break;
}
eventType = pullParser.next();
}
}
return map;
}
}
Md5加密工具类
public class Md5Utils
{
private static final Logger log = LoggerFactory.getLogger(Md5Utils.class);
private static byte[] md5(String s)
{
MessageDigest algorithm;
try
{
algorithm = MessageDigest.getInstance("MD5");
algorithm.reset();
algorithm.update(s.getBytes("UTF-8"));
byte[] messageDigest = algorithm.digest();
return messageDigest;
}
catch (Exception e)
{
log.error("MD5 Error...", e);
}
return null;
}
private static final String toHex(byte hash[])
{
if (hash == null)
{
return null;
}
StringBuffer buf = new StringBuffer(hash.length * 2);
int i;
for (i = 0; i < hash.length; i++)
{
if ((hash[i] & 0xff) < 0x10)
{
buf.append("0");
}
buf.append(Long.toString(hash[i] & 0xff, 16));
}
return buf.toString();
}
public static String hash(String s)
{
try
{
return new String(toHex(md5(s)).getBytes("UTF-8"), "UTF-8");
}
catch (Exception e)
{
log.error("not supported charset...{}", e);
return s;
}
}
public static String MD5Encode(String s) {
try {
// 得到一个信息摘要器
MessageDigest digest = MessageDigest.getInstance("md5");
byte[] result = digest.digest(s.getBytes());
StringBuffer buffer = new StringBuffer();
// 把每一个byte 做一个与运算 0xff;
for (byte b : result) {
// 与运算
int number = b & 0xff;// 加盐
String str = Integer.toHexString(number);
if (str.length() == 1) {
buffer.append("0");
}
buffer.append(str);
}
// 标准的md5加密后的结果
return buffer.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return "";
}
}
private static String byteArrayToHexString(byte b[]) {
StringBuffer resultSb = new StringBuffer();
for (int i = 0; i < b.length; i++)
resultSb.append(byteToHexString(b[i]));
return resultSb.toString();
}
private static String byteToHexString(byte b) {
int n = b;
if (n < 0)
n += 256;
int d1 = n / 16;
int d2 = n % 16;
return hexDigits[d1] + hexDigits[d2];
}
public static String MD5Encode(String origin, String charsetname) {
String resultString = null;
try {
resultString = new String(origin);
MessageDigest md = MessageDigest.getInstance("MD5");
if (charsetname == null || "".equals(charsetname))
resultString = byteArrayToHexString(md.digest(resultString
.getBytes()));
else
resultString = byteArrayToHexString(md.digest(resultString
.getBytes(charsetname)));
} catch (Exception exception) {
}
return resultString;
}
private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
}
微信签名相关工具类
@Slf4j
public class WXPayUtil1 {
private static final Logger log = LoggerFactory.getLogger(WXPayUtil1.class);
public static Map<Object, Object> wxPay(Map map) {
Map<Object, Object> resultMap = new HashMap<>();
Map<String, String> parameters = null;
try {
parameters = new LinkedHashMap<>();
parameters.put("appid", TransferConstants.APP_ID); //公众账号appid
parameters.put("mch_id", TransferConstants.MCH_ID); //商户号
parameters.put("sub_appid", TransferConstants.SUB_APPID); //子商户应用ID
parameters.put("sub_mch_id", TransferConstants.SUB_MCH_ID); //子商户号
parameters.put("nonce_str", PayUtil.getNonceStr()); //随机字符串
parameters.put("out_trade_no", (String) map.get("transferNo")); //商户订单号
parameters.put("total_fee", (String)map.get("money")); //转账金额
parameters.put("body", (String) map.get("desc")); //企业付款描述信息
parameters.put("spbill_create_ip", (String) map.get("ip")); //Ip地址
parameters.put("notify_url", PayConstants.wxNotifyUrl); //回调通知地址
parameters.put("trade_type", "APP");
// String sign = PayUtil.getSign(parameters, TransferConstants.WX_KEY);
Map<String, String> tmpMap = parameters;
Map<Object,Object> params1 = PayUtil.formatASCIIMap(tmpMap);
//生成sign
String sign = PayUtil.getSign(params1, TransferConstants.API_SECRET);
parameters.put("sign", sign);
Map<String, String> tmpMap2 = parameters;
Map<Object,Object> params2 = PayUtil.formatASCIIMap(tmpMap2);
//生成签名
resultMap = createSecondSign(params2);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return resultMap;
}
/**
* 微信支付签名算法sign
*
* @param parameters
* @return
*/
public static Map<Object, Object> createSecondSign(Map<Object, Object> parameters) {
Map<Object, Object> resultMap = new HashMap<>();
//微信支付
String xml = XmlUtil.xmlFormatObject(parameters, false);
log.info("----签名-------"+ xml);
String restxml = HttpUtilA.posts(TransferConstants.UNIFIEDORDER_PAY, XmlUtil.xmlFormatObject(parameters, false));
log.info("----发送签名-------"+ restxml);
Map<String, String> returnMap = null;
try {
returnMap = XmlUtil.xmlParse(restxml);
log.info("-----------"+ JSONObject.valueAsStr(returnMap));
// 通信标识和交易标识都为SUCCESS
if (CollectionUtil.isNotEmpty(returnMap) && "SUCCESS".equals(returnMap.get("return_code")) && "SUCCESS".equals(returnMap.get("result_code"))) {
SortedMap<Object,Object> parameterMap = new TreeMap<Object,Object>();
String prepayid = returnMap.get("prepay_id");// 预支付交易会话标识
String noncestr = returnMap.get("nonce_str");// 微信返回的随机字符串
parameterMap.put("appid", TransferConstants.SUB_APPID);
parameterMap.put("partnerid", TransferConstants.SUB_MCH_ID);
parameterMap.put("prepayid", prepayid);
parameterMap.put("package", "Sign=WXPay");
parameterMap.put("noncestr", noncestr);
parameterMap.put("timestamp", PayUtil.payTimestamp());
//生成sign
String sign = PayUtil.getSign(parameterMap, TransferConstants.API_SECRET);
parameterMap.put("sign", sign);
log.info("-----------"+ JSONObject.valueAsStr(parameterMap));
resultMap.put("code", "200");
resultMap.put("msg", parameterMap);
}
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return resultMap;
}
/**
* <p>统一下单</p>
*
* @return
* @throws Exception
*/
public Map<Object, Object> pay(Map map) {
Map<Object, Object> resultMap = new HashMap<>();
Map<String, String> returnMap = null;
try {
returnMap = wxUnifieldOrder(map);
// 通信标识和交易标识都为SUCCESS
if (CollectionUtil.isNotEmpty(returnMap) && "SUCCESS".equals(returnMap.get("return_code")) && "SUCCESS".equals(returnMap.get("result_code"))) {
resultMap.put("returnCode", returnMap.get("return_code"));
resultMap.put("returnMsg", "OK");
resultMap.put("codeUrl", returnMap.get("code_url"));
resultMap.put("payMethod", "微信扫码支付");
} else {
resultMap.put("returnCode", returnMap.get("return_code"));
resultMap.put("returnMsg", returnMap.get("return_msg"));
}
} catch (Exception e) {
e.printStackTrace();
}
return resultMap;
}
/**
* <p>微信支付统一下单</p>
*
* @return
* @throws Exception
*/
private Map<String, String> wxUnifieldOrder(Map map) throws Exception {
//封装参数
SortedMap<String, String> parameters = new TreeMap<String, String>();
parameters.put("mch_appid", TransferConstants.APP_ID); //公众账号appid
parameters.put("mchid", TransferConstants.MCH_ID); //商户号
parameters.put("nonce_str", PayUtil.getNonceStr()); //随机字符串
parameters.put("partner_trade_no", (String) map.get("transferNo")); //商户订单号
parameters.put("openid", (String) map.get("openId")); //用户openid oCVr20N2YLH9VQztnkZTaCj2aYYY
parameters.put("check_name", "NO_CHECK"); //校验用户姓名选项 OPTION_CHECK
//parameters.put("re_user_name", "安迪"); //check_name设置为FORCE_CHECK或OPTION_CHECK,则必填
parameters.put("amount", (String) map.get("money")); //转账金额
parameters.put("desc", (String) map.get("desc")); //企业付款描述信息
parameters.put("spbill_create_ip", (String) map.get("ip")); //Ip地址
// parameters.put("notify_url", notifyUrl); //回调通知地址
parameters.put("trade_type", "APP"); //支付类型
parameters.put("sign", createSign(parameters, ""));
//转换为xml
String xmlData = mapToXml(parameters);
//请求微信后台
String resXml = HttpUtilA.posts(TransferConstants.UNIFIEDORDER_PAY, xmlData);
log.info("【微信支付】 统一下单响应:\n" + resXml);
return xmlStrToMap(resXml);
}
/**
* Map转换为 Xml
*
* @return Xml
* @throws Exception
*/
public static String mapToXml(SortedMap<String, String> map) throws Exception {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
//防止XXE攻击
documentBuilderFactory.setXIncludeAware(false);
documentBuilderFactory.setExpandEntityReferences(false);
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
org.w3c.dom.Document document = documentBuilder.newDocument();
org.w3c.dom.Element root = document.createElement("xml");
document.appendChild(root);
for (String key : map.keySet()) {
String value = map.get(key);
if (value == null) {
value = "";
}
value = value.trim();
org.w3c.dom.Element filed = document.createElement(key);
filed.appendChild(document.createTextNode(value));
root.appendChild(filed);
}
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
DOMSource source = new DOMSource(document);
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
transformer.transform(source, result);
String output = writer.getBuffer().toString();
try {
writer.close();
} catch (Exception ex) {
}
return output;
}
/**
* 创建签名Sign
*
* @param key
* @param parameters
* @return
*/
public static String createSign(SortedMap<String, String> parameters, String key) {
StringBuffer sb = new StringBuffer();
Set es = parameters.entrySet();
Iterator<?> it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
if (entry.getValue() != null || !"".equals(entry.getValue())) {
String v = String.valueOf(entry.getValue());
if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
}
sb.append("key=" + key);
String sign = MD5Util.MD5Encode(sb.toString(), "UTF-8").toUpperCase();
return sign;
}
/**
* Xml字符串转换为Map
*
* @param xmlStr
* @return
*/
public static Map<String, String> xmlStrToMap(String xmlStr) {
Map<String, String> map = new HashMap<String, String>();
Document doc;
try {
doc = DocumentHelper.parseText(xmlStr);
Element root = doc.getRootElement();
List children = root.elements();
if (children != null && children.size() > 0) {
for (int i = 0; i < children.size(); i++) {
Element child = (Element) children.get(i);
map.put(child.getName(), child.getTextTrim());
}
}
} catch (DocumentException e) {
e.printStackTrace();
}
return map;
}
}
微信异步请求处理
/**
* 微信异步请求逻辑处理
*
* @param inStream
* @return
*/
@Override
public ResultData wxNotify(InputStream inStream) {
int code = -1;
Map<String, String> return_data = new HashMap<String, String>();
try {
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
String resultxml = new String(outSteam.toByteArray(), "utf-8");
Map<String, String> params = XmlUtil.xmlParse(resultxml);
outSteam.close();
inStream.close();
if (!PayCommonUtil.isTenpaySign("utf-8", params)) {
// 支付失败
return_data.put("return_code", "FAIL");
return_data.put("return_msg", "return_code不正确");
code = 4;
} else {
System.out.println("===============付款成功==============");
String outTradeNo = String.valueOf(Long.parseLong(params.get("out_trade_no").split("O")[0]));
// 查询订单
AppOrders appOrders = appOrdersService.selectAppOrdersByOrderNo(outTradeNo);
if ("0".equals(appOrders.getStatus())){
log.info("-----微信异步回调,修改订单状态--------------"+appOrders.toString());
String totalFee = params.get("total_fee");
Map map = new HashMap();
map.put("orderNo", appOrders.getOrderNo());
map.put("payIntegral", appOrders.getPayIntegral());
map.put("payMoney", totalFee);
map.put("payWay", "2");
map.put("discountMoney", appOrders.getDiscountMoney());
appOrdersService.updateOrderStatusToPay(map);
}
String transactionId = params.get("transaction_id");
// 只处理支付成功的订单: 修改交易表状态,支付成功
//更新交易表中状态
int returnResult = appTransactionHistoryService.updateTransactionHistoryByTransactionNo(outTradeNo, transactionId);
log.info("==================returnResult:" + returnResult);
if (returnResult > 0) {
log.info("==================更新交易表中状态成功:" + transactionId);
return_data.put("return_code", "SUCCESS");
return_data.put("return_msg", "OK");
code = 0;
} else {
return new ResultData(4, "更新交易表失败 !");
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (XmlPullParserException e) {
e.printStackTrace();
}
return new ResultData(code, XmlUtil.xmlFormat(return_data, false));
}
--支付宝
支付宝实体类
public class AlipaymentOrderDto {
//支付宝分配给开发者的应用Id
private String appId;
//通知时间:yyyy-MM-dd HH:mm:ss
private String notifyTime;
//交易创建时间:yyyy-MM-dd HH:mm:ss
private String gmtCreate;
//交易付款时间
private String gmtPayment;
//交易退款时间
private String gmtRefund;
//交易结束时间
private String gmtClose;
//支付宝的交易号
private String tradeNo;
//获取商户之前传给支付宝的订单号(商户系统的唯一订单号)
private String outTradeNo;
//商户业务号(商户业务ID,主要是退款通知中返回退款申请的流水号)
private String outBizNo;
//买家支付宝账号
private String buyerLogonId;
//卖家支付宝用户号
private String sellerId;
//卖家支付宝账号
private String sellerEmail;
//订单金额:本次交易支付的订单金额,单位为人民币(元)
private Double totalAmount;
//实收金额:商家在交易中实际收到的款项,单位为元
private Double receiptAmount;
//开票金额:用户在交易中支付的可开发票的金额
private Double invoiceAmount;
//付款金额:用户在交易中支付的金额
private Double buyerPayAmount;
// 获取交易状态
private String tradeStatus;
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public String getNotifyTime() {
return notifyTime;
}
public void setNotifyTime(String notifyTime) {
this.notifyTime = notifyTime;
}
public String getGmtCreate() {
return gmtCreate;
}
public void setGmtCreate(String gmtCreate) {
this.gmtCreate = gmtCreate;
}
public String getGmtPayment() {
return gmtPayment;
}
public void setGmtPayment(String gmtPayment) {
this.gmtPayment = gmtPayment;
}
public String getGmtRefund() {
return gmtRefund;
}
public void setGmtRefund(String gmtRefund) {
this.gmtRefund = gmtRefund;
}
public String getGmtClose() {
return gmtClose;
}
public void setGmtClose(String gmtClose) {
this.gmtClose = gmtClose;
}
public String getTradeNo() {
return tradeNo;
}
public void setTradeNo(String tradeNo) {
this.tradeNo = tradeNo;
}
public String getOutTradeNo() {
return outTradeNo;
}
public void setOutTradeNo(String outTradeNo) {
this.outTradeNo = outTradeNo;
}
public String getOutBizNo() {
return outBizNo;
}
public void setOutBizNo(String outBizNo) {
this.outBizNo = outBizNo;
}
public String getBuyerLogonId() {
return buyerLogonId;
}
public void setBuyerLogonId(String buyerLogonId) {
this.buyerLogonId = buyerLogonId;
}
public String getSellerId() {
return sellerId;
}
public void setSellerId(String sellerId) {
this.sellerId = sellerId;
}
public String getSellerEmail() {
return sellerEmail;
}
public void setSellerEmail(String sellerEmail) {
this.sellerEmail = sellerEmail;
}
public Double getTotalAmount() {
return totalAmount;
}
public void setTotalAmount(Double totalAmount) {
this.totalAmount = totalAmount;
}
public Double getReceiptAmount() {
return receiptAmount;
}
public void setReceiptAmount(Double receiptAmount) {
this.receiptAmount = receiptAmount;
}
public Double getInvoiceAmount() {
return invoiceAmount;
}
public void setInvoiceAmount(Double invoiceAmount) {
this.invoiceAmount = invoiceAmount;
}
public Double getBuyerPayAmount() {
return buyerPayAmount;
}
public void setBuyerPayAmount(Double buyerPayAmount) {
this.buyerPayAmount = buyerPayAmount;
}
public String getTradeStatus() {
return tradeStatus;
}
public void setTradeStatus(String tradeStatus) {
this.tradeStatus = tradeStatus;
}
}
支付宝支付工具类
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeAppPayModel;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradeAppPayRequest;
import com.alipay.api.response.AlipayTradeAppPayResponse;
import com.spa.app.controller.app.constants.PayConstants;
import com.spa.app.controller.app.dto.AlipaymentOrderDto;
import com.spa.common.constant.TransferConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
public class AliPayUtil {
private static final Logger log = LoggerFactory.getLogger(AliPayUtil.class);
// 服务器异步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
// public static String notify_url = "http://工程公网访问地址/alipay.trade.page.pay-JAVA-UTF-8/notify_url.jsp";
// 页面跳转同步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
// public static String return_url = "http://工程公网访问地址/alipay.trade.page.pay-JAVA-UTF-8/return_url.jsp";
// 字符编码格式
public static String charset = "utf-8";
/**
* 支付
*
* @param map
* @return
*/
public static String pay(Map map) {
// public static JSONObject pay(Map map) {
//最终返回加签之后的,app需要传给支付宝app的订单信息字符串
String result = "";
// JSONObject result = new JSONObject();
try {
//商户订单号,商户网站订单系统中唯一订单号,必填
String out_trade_no = (String) map.get("orderNo");
//付款金额,必填
String total_amount = (String) map.get("money");
//订单名称,必填
String subject = (String) map.get("orderName");
//实例化客户端(参数:网关地址、商户appid、商户私钥、格式、编码、支付宝公钥、加密类型),为了取得预付订单信息
AlipayClient alipayClient = new DefaultAlipayClient(TransferConstants.URL, TransferConstants.APPID, TransferConstants.APP_PRIVATE_KEY, "json", charset, TransferConstants.ALIPAY_PUBLIC_KEY, TransferConstants.SIGN_TYPE);
//实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay
AlipayTradeAppPayRequest ali_request = new AlipayTradeAppPayRequest();
//SDK已经封装掉了公共参数,这里只需要传入业务参数。以下方法为sdk的model入参方式
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
//业务参数传入,可以传很多,参考API
//model.setPassbackParams(URLEncoder.encode(request.getBody().toString())); //公用参数(附加数据)
// model.setBody(orderTest.getBody()); //对一笔交易的具体描述信息。如果是多种商品,请将商品描述字符串累加传给body。
model.setSubject(subject); //商品名称
log.info("====================商户订单号:" + out_trade_no);
model.setOutTradeNo(out_trade_no); //商户订单号(自动生成)
// model.setTimeoutExpress("30m"); //交易超时时间
model.setTotalAmount(total_amount); //支付金额
// model.setProductCode("QUICK_MSECURITY_PAY"); //销售产品码(固定值)
ali_request.setBizModel(model);
log.info("====================异步通知的地址为:" + PayConstants.aliNotifyUrl);
ali_request.setNotifyUrl(PayConstants.aliNotifyUrl); //异步回调地址(后台)
// ali_request.setReturnUrl(return_url); //同步回调地址(APP)
// 这里和普通的接口调用不同,使用的是sdkExecute
AlipayTradeAppPayResponse alipayTradeAppPayResponse = alipayClient.sdkExecute(ali_request); //返回支付宝订单信息(预处理)
log.info("-------------" + JSONObject.toJSONString(alipayTradeAppPayResponse));
result = alipayTradeAppPayResponse.getBody();//就是orderString 可以直接给APP请求,无需再做处理。
if (alipayTradeAppPayResponse.isSuccess()) {
// log.info("调用成功");
// String sign = DigestUtils.md5Hex(out_trade_no).toUpperCase();
// result.put("code", "SUCCESS");
// result.put("out_trade_no", out_trade_no);
// result.put("sign", sign);
// result.put("orderStr", result);
// log.info(result.toJSONString());
// log.info(result.toString());
} else {
log.info("与支付宝交互出错,未能生成订单,请检查代码!");
return null;
}
} catch (AlipayApiException e) {
e.printStackTrace();
log.info("与支付宝交互出错,未能生成订单,请检查代码!");
}
return result;
}
/**
* 支付宝异步请求逻辑处理
*
* @return
*/
public static AlipaymentOrderDto notify(Map<String, String> conversionParams) {
AlipaymentOrderDto alipaymentOrder = new AlipaymentOrderDto();
log.info("==================支付宝异步请求逻辑处理");
//签名验证(对支付宝返回的数据验证,确定是支付宝返回的)
boolean signVerified = false;
try {
//调用SDK验证签名
signVerified = AlipaySignature.rsaCheckV1(conversionParams, TransferConstants.ALIPAY_PUBLIC_KEY, TransferConstants.CHARSET, TransferConstants.SIGN_TYPE);
} catch (AlipayApiException e) {
log.info("==================验签失败 !");
e.printStackTrace();
}
//对验签进行处理
if (signVerified) {
//验签通过
//获取需要保存的数据
String appId = conversionParams.get("app_id");//支付宝分配给开发者的应用Id
String notifyTime = conversionParams.get("notify_time");//通知时间:yyyy-MM-dd HH:mm:ss
String gmtCreate = conversionParams.get("gmt_create");//交易创建时间:yyyy-MM-dd HH:mm:ss
String gmtPayment = conversionParams.get("gmt_payment");//交易付款时间
String gmtRefund = conversionParams.get("gmt_refund");//交易退款时间
String gmtClose = conversionParams.get("gmt_close");//交易结束时间
String tradeNo = conversionParams.get("trade_no");//支付宝的交易号
String outTradeNo = conversionParams.get("out_trade_no");//获取商户之前传给支付宝的订单号(商户系统的唯一订单号)
String outBizNo = conversionParams.get("out_biz_no");//商户业务号(商户业务ID,主要是退款通知中返回退款申请的流水号)
String buyerLogonId = conversionParams.get("buyer_logon_id");//买家支付宝账号
String sellerId = conversionParams.get("seller_id");//卖家支付宝用户号
String sellerEmail = conversionParams.get("seller_email");//卖家支付宝账号
String totalAmount = conversionParams.get("total_amount");//订单金额:本次交易支付的订单金额,单位为人民币(元)
String receiptAmount = conversionParams.get("receipt_amount");//实收金额:商家在交易中实际收到的款项,单位为元
String invoiceAmount = conversionParams.get("invoice_amount");//开票金额:用户在交易中支付的可开发票的金额
String buyerPayAmount = conversionParams.get("buyer_pay_amount");//付款金额:用户在交易中支付的金额
String tradeStatus = conversionParams.get("trade_status");// 获取交易状态
//赋值
alipaymentOrder.setNotifyTime(notifyTime);
alipaymentOrder.setGmtCreate(gmtCreate);
alipaymentOrder.setGmtPayment(gmtPayment);
alipaymentOrder.setGmtRefund(gmtRefund);
alipaymentOrder.setGmtClose(gmtClose);
alipaymentOrder.setTradeNo(tradeNo);
alipaymentOrder.setOutTradeNo(outTradeNo);
alipaymentOrder.setOutBizNo(outBizNo);
alipaymentOrder.setBuyerLogonId(buyerLogonId);
alipaymentOrder.setSellerId(sellerId);
alipaymentOrder.setSellerEmail(sellerEmail);
alipaymentOrder.setTotalAmount(Double.parseDouble(totalAmount));
alipaymentOrder.setReceiptAmount(Double.parseDouble(receiptAmount));
alipaymentOrder.setInvoiceAmount(Double.parseDouble(invoiceAmount));
alipaymentOrder.setBuyerPayAmount(Double.parseDouble(buyerPayAmount));
alipaymentOrder.setTradeStatus(tradeStatus);
//支付宝官方建议校验的值(out_trade_no、total_amount、sellerId、app_id)
if (!TransferConstants.APPID.equals(appId)) {
return null;
}
}
return alipaymentOrder;
}
}
支付宝异步请求处理
/**
* 支付宝异步请求逻辑处理
*
* @param aliParams
* @return
*/
@Transactional
@Override
public ResultData notify(Map<String, String[]> aliParams) {
//用以存放转化后的参数集合
Map<String, String> conversionParams = new HashMap<String, String>();
for (Iterator<String> iter = aliParams.keySet().iterator(); iter.hasNext(); ) {
String key = iter.next();
String[] values = aliParams.get(key);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
}
// 乱码解决,这段代码在出现乱码时使用。如果mysign和sign不相等也可以使用这段代码转化
// valueStr = new String(valueStr.getBytes("ISO-8859-1"), "uft-8");
log.info("---------------------" + valueStr);
conversionParams.put(key, valueStr);
}
log.info("==================返回参数集合:" + conversionParams);
AlipaymentOrderDto alipaymentOrder = AliPayUtil.notify(conversionParams);
if (alipaymentOrder != null) {
// 查询订单
AppOrders appOrders = appOrdersService.selectAppOrdersByOrderNo(alipaymentOrder.getOutTradeNo());
if ("0".equals(appOrders.getStatus())){
log.info("-----微信异步回调,修改订单状态--------------"+appOrders.toString());
Double totalFee = alipaymentOrder.getTotalAmount();
Map map = new HashMap();
map.put("orderNo", appOrders.getOrderNo());
map.put("payIntegral", appOrders.getPayIntegral());
map.put("payMoney", totalFee);
map.put("payWay", "1");
map.put("discountMoney", appOrders.getDiscountMoney());
appOrdersService.updateOrderStatusToPay(map);
}
//支付宝官方建议校验的值(out_trade_no、total_amount、sellerId、app_id)
if (alipaymentOrder.getTradeStatus().equals("TRADE_SUCCESS")) {
//只处理支付成功的订单: 修改交易表状态,支付成功
//更新交易表中状态
int returnResult = appTransactionHistoryService.updateTransactionHistoryByTransactionNo(alipaymentOrder.getOutTradeNo(), alipaymentOrder.getTradeNo());
log.info("==================returnResult:" + returnResult);
if (returnResult > 0) {
log.info("==================更新交易表中状态成功:" + alipaymentOrder.getTradeNo());
return ResultData.success();
} else {
return new ResultData(4, "更新交易表失败 !");
}
} else {
return new ResultData(4, "验签失败 !");
}
} else {
return new ResultData(4, "验签失败 !");
}
}
以上是支付的步骤,欢迎大家提供好的建议或问题!