支付宝JAVA端支付与验签

前言


公司项目中需要用到支付宝开放平台实现支付功能,因为前端有多个平台,所以将关于支付的所有操作都集中在服务器端实现,给前端的由服务器端接好支付宝接口的黑盒暴露的接口。便于整理记忆,特摘此博客记录在其中踩过的坑。

使用的应用为手机网站支付2.0

链接

准备工作


密钥生成与上传

本人电脑为WIN10版本,因此使用WINDOWS版工具(2017/03/29),使用工具生成PKCS8密钥后,需要上传应用公钥到开放平台。开放平台->

坑1:由于工具上面的工具栏中的签名功能里生成的sign必须含有sing_type参数,与开放平台上面验证上传应用公钥正确性上的参数需求只有a=123不符合,因此该工具生成的签名无法在开放平台上上传应用公钥上的验证密钥正确性上成功通过。但是生成的是公钥与私钥是正确的。

上传应用公钥成功后会生成支付宝公钥,该公钥与应用公钥需要区分,并且自己保存好在服务器端。

tip1:开放平台上面写的RSA1与RSA2密钥与生成的密钥没有关系,只是不同的加签算法而已,在你需要验签以及自己对参数加签的时候使用不同的算法而已。官方推荐使用SHA256那么就将工具生成的应用公钥上传到RSA2(SHA256)密钥处即可。

SDK下载、代码分析

配置

本人使用 alipay-sdk-java20170307171631.jar 版本,从SDK中的pay.jsp中可以分析出,需要自己定义好AlipayClient所需的各种参数。可以参照SDK中的AlipayConfig:

public class AlipayConfig {
    // 商户appid
    public static String APPID = "";
    // 私钥 pkcs8格式的
    public static String RSA_PRIVATE_KEY = "";
    // 服务器异步通知页面路径 需http://或者https://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
    public static String notify_url = "http://商户网关地址/alipay.trade.wap.pay-JAVA-UTF-8/notify_url.jsp";
    // 页面跳转同步通知页面路径 需http://或者https://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问 商户可以自定义同步跳转地址
    public static String return_url = "http://商户网关地址/alipay.trade.wap.pay-JAVA-UTF-8/return_url.jsp";
    // 请求网关地址
    public static String URL = "https://openapi.alipay.com/gateway.do";
    // 编码
    public static String CHARSET = "UTF-8";
    // 返回格式
    public static String FORMAT = "json";
    // 支付宝公钥
    public static String ALIPAY_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjrEVFMOSiNJXaRNKicQuQdsREraftDA9Tua3WNZwcpeXeh8Wrt+V9JilLqSa7N7sVqwpvv8zWChgXhX/A96hEg97Oxe6GKUmzaZRNh0cZZ88vpkn5tlgL4mH/dhSr3Ip00kvM4rHq9PwuT4k7z1DpZAf1eghK8Q5BgxL88d0X07m9X96Ijd0yMkXArzD7jg+noqfbztEKoH3kPMRJC2w4ByVdweWUT2PwrlATpZZtYLmtDvUKG/sOkNAIKEMg3Rut1oKWpjyYanzDgS7Cg3awr1KPTl9rHCazk15aNYowmYtVabKwbGVToCAGK+qQ1gT3ELhkGnf3+h53fukNqRH+wIDAQAB";
    // 日志记录目录
    public static String log_path = "/log";
    // RSA2
    public static String SIGNTYPE = "RSA2";
}
  • URL、CHARSET为固定不需要更改
  • APPID为你签约应用的id
  • RSA_PRIVATE_KEY为工具生成的应用私钥,ALIPAY_PUBLIC_KEY为支付宝公钥而非应用公钥。
  • FORMAT为数据传输格式,本人使用的也是json因此照上
  • notify_url与return_url为自己服务器接口的地址,可以自己设置,官方说需要结尾必须notify_url,return_url。其实只是该链接不要有?&参数即可。

代码流程分析

具体代码就不贴出来,只记录一下支付宝的支付流程。

pay.jsp中当调用AlipayClient的pageExecute()方法的时候,会返回一个含有HTML标签的表单内容,该表单内容的作用是跳转到支付宝的支付页面,与服务器端的页面无关联,该表单可以自定义的放置在页面的任意位置,然而实际上跳转1s左右就会发生。

支付宝文档有区分前后回跳与异步通知,这点比微信支付只有异步通知上多了一个前后回跳。

前后回跳也就是支付成功后会自动跳转到已定义好的return_url地址,服务器端能收到支付宝传过来的支付结果里面的参数。服务器端需要对返回进行自己的后台操作,当支付成功了则更新流水,失败则提示用户失败信息。

异步通知在实际测试中,比前后回跳要更快发出,也就是说服务器端更先收到前后回跳,理论上是服务器的异步通知处理逻辑要比前后回跳更快。为了金钱的安全性,优先级中异步>前后。

