行业知识
Java接入支付宝支付功能(APP支付)
客户就是上帝这句话一点都没错, 之前开发的一个专门接支付的项目,起初项目原型是接入支付宝支付,用的是客户公司的商户号. 后来因为种种原因导致客户商户号出现问题,联系我们看能不能把支付宝支付换成微信支付. 客户要求嘛, 改吧. 就把项目的支付从支付宝换到了微信.
本以为到此就结束了,没想到前几天 客户又要求换回支付宝,只不过用我们公司的商户.具体原因也说不清,反正就是不用微信支付了,又要换回支付宝, 没办法 改呗.
但是这次换支付宝我整整写了一个下午, 开发期间有几个需要注意的坑, 下面会说.
首先准备工作:
一、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文档
https://open.alipay.com/api
进去之后会有一些示例什么的,大家看看就行,要根据自己项目实际情况来看 需要对接哪些API, 我们只是对接了一个支付功能,目前退款和其他的API还没有接入 所以这期只写一下关于支付的功能.
点击查看文档进去之后会看到一些公共参数和请求参数
要想完整的接入支付宝,请求参数是必须规范的,一定要仔细看文档,懂了之后 再去写代码.
四、完善代码
ps:我们是SSM项目,没有用到pom,大家用boot项目的话,下面是sdk依赖,我们只是下的jar包
依赖:
com.alipay.sdk alipay-sdk-java 4.9.28.ALL
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 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 convertRequestParamsToMap(HttpServletRequest request) { Map retMap = new HashMap(); Set entrySet = request.getParameterMap().entrySet(); for (Entry 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 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 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 AlipaySignature.rsaCheckV1 调用支付宝验签方法时报的错
好了感谢大家收看
上一篇:不能发招聘信息吗
下一篇:最右怎么关服务器了?