客户就是上帝这句话一点都没错, 之前开发的一个专门接支付的项目,起初项目原型是接入支付宝支付,用的是客户公司的商户号. 后来因为种种原因导致客户商户号出现问题,联系我们看能不能把支付宝支付换成微信支付. 客户要求嘛, 改吧. 就把项目的支付从支付宝换到了微信.
本以为到此就结束了,没想到前几天 客户又要求换回支付宝,只不过用我们公司的商户.具体原因也说不清,反正就是不用微信支付了,又要换回支付宝, 没办法 改呗.
但是这次换支付宝我整整写了一个下午, 开发期间有几个需要注意的坑, 下面会说.
首先准备工作:
一、appid, 登录支付宝开放平台点击右上角控制台
选择你要用的商户,点击进去
进去之后在页面左上角有商户的信息, 我圈出来的位置就是你的appid
二、public_key和private_key 接口加签方式(密钥/证书)
开发设置 > 开发信息 > 接口加签方式(密钥/证书)> 设置。
配置流程:
-
设置加签方式,选择密钥->下一步
-
生成密钥文件
a.需要下载一个支付宝开放平台密钥工具.
b.打开密钥工具,进入生成密钥功能
c.加签方式选择密钥,加密算法选择RSA2.
d.点击生成密钥,工具会自动生成应用公钥(public_key)和应用私钥(private_key)点击打开文件位置就能找到生成的公私钥文件, 私钥一定妥善保管,代码里需要用也就是这个private_key
e.复制应用公钥字符串
f.返回开放平台控制台中,点击下一步
3.上传应用公钥,粘贴复制的应用公钥字符串,点击确认上传.此时需要输入短信验证码或支付密码完成安全验证.
4.密钥配置完成.
public_key就是你复制的应用公钥字符串,或者复制这个
private_key就是上面让你保存好的应用私钥
三、支付宝API文档
进去之后会有一些示例什么的,大家看看就行,要根据自己项目实际情况来看 需要对接哪些API, 我们只是对接了一个支付功能,目前退款和其他的API还没有接入 所以这期只写一下关于支付的功能.
点击查看文档进去之后会看到一些公共参数和请求参数
要想完整的接入支付宝,请求参数是必须规范的,一定要仔细看文档,懂了之后 再去写代码.
四、完善代码
ps:我们是SSM项目,没有用到pom,大家用boot项目的话,下面是sdk依赖,我们只是下的jar包
依赖:
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.9.28.ALL</version>
</dependency>
jar包:
https://pan.baidu.com/s/1bnmfYNRaDmLe27BFlN0gTA
提取码:1234 /没有
创建AlipayConfig类
public class AlipayConfig {
//2023/12/11 观塘新标准测试 商户appid
public static String APPID = "你的appid";
//支付宝私钥
public static String RSA_PRIVATE_KEY = "你的支付宝私钥";
//支付宝公钥
public static String ALIPAY_PUBLIC_KEY = "你的支付宝公钥";
// 服务器异步通知页面路径 需http://或者https://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
public static String off_line_notify_url ="支付宝异步通知请求地址,必须是外网可以访问的接口";
// 商户可以自定义同步跳转地址
public static String return_url = "无用";
// 线上请求网关地址
public static String URL = "https://openapi.alipay.com/gateway.do";
// 沙箱环境网关地址
// public static String URL = "https://openapi.alipaydev.com/gateway.do";
// 编码
public static String CHARSET = "UTF-8";
// public static String CHARSET = "GBK";
// 返回格式
public static String FORMAT = "json";
// 日志记录目录
public static String log_path = "/log";
// RSA2
public static String SIGNTYPE = "RSA2";
}
下单接口
String doctor_spwz_money = UserService.AdvisoryCodePrice();
String tradeno = getTradeNo();
if (doctor_spwz_money.equals("0") || null == doctor_spwz_money) {
map.put("msg", "金额不能为0");
map.put("code", "-5");
return map;
}
map.put("code_pay_userid", code_pay_userid);
map.put("code_money", doctor_spwz_money);
map.put("username", username);
map.put("tradeno", tradeno);
//创建订单
int i = UserService.insertOrderinfo(map);
//构建支付宝返回类
AlipayTradeAppPayResponse response = null;
// 实例化客户端
AlipayClient alipayClient = new DefaultAlipayClient(
"https://openapi.alipay.com/gateway.do", AlipayConfig.APPID,
AlipayConfig.RSA_PRIVATE_KEY, "json", AlipayConfig.CHARSET,
AlipayConfig.ALIPAY_PUBLIC_KEY, "RSA2");
// 实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
//构建请求参数
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", tradeno);
bizContent.put("total_amount", doctor_spwz_money);
bizContent.put("subject", subject);
bizContent.put("product_code", "QUICK_MSECURITY_PAY");
request.setNotifyUrl(AlipayConfig.off_line_notify_url);
request.setBizContent(bizContent.toString());
try {
//这里和普通的接口调用不同,使用的是sdkExecute
response = alipayClient.sdkExecute(request);
System.out.println("---------------------" + response.getBody() + "---------------------");// 就是orderString
// 可以直接给客户端请求,无需再做处理。
} catch (AlipayApiException e) {
e.printStackTrace();
}
if (response.isSuccess()) {
map.put("code_money",doctor_spwz_money);
map.put("data", response.getBody());
map.put("code", "200");
map.put("msg", "下单成功");
return map;
}else{
map.put("code", "5000");
map.put("msg", "下单失败");
return map;
}
public static String getTradeNo() {
// 自增8位数 00000001
return "TNO" + formatDate(new Date(), "yyyyMMddHHmmssSSS") + "00000001";
}
//自己定义随机字符串就行
异步通知回调接口
// 异步回调
@SuppressWarnings({ "unused", "rawtypes" })
@RequestMapping(value = "NotifyUrl", method = RequestMethod.POST)
@ResponseBody
public String NotifyUrl(HttpServletRequest request) throws IOException {
System.out.println("******************************************************NotifyUrl***************************************************");
final Map<String, String> params = convertRequestParamsToMap(request); // 将异步通知中收到的待验证所有参数都存放到map中
final String paramsJson = JSON.toJSONString(params);
logger.info("支付宝回调,{}" , paramsJson);
try {
AlipayConfig alipayConfig = new AlipayConfig();// 支付宝配置
// 调用SDK验证签名
boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.ALIPAY_PUBLIC_KEY,
AlipayConfig.CHARSET, AlipayConfig.SIGNTYPE);
if (signVerified) {
logger.info("支付宝回调签名认证成功");
// 按照支付结果异步通知中的描述,对支付结果中的业务内容进行二次校验,校验成功后在response中返回success,校验失败返回failure
this.check(params);
// 另起线程处理业务
executorService.execute(new Runnable() {
@Override
public void run() {
AlipayNotifyParam param = buildAlipayNotifyParam(params);
String trade_status = param.getTrade_status();
// 支付成功
if (trade_status.equals("TRADE_SUCCESS")
|| trade_status.equals("TRADE_FINISHED")) {
// 处理支付成功逻辑
try {
String out_trade_no = param.getOut_trade_no();
String trade_no = param.getTrade_no();
int i = UserService.udcode_paystatus(out_trade_no, trade_no);
if (i != 1) {
logger.info("订单支付成功,但信息保存失败");
} else {
logger.info("success");
}
} catch (Exception e) {
logger.error("支付宝回调业务处理报错,params:" + paramsJson, e);
}
} else {
logger.error("没有处理支付宝回调业务,支付宝交易状态:{},params:{}",trade_status,paramsJson);
}
}
});
//验签成功之后 直接return success
logger.info("验签成功 return success,业务正在处理中");
return "success";
} else {
logger.info("支付宝回调签名认证失败,signVerified=false, paramsJson:{}", paramsJson);
return "failure";
}
} catch (AlipayApiException e) {
logger.error("支付宝回调签名认证失败,paramsJson:{},errorMsg:{}", paramsJson, e.getMessage());
return "failure";
}
}
// 将request中的参数转换成Map
private static Map<String, String> convertRequestParamsToMap(HttpServletRequest request) {
Map<String, String> retMap = new HashMap<String, String>();
Set<Entry<String, String[]>> entrySet = request.getParameterMap().entrySet();
for (Entry<String, String[]> entry : entrySet) {
String name = entry.getKey();
String[] values = entry.getValue();
int valLen = values.length;
if (valLen == 1) {
retMap.put(name, values[0]);
} else if (valLen > 1) {
StringBuilder sb = new StringBuilder();
for (String val : values) {
sb.append(",").append(val);
}
retMap.put(name, sb.toString().substring(1));
} else {
retMap.put(name, "");
}
}
return retMap;
}
private AlipayNotifyParam buildAlipayNotifyParam(Map<String, String> params) {
String json = JSON.toJSONString(params);
return JSON.parseObject(json, AlipayNotifyParam.class);
}
/**
* 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
* 2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
* 3、校验通知中的seller_id(或者seller_email)是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email),
* 4、验证app_id是否为该商户本身。上述1、2、3、4有任何一个验证不通过,则表明本次通知是异常通知,务必忽略。
* 在上述验证通过后商户必须根据支付宝不同类型的业务通知,正确的进行不同的业务处理,并且过滤重复的通知结果数据。
* 在支付宝的业务通知中,只有交易通知状态为TRADE_SUCCESS或TRADE_FINISHED时,支付宝才会认定为买家付款成功。
*
* @param params
* @throws AlipayApiException
*/
private void check(Map<String, String> params) throws AlipayApiException {
String outTradeNo = params.get("out_trade_no");
// 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
Order order = getOrderByOutTradeNo(outTradeNo);//这个自己写查询
if (order == null) {
throw new AlipayApiException("out_trade_no错误");
}
// 2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额)
if (!params.get("total_amount").toString().equals(order.getPayPrice().toString())) {
throw new AlipayApiException("error total_amount");
}
// 3、校验通知中的seller_id(或者seller_email)是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email),
// 第三步省略
// 4、验证app_id是否为该商户本身。
if (!params.get("app_id").equals(AlipayConfig.APPID)) {
throw new AlipayApiException("app_id不一致");
}
}
private Order getOrderByOutTradeNo(String outTradeNo){
return UserService.getOrderByOutTradeNo(outTradeNo);
}
到这代码就完成了.
代码中的import:
import java.io.IOException;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import javax.inject.Qualifier;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import net.sf.json.JSONArray;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.alibaba.fastjson.JSON;
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.ecg.pay.AlipayNotifyParam;
import com.ecg.service.WeChatPayService;
import com.ecg.util.AlipayConfig;
import com.ecg.util.ApplicationContextHelperUtil;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
import com.yc.pay.core.PayApiProvider;
import com.yc.pay.core.wechat.v3.WeChatApiProvider;
import com.yc.pay.core.wechat.v3.WeChatDirectPayApi;
import com.yc.pay.core.wechat.v3.entity.ResponseSignVerifyParams;
import com.yc.pay.vo.Amount;
import com.yc.pay.vo.PayParams;
总结: 其实接支付宝支付并不难,只是比较繁琐. 我遇到的问题就是在用户支付成功之后的异步通知回调接口时,处理请求体中的request.getParameterMap();
循环处理待验签字符串的时候,处理支付宝回调请求里的数据时,没有处理好,下面把出现问题的代码贴过来, 大家可以看一下具体问题出现在哪我就不做解释了!可以在评论区讨论一下
Map requestParams = request.getParameterMap();
for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext();) {
String name = (String) iter.next();
String[] values = (String[]) requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i]
: valueStr + values[i] + ",";
}
// 订单编号
out_trade_no = requestParams.get("out_trade_no");
// 订单交易号
trade_no = requestParams.get("trade_no");
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//
// 设置日期格式
String format = df.format(new Date());// new Date()为获取当前系统时间,支付时间
// 乱码解决,这段代码在出现乱码时使用。
// valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
param.put(name, valueStr);
}
// 获取支付宝POST过来反馈信息
Map<String, String> params = new HashMap<String, String>();
boolean flag = true;
try {
flag = AlipaySignature.rsaCheckV1(param,
AlipayConfig.ALIPAY_PUBLIC_KEY, AlipayConfig.CHARSET,
"RSA2");
} catch (AlipayApiException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
AlipaySignature.rsaCheckV1 调用支付宝验签方法时报的错
好了感谢大家收看