前后回跳与异步通知需要注意幂等性。

前后回跳不需要验签,异步通知需要验签。

以上则是整个支付流程。

单元测试


支付

支付接口调用,参数使用SDK上的AlipayTradeWapPayModel,调起支付代码(不贴出具体的参数):

Long userId;
Long auctionId;
AlipayTradeWapPayModel payParam;

@Test
public void payInsurance() throws AlipayApiException {
        payParam.setDisablePayChannels("credit_group");  // 禁用信用卡渠道
        TransmissionResult getOrderInsuranceResult = userBidService.getOrderInsurance(userId, auctionId);
        AuctionItemVO auctionItemVO = (AuctionItemVO) auctionItemService.getAuctionITem(auctionId).getVO();
        payParam.setTotalAmount(auctionItemVO.getInsurance() + "");  // 从数据库提取金额
        if (!getOrderInsuranceResult.isSuccess()) {
            payParam.setOutTradeNo(VarPayParamUtils.generateOutTradeNo());         // 生成商户订单号
            // 创建保险订单
            OrderInsuranceVO orderInsuranceVO = new OrderInsuranceVO();
            orderInsuranceVO.setUserId(userId);
            orderInsuranceVO.setAuctionId(auctionId);
            orderInsuranceVO.setInsuranceStatus(InsuranceStatusEnum.WAITTING_PAY.getCode());    // -> 待支付状态
            orderInsuranceVO.setOutTradeNo(payParam.getOutTradeNo());
//            orderInsuranceVO.setInsurance(payParam.getTotal_amount());
            orderInsuranceVO.setInsurance(auctionItemVO.getInsurance() + "");
            userBidService.addOrderInsurance(orderInsuranceVO);
            // 添加概括表
            TradeGenerationDO tradeGenerationDO = new TradeGenerationDO();
            tradeGenerationDO.setUserId(userId);
            tradeGenerationDO.setAuctionId(auctionId);
            tradeGenerationDO.setOutTradeNo(payParam.getOutTradeNo());
            tradeGenerationDO.setModule(PayModuleEnum.INSURANCE_MODULE.getCode());
            userBidService.addTradeGeneration(tradeGenerationDO);
        } else {
            payParam.setOutTradeNo(((OrderInsuranceDO) getOrderInsuranceResult.getVO()).getOutTradeNo());
        }
        // 调用支付宝API支付
        AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
        request.setReturnUrl(alipayNeedUrl + "insurancePay/return_url");
        request.setNotifyUrl(alipayNeedUrl + "insurancePay/notify_url");
        request.setBizModel(payParam);
        String form = AlipayConfig.alipayClient.pageExecute(request).getBody();
        Assert.assertEquals(false,StringUtls.isEmpty(form));
        System.out.println(form);
    }

AlipayConfig.alipayClient则是以下代码静态生成维护:

    public static AlipayClient alipayClient = new DefaultAlipayClient(OPEN_URL,APP_ID,APP_PRIVATE_KEY,FORMAT,CHARSET,ALIPAY_PUBLIC_KEY,SIGN_TYPE);

验签

支付宝发送异步通知将参数发送给服务器端后,拿到数据进行验签单测。

以下是本人的数据并进行验签(可取出sign字段,将剩下的字符到工具中生成签名,再放回该字段进行验签,该字符串与自己的应用公钥匹配):

tip2:SDK似乎没有异步通知的参数DTO,需要自己定义类来接收数据。
tip3:SDK中的AlipaySignature中校验方法有rsaCheckV1与rsaCheckV2,用来区分SHA1与SHA256密钥的验证。实际上rsaCheckV1也可以验签sha256密钥。
tip4:使用rsaCheckV1验签sha256密钥时候,无论有没有sign_type都能验签通过,然而rsaCheckV2验签的时候,不能含有sign_type参数。

坑2:在我询问了技术人员后才知道,原来文档上漏了一个参数,参照文档上异步通知的参数是只有31个的,实际上少了auth_app_id,该参数值与app_id一致。

