尚筹网2-6.接入支付

第五章 接入支付

1.蚂蚁金服文档地址

文档入口:https://docs.open.alipay.com/270

沙箱文档入口:https://docs.open.alipay.com/200/105311

2.支付流程

3.内网穿透

①目的

让本地运行的项目可以通过外网访问。

②工作机制

③NATAPP内网穿透服务使用

  • 注册账号

  • 登录

  • ★实名认证

  • 我的隧道

    • 购买隧道
    • 免费隧道
  • 下载客户端

    https://natapp.cn/#download

  • 准备config.int文件

    • 下载地址

      http://download.natapp.cn/assets/downloads/config.ini

    • 教程地址

      https://natapp.cn/article/config_ini

  • 在config.ini中配置authtoken

    • 配置项

      authtoken

    • 属性值

      登录NATAPP->我的隧道->authtoken

#authtoken每个人不一样,这个文件仅供参考
#将本文件放置于natapp同级目录 程序将读取 [default] 段
#在命令行参数模式如 natapp -authtoken=xxx 等相同参数将会覆盖掉此配置
#命令行参数 -config= 可以指定任意config.ini文件
[default]
authtoken=79a1980a333f8a5f      #对应一条隧道的authtoken
clienttoken=                    #对应客户端的clienttoken,将会忽略authtoken,若无请留空,
log=none                        #log 日志文件,可指定本地文件, none=不做记录,stdout=直接屏幕输出 ,默认为none
loglevel=ERROR                  #日志等级 DEBUG, INFO, WARNING, ERROR 默认为 DEBUG
http_proxy=                     #代理设置 如 http://10.123.10.10:3128 非代理上网用户请务必留空

启动natapp.exe,如果上面操作成功,会看到下面效果:

④测试效果

[1]启动本地应用

本地应用监听的端口号需要和隧道一致,比如都是8080

[2]通过隧道暴露到外网的域名访问本地应用

例如:http://aatczu.natappfree.cc/apple/to/thymeleaf/page

4.密钥相关的背景知识

①加密方式介绍

[1]对称加密

明文:亲爱的,今晚8点,老地方见。

加密步骤1[转换为拼音]:qinaidedouhaojinwanbadiandouhaolaodifangjian

加密步骤2[将字母转换为序号]:13261205041101...220907

加密步骤3[将序号+5]:18311710091609..271413

密文:18311710091609..271413

解密步骤1[将序号-5]:13261205041101...220907

解密步骤2[将序号还原为字母]:qinaidedouhaojinwanbadiandouhaolaodifangjian

解密步骤3[将拼音还原为明文]:亲爱的,今晚8点,老地方见。

问题:一旦加密方式被敌人知道,敌人就会将密文反推出明文。

[2]非对称加密

所谓非对称加密,就是说加密和解密分别使用不同钥匙。私钥加密就必须用公钥解密,公钥加密就必须使用私钥解密。

②加签过程

③验签过程

5.官方Demo使用

①Demo工程下载地址

https://docs.open.alipay.com/270/106291/

②沙箱环境地址

https://openhome.alipay.com/platform/appDaily.htm

访问这个地址查看相关参数

③生成密钥文档地址

https://docs.open.alipay.com/291/105971

下载RSA签名验签工具windows_V1.4工具

运行RSA签名验签工具.bat文件

点击生成密钥

③AlipayConfig类的设置

// 应用ID,您的APPID,收款账号既是您的APPID对应支付宝账号
public static String app_id = "2016101700711393";