以下是验签代码:

        Map map = new HashMap();
        String resultInfo ="gmt_create=2017-06-27 15:38:45&charset=UTF-8&seller_email=panch198@163.com&subject=测试订单&sign=eV1dZxV1mtSK+7IB/pfBBiaMSGPKY3zk/fJYflcc7XahwT9F7lXewwePebxO3cPp00PC1pUlkMRajIJKHHompojSFXsUdLc2pLtSrghnxMi+Q/W7HxIHAl4dnKkmUU890qFequkBoqnrlrcgcobNZOKv4i4RtsDFxDDyy2aTyb7yi8NW30muSvT9kcOr96hEtxnIDMgXxU4I76My5cqyiCgV3wRzpZeiVoURrRnLem1WDIImJ2+LEwuE8VLcyJs/jWEAEwFKpKKE8r5rZ9NeCAyLe47a9pP2IvPUmF8IMrzVCzaNn2sUDpwEJQk8Bn6yOiXLVNBGMbP7Myi1/1qs+Q==&body=测试订单内容&buyer_id=2088112008646530&invoice_amount=0.01&notify_id=cd46801bb75798a5adbbd7458045548k3a&fund_bill_list=[{\"amount\":\"0.01\",\"fundChannel\":\"ALIPAYACCOUNT\"}]&notify_type=trade_status_sync&trade_status=TRADE_SUCCESS&receipt_amount=0.01&app_id=2016082601806424&buyer_pay_amount=0.01&sign_type=RSA2&seller_id=2088812720933216&gmt_payment=2017-06-27 15:38:46&notify_time=2017-06-27 16:02:31&version=1.0&out_trade_no=149854908578621066&total_amount=0.01&trade_no=2017062721001004530210421009&auth_app_id=2016082601806424&buyer_logon_id=177****6223&point_amount=0.00";
        StringTokenizer items;
        for(StringTokenizer entrys = new StringTokenizer(resultInfo, "&");
            entrys.hasMoreTokens(); map.put(items.nextToken(), items.hasMoreTokens() ? ((items.nextToken())) : null)){
            items = new StringTokenizer(entrys.nextToken(), "="); }
        map.put("sign","eV1dZxV1mtSK+7IB/pfBBiaMSGPKY3zk/fJYflcc7XahwT9F7lXewwePebxO3cPp00PC1pUlkMRajIJKHHompojSFXsUdLc2pLtSrghnxMi+Q/W7HxIHAl4dnKkmUU890qFequkBoqnrlrcgcobNZOKv4i4RtsDFxDDyy2aTyb7yi8NW30muSvT9kcOr96hEtxnIDMgXxU4I76My5cqyiCgV3wRzpZeiVoURrRnLem1WDIImJ2+LEwuE8VLcyJs/jWEAEwFKpKKE8r5rZ9NeCAyLe47a9pP2IvPUmF8IMrzVCzaNn2sUDpwEJQk8Bn6yOiXLVNBGMbP7Myi1/1qs+Q==");
        Assert.assertEquals(true, AlipaySignature.rsaCheckV1(map, AlipayConfig.ALIPAY_PUBLIC_KEY, AlipayConfig.CHARSET, "RSA2"));
  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Java 语言中,你可以使用支付宝提供的 SDK实现支付宝支付回调验签。以下是一个简单的示例代码: 1. 首先,确保你已经引入了支付宝SDK,例如 alipay-sdk-java。 2. 在你的回调接口中,获取支付宝返回的参数。 3. 使用支付宝提供的 DefaultAlipayClient 类创建一个 AlipayClient 对象,传入相应的配置参数。 4. 创建一个 AlipaySignature 类的实例,使用该实例的 verify 方法进行名验证。 5. 根据名验证的结果进行相应的处理。 示例代码: ```java import com.alipay.api.AlipayApiException; import com.alipay.api.DefaultAlipayClient; import com.alipay.api.internal.util.AlipaySignature; import javax.servlet.http.HttpServletRequest; import java.util.Map; public class AlipayCallbackHandler { public void handleCallback(HttpServletRequest request) { // 获取支付宝返回的参数 Map<String, String[]> parameterMap = request.getParameterMap(); Map<String, String> params = new HashMap<>(); for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) { String key = entry.getKey(); String[] values = entry.getValue(); if (values.length > 0) { params.put(key, values[0]); } } // 配置参数 String gatewayUrl = "https://openapi.alipay.com/gateway.do"; String appId = "your app id"; String privateKey = "your private key"; String charset = "UTF-8"; String alipayPublicKey = "alipay public key"; String signType = "RSA2"; // 创建 AlipayClient 对象 DefaultAlipayClient alipayClient = new DefaultAlipayClient(gatewayUrl, appId, privateKey, "json", charset, alipayPublicKey, signType); // 验证名 try { boolean verifyResult = AlipaySignature.rsaCheckV1(params, alipayPublicKey, charset, signType); if (verifyResult) { // 名验证成功,根据支付结果进行处理 String tradeStatus = params.get("trade_status"); if ("TRADE_SUCCESS".equals(tradeStatus)) { // 修改订单状态、发送邮件等 } } else { // 名验证失败,处理异常情况 } } catch (AlipayApiException e) { // 验证过程中发生异常,处理异常情况 } } } ``` 在上述示例代码中,需要将 "your app id"、"your private key"、"alipay public key" 替换为你自己的配置参数。另外,根据你的具体业务需求,可以在名验证成功后进行相应的处理,例如修改订单状态、发送邮件等。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值