// 商户私钥,您的PKCS8格式RSA2私钥
public static String merchant_private_key = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCZW5QIcTSb5OlkZrxUYdOLMmMlPD+qiVC7gp7tdTGi6nTEwhy/MtIUBStOQ3jntqpY2W00Di6KhZiT9L4z1rf2j7lUk53nRaFGX2yLS6cdrykdMLAXqXvAR6U/oWLw2OrRbHltdGVCgW1hrTKI480CoFL5ywqL9tTXlOlR1ewQjpCl7EeMcSL3aB9dTVmRX5Yc5qDdgcUDtMwNZ48eOJB+u5u6LGz+XWzconeS3Rxp9Dt/+GfxG4+EgUAAiqpDVh8Fb8QYcFaBlQ0DNwkwNfng5K0hbD/Y/d4c9e04khyKtTLbuHhkM1mogZtu5vrCt4RVgXa3fHqhfS9luUUlCkVPAgMBAAECggEBAJes3YJkBLZS9wrYGVgT0RgQ48JGDmnqmbB7BhixGkCZRpf7rFbmOqSWYDhtvzzdiymJEdq0VA+pBjj/jMbUjOoNSC2AOmIsmc9yPXafgk93mGBIcUFV3RFVSvjd829l+wjaElbREf1u6PH8TMjbFT6NtsNbr7iyjdwSaZkIzQuphBa0mtd3bIJf0SAtS6CK7HQujvxaYXLHY3hmNCUwb0rsNjuOLEsw6HAKnVgTh28c3JkXIwXdWsB5k4bPr/fwRxkE8F2ritbWRR3GhzfTgUebTPwpdYOqssy4r0Sla1WCdro5lFJ4kwK76i6esN7maN7L3+/RJ9wT/kjpqH8XToECgYEA9bUy6dAcwe3+Tjg9e7clzdopNQJGdbpFhVxruy/194WXBijezXUz+im+FnrVvjjubWnn4dEOmCH3Vv/AY6YQdsxr9MGv0mCspqRih0UFBgTn9Lmb22ZxJP9dQ3PlmspNUaxD4+fTshqcbBqQRlCkmy7s1t3Ga1zjaLUhFrtsn68CgYEAn8gUrkw1puGBkD8MVCAbOY9nbYDwtD1qu7Ne2RtilDBd91nJgyg0T4lSvTvCth7+8yYPp5LcmRxMlP7JcX/tGtMkDF9SpXgJqbWmXYK5OvhRmeC7FQCa7n/m5TIEfox+fFn1e3go8J2ABrHWECezCHk5rAYpo8HVKOEUOOZHfGECgYEAzfDL9bFbCSiDJ+h3kAQYCYu7UhTQHci8kGTAXyp5zLcJuD31UNvVbu4hxxG5qTBqlwZXNqxUwSpm92JxTJRZ5Fi+e20EAx7IVvwdISe2aC8gg7w2aBRa7Rmkf+7aGX1KhRQP6yvaAcPQK8Ov/V+GOBOCqy1DK6W1bYOzciwxhT0CgYAFvTgQ8qfSsLLorXsR4n1X2fDLHCCJONOGnnC/t54Y2IvngJAeZ+lJGhOgvG7H/+EEds+FI19NGJkfmO8/Zmrr1b2rvBjJ3L/sGpw5K6LfnV/A+TN/E6B9BJcUkHFc4ksAyHJq1uwRiHa6xtR7jBGAMmqB5U7FlQbFCyCg0pSqwQKBgEBEGy8r/RxBxX7Q/61arjP3/Gc/grXBuZPuxXD9GCPb5KDnvbYRUn9Hlew9gcIgu4GGA0OMiZPMCfFcDyuyKIgiZvqCzVpFGlcoWD+Q9wVlEDGTVH65iSWtaghWM7KH0y0RZ61dHUbiWIlKY4Q2NPkEZjE3iUphGszda5dr7feR";

// [正式环境]支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
// [沙箱环境]支付宝公钥,查看地址:https://openhome.alipay.com/platform/appDaily.htm 信息配置->必看部分->RSA2(SHA256)密钥(推荐)->查看支付宝公钥
public static String alipay_public_key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl9LZ4sPSJeZy3wnO9IetyU1QP9u589LfWls6yHEX59oLSonYcZAK34sNWVuYMDeU/wBjENE5QXAT6IXxC/q2FiomNOnICN2mN/HoXQpfkkd801heDqPJ0NeTOmD+OPb+s/aLTOtxWpVDMfaDp529YJ5b8oxGkEFU3X/FE3ZzDFLj0As+tkcJQfZKuNnKh3jMzBlgpNQqcb2agCB2fDSVSKiX7EGpk/4JK53Igosu8w9MOYD8WDuqChVb8vnzYSHN3R0LDcoU8eVDEv/Hu9anGRzskvYkEgLm8ZXHPrkl2O3H+qnvFiDKLceqS7DYUOXVDJcMoMUxEGtvudR67fwzIwIDAQAB";

// 服务器异步通知页面路径  需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
// public static String notify_url = "http://工程公网访问地址/alipay.trade.page.pay-JAVA-UTF-8/notify_url.jsp";

// 使用内网穿透提供的对外暴露域名
public static String notify_url = "http://qckkb7.natappfree.cc/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 return_url = "http://qckkb7.natappfree.cc/alipay.trade.page.pay-JAVA-UTF-8/return_url.jsp";

// 签名方式
public static String sign_type = "RSA2";

// 字符编码格式
public static String charset = "utf-8";

// [正式环境]支付宝网关
// public static String gatewayUrl = "https://openapi.alipay.com/gateway.do";
// [沙箱环境]支付宝网关
public static String gatewayUrl = "https://openapi.alipaydev.com/gateway.do";

④在沙箱环境中设置应用公钥

操作位置:信息配置->必看部分->RSA2(SHA256)密钥(推荐)->查看应用公钥->修改

⑤运行工程

index.jsp→run as→run on sever

6.支付功能移植到项目

①加入支付功能相关依赖

参考文档:https://docs.open.alipay.com/54/103419

所在工程:distribution-crowd-7-webui


  com.alipay.sdk
  alipay-sdk-java
  3.3.49.ALL

②加入AlipayConfig

所在工程:distribution-crowd-7-webui

全类名:com.rgh.crowd.config.AlipayConfig

使用官方Demo工程中的AlipayConfig类。内网穿透地址需要根据当前运行时的实际情况调整。notify_url和return_url需要编写对应的handler方法处理。

public class AlipayConfig {
    
//↓↓↓↓↓↓↓↓↓↓请在这里配置您的基本信息↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

    // 应用ID,您的APPID,收款账号既是您的APPID对应支付宝账号
    public static String app_id = "2016101700711393";
    
    // 商户私钥,您的PKCS8格式RSA2私钥
    public static String merchant_private_key = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCZW5QIcTSb5OlkZrxUYdOLMmMlPD+qiVC7gp7tdTGi6nTEwhy/MtIUBStOQ3jntqpY2W00Di6KhZiT9L4z1rf2j7lUk53nRaFGX2yLS6cdrykdMLAXqXvAR6U/oWLw2OrRbHltdGVCgW1hrTKI480CoFL5ywqL9tTXlOlR1ewQjpCl7EeMcSL3aB9dTVmRX5Yc5qDdgcUDtMwNZ48eOJB+u5u6LGz+XWzconeS3Rxp9Dt/+GfxG4+EgUAAiqpDVh8Fb8QYcFaBlQ0DNwkwNfng5K0hbD/Y/d4c9e04khyKtTLbuHhkM1mogZtu5vrCt4RVgXa3fHqhfS9luUUlCkVPAgMBAAECggEBAJes3YJkBLZS9wrYGVgT0RgQ48JGDmnqmbB7BhixGkCZRpf7rFbmOqSWYDhtvzzdiymJEdq0VA+pBjj/jMbUjOoNSC2AOmIsmc9yPXafgk93mGBIcUFV3RFVSvjd829l+wjaElbREf1u6PH8TMjbFT6NtsNbr7iyjdwSaZkIzQuphBa0mtd3bIJf0SAtS6CK7HQujvxaYXLHY3hmNCUwb0rsNjuOLEsw6HAKnVgTh28c3JkXIwXdWsB5k4bPr/fwRxkE8F2ritbWRR3GhzfTgUebTPwpdYOqssy4r0Sla1WCdro5lFJ4kwK76i6esN7maN7L3+/RJ9wT/kjpqH8XToECgYEA9bUy6dAcwe3+Tjg9e7clzdopNQJGdbpFhVxruy/194WXBijezXUz+im+FnrVvjjubWnn4dEOmCH3Vv/AY6YQdsxr9MGv0mCspqRih0UFBgTn9Lmb22ZxJP9dQ3PlmspNUaxD4+fTshqcbBqQRlCkmy7s1t3Ga1zjaLUhFrtsn68CgYEAn8gUrkw1puGBkD8MVCAbOY9nbYDwtD1qu7Ne2RtilDBd91nJgyg0T4lSvTvCth7+8yYPp5LcmRxMlP7JcX/tGtMkDF9SpXgJqbWmXYK5OvhRmeC7FQCa7n/m5TIEfox+fFn1e3go8J2ABrHWECezCHk5rAYpo8HVKOEUOOZHfGECgYEAzfDL9bFbCSiDJ+h3kAQYCYu7UhTQHci8kGTAXyp5zLcJuD31UNvVbu4hxxG5qTBqlwZXNqxUwSpm92JxTJRZ5Fi+e20EAx7IVvwdISe2aC8gg7w2aBRa7Rmkf+7aGX1KhRQP6yvaAcPQK8Ov/V+GOBOCqy1DK6W1bYOzciwxhT0CgYAFvTgQ8qfSsLLorXsR4n1X2fDLHCCJONOGnnC/t54Y2IvngJAeZ+lJGhOgvG7H/+EEds+FI19NGJkfmO8/Zmrr1b2rvBjJ3L/sGpw5K6LfnV/A+TN/E6B9BJcUkHFc4ksAyHJq1uwRiHa6xtR7jBGAMmqB5U7FlQbFCyCg0pSqwQKBgEBEGy8r/RxBxX7Q/61arjP3/Gc/grXBuZPuxXD9GCPb5KDnvbYRUn9Hlew9gcIgu4GGA0OMiZPMCfFcDyuyKIgiZvqCzVpFGlcoWD+Q9wVlEDGTVH65iSWtaghWM7KH0y0RZ61dHUbiWIlKY4Q2NPkEZjE3iUphGszda5dr7feR";
    
    // 支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
    // 沙箱公钥地址:https://openhome.alipay.com/platform/appDaily.htm
    public static String alipay_public_key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl9LZ4sPSJeZy3wnO9IetyU1QP9u589LfWls6yHEX59oLSonYcZAK34sNWVuYMDeU/wBjENE5QXAT6IXxC/q2FiomNOnICN2mN/HoXQpfkkd801heDqPJ0NeTOmD+OPb+s/aLTOtxWpVDMfaDp529YJ5b8oxGkEFU3X/FE3ZzDFLj0As+tkcJQfZKuNnKh3jMzBlgpNQqcb2agCB2fDSVSKiX7EGpk/4JK53Igosu8w9MOYD8WDuqChVb8vnzYSHN3R0LDcoU8eVDEv/Hu9anGRzskvYkEgLm8ZXHPrkl2O3H+qnvFiDKLceqS7DYUOXVDJcMoMUxEGtvudR67fwzIwIDAQAB";

    // 服务器异步通知页面路径  需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
    public static String notify_url = "http://ekmmng.natappfree.cc/pay/notify.html";

    // 页面跳转同步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
    public static String return_url = "http://ekmmng.natappfree.cc/pay/return.html";

    // 签名方式
    public static String sign_type = "RSA2";
    
    // 字符编码格式
    public static String charset = "utf-8";
    
    // 支付宝网关
    public static String gatewayUrl = "https://openapi.alipaydev.com/gateway.do";

}

③创建PayController

所在工程:distribution-crowd-7-webui

全类名:com.rgh.crowd.controller.PayController

@Controller
public class PayController {

}

④处理付款请求的方法

所在工程:distribution-crowd-7-webui

所在类:com.rgh.crowd.controller.PayController

@ResponseBody
@RequestMapping("/pay/do/pay.html")
public String doPay(HttpServletRequest request) throws AlipayApiException, UnsupportedEncodingException {

    // 获得初始化的AlipayClient
    AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.gatewayUrl, AlipayConfig.app_id,
                                                        AlipayConfig.merchant_private_key, "json", AlipayConfig.charset, AlipayConfig.alipay_public_key,
                                                        AlipayConfig.sign_type);

    // 设置请求参数
    AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
    alipayRequest.setReturnUrl(AlipayConfig.return_url);
    alipayRequest.setNotifyUrl(AlipayConfig.notify_url);

    // 商户订单号,商户网站订单系统中唯一订单号,必填
    String out_trade_no = new String(request.getParameter("WIDout_trade_no").getBytes("ISO-8859-1"), "UTF-8");
    // 付款金额,必填
    String total_amount = new String(request.getParameter("WIDtotal_amount").getBytes("ISO-8859-1"), "UTF-8");
    // 订单名称,必填
    String subject = new String(request.getParameter("WIDsubject").getBytes("ISO-8859-1"), "UTF-8");
    // 商品描述,可空
    String body = new String(request.getParameter("WIDbody").getBytes("ISO-8859-1"), "UTF-8");

    alipayRequest.setBizContent("{\"out_trade_no\":\"" + out_trade_no + "\"," + "\"total_amount\":\"" + total_amount
                                + "\"," + "\"subject\":\"" + subject + "\"," + "\"body\":\"" + body + "\","
                                + "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");

    // 若想给BizContent增加其他可选请求参数,以增加自定义超时时间参数timeout_express来举例说明
    // alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\","
    // + "\"total_amount\":\""+ total_amount +"\","
    // + "\"subject\":\""+ subject +"\","
    // + "\"body\":\""+ body +"\","
    // + "\"timeout_express\":\"10m\","
    // + "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
    // 请求参数可查阅【电脑网站支付的API文档-alipay.trade.page.pay-请求参数】章节

    // 请求
    String result = alipayClient.pageExecute(alipayRequest).getBody();

    // Demo中的处理方式是:out.println(result)
    // 我们使用这个result作为响应体即可,也就是说当前方法返回result,配合@ResponseBody注解让result作为响应体
    return result;

}

⑤return方法

@ResponseBody
@RequestMapping("/pay/return.html")
public String returnUrl(HttpServletRequest request) throws Exception {

    // 获取支付宝GET过来反馈信息
    Map params = new HashMap();
    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] + ",";
        }
        // 乱码解决,这段代码在出现乱码时使用
        valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
        params.put(name, valueStr);
    }

    boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.alipay_public_key, AlipayConfig.charset,
                                                      AlipayConfig.sign_type); // 调用SDK验证签名

    // ——请在这里编写您的程序(以下代码仅作参考)——
    if (signVerified) {
        // 商户订单号
        String out_trade_no = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"), "UTF-8");

        // 支付宝交易号
        String trade_no = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"), "UTF-8");

        // 付款金额
        String total_amount = new String(request.getParameter("total_amount").getBytes("ISO-8859-1"), "UTF-8");

        return "trade_no:" + trade_no + "
out_trade_no:" + out_trade_no + "
total_amount:" + total_amount; } else { return "验签失败"; } }

⑥notify方法

@ResponseBody
@RequestMapping("/pay/notify.html")
public String notifyUrl(HttpServletRequest request) throws Exception {

    //获取支付宝POST过来反馈信息
    Map params = new HashMap();
    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] + ",";
        }
        //乱码解决,这段代码在出现乱码时使用
        valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
        params.put(name, valueStr);
    }

    boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.alipay_public_key, AlipayConfig.charset, AlipayConfig.sign_type); //调用SDK验证签名

    //——请在这里编写您的程序(以下代码仅作参考)——

    /* 实际验证过程建议商户务必添加以下校验:
        1、需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
        2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
        3、校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email)
        4、验证app_id是否为该商户本身。
        */
    if(signVerified) {//验证成功
        //商户订单号
        // String out_trade_no = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"),"UTF-8");

        //支付宝交易号
        // String trade_no = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"),"UTF-8");

        //交易状态
        String trade_status = new String(request.getParameter("trade_status").getBytes("ISO-8859-1"),"UTF-8");

        if(trade_status.equals("TRADE_FINISHED")){
            //判断该笔订单是否在商户网站中已经做过处理
            //如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
            //如果有做过处理,不执行商户的业务程序

            //注意:
            //退款日期超过可退款期限后(如三个月可退款),支付宝系统发送该交易状态通知
        }else if (trade_status.equals("TRADE_SUCCESS")){
            //判断该笔订单是否在商户网站中已经做过处理
            //如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
            //如果有做过处理,不执行商户的业务程序

            //注意:
            //付款完成后,支付宝系统发送该交易状态通知
        }

        return "success";

    }else {//验证失败
        return "fail";

        //调试用,写文本函数记录程序运行情况是否正常
        //String sWord = AlipaySignature.getSignCheckContentV1(params);
        //AlipayConfig.logResult(sWord);
    }

}

⑦将AliPayConfig中的属性配置转移到yml文件

[1]@Value注解不能修饰静态资源

加载类比IOC容器初始化要早,静态资源初始化时IOC容器还没有初始化好,@Value注解的值设置不进去。

[2]解决办法

实现接口org.springframework.beans.factory.InitializingBean

@Configuration
public class AlipayConfig implements InitializingBean {
    
    @Value("${alipay.app_id}")
    private String appId;
    //……
    @Override
    public void afterPropertiesSet() throws Exception {
        app_id = this.appId;
        //……
    }

在yml配置文件中加入

ali.pay.app.id: "2016101700711393"

你可能感兴趣的:(尚筹网2-6.接入支付)