JAVA支付宝支付

记录:前面写了微信支付,现在完成支付宝支付,记录实现步骤和自己的理解。
借鉴:https://cloud.tencent.com/developer/article/1448209

一、沙箱环境测试,把应用密钥,应用公钥 ,支付宝公钥保存好备用。(怎么来的百度一下,多得很)


image.png
image.png
image.png

二、代码实现
2.1导入依赖包

<dependency>
    <groupId>com.alipay.sdk</groupId>
    <artifactId>alipay-sdk-java</artifactId>
    <version>3.7.4.ALL</version>
</dependency>

2.2在resources下面新建一个alipay.properties文件

# 应用ID,您的APPID,收款账号既是您的APPID对应支付宝账号
appId: 2016110300790037
# 商户私钥,您的PKCS8格式RSA2私钥
privateKey: ......
# 支付宝公钥,查看地址:https://openhome.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
publicKey: ......
# 服务器异步通知页面路径需http://格式的完整路径。异步的后端接口,这里用的natapp做一个内网映射。
notifyUrl:http://dj29ri.natappfree.cc/order/notifyUrl
# 页面跳转同步通知页面路径 需http://格式的完整路径,没得前端页面地址就返回的后面项目index页面。
returnUrl: http://dj29ri.natappfree.cc
# 签名方式
signType: RSA2
# 字符编码格式
charset: utf-8
# 支付宝网关
gatewayUrl: https://openapi.alipaydev.com/gateway.do

2.3 订单接口

package cn.huangsong.util.pay.ali;

import com.alipay.api.internal.util.AlipaySignature;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.alipay.api.AlipayApiException;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * 订单接口
 * 
 * @author Louis
 * @date Dec 12, 2018
 */
@Slf4j
@RestController()
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private Alipay aliPay;

    /**
     * 阿里支付
     * @param outTradeNo
     * @param subject
     * @param totalAmount
     * @param body
     * @return
     * @throws AlipayApiException
     */
    @PostMapping(value = "/alipay")
    public String alipay(String outTradeNo, String subject, String totalAmount, String body) throws AlipayApiException {
         //根据支付宝接口协议,必填的参数
        AlipayBean alipayBean = new AlipayBean();
        alipayBean.setOut_trade_no(outTradeNo);
        alipayBean.setSubject(subject);
        alipayBean.setTotal_amount(totalAmount);
        alipayBean.setBody(body);
        return aliPay.pay(alipayBean);
    }

    /**
     * 回调接口
     * @param request
     * @return
     */
    @PostMapping("/notifyUrl")
    public String callback(HttpServletRequest request){
        //获取回调参数
        Map<String, String> params = convertRequestParamsToMap(request);
        log.info("支付宝回调:已回调,参数为====" + params);
        try {
            //校验签名及是否支付成功
            checkCallbackAlipay(params);
            /*处理一些业务*/
            return "success";
        } catch (Exception e) {
            log.info("支付宝回调>>>>>>>>>>>>>>出错了");
            e.printStackTrace();
            return "failure";
        }
    }

    /**
     *  判断是否成功和回调验证签名
     */
    private void checkCallbackAlipay(Map<String, String> params) throws Exception {
        //是否支付成功
        if (!"TRADE_SUCCESS".equals(params.get("trade_status"))) {
            throw new Exception("支付宝支付失败!");
        }
        //......效验其他
        //校验签名
        if (!rsaCheckV1(params, AlipayProperties.getPublicKey())) {
            throw new Exception("支付宝回调签名认证失败");
        }
    }

    /**
     * 支付宝支付回调验证签名
     * 验证签名;参考:https://opendocs.alipay.com/open/54/106370
     * @param params 回调参数
     * @param alipayPublickey 传入支付宝公钥
     * @return true 正确 false 失败
     * @throws AlipayApiException
     */
    public static boolean rsaCheckV1(Map<String, String> params, String alipayPublickey) throws AlipayApiException {
        return AlipaySignature.rsaCheckV1(params, alipayPublickey, "utf-8", "RSA2");
    }

    /**
     * 获取支付宝回调参数
     */
    private static Map<String, String> convertRequestParamsToMap(HttpServletRequest request) {
        Map<String, String> retMap = new HashMap<String, String>();
        Set<Map.Entry<String, String[]>> entrySet = request.getParameterMap().entrySet();
        for (Map.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;
    }
}

2.4支付接口

package cn.huangsong.util.pay.ali;

import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.request.AlipayTradePagePayRequest;

/**
 * 支付宝支付接口
 * @author Louis
 * @date Dec 12, 2018
 */
@Component
public class Alipay {
    
    /**
     * 支付接口
     * @param alipayBean
     * @return
     * @throws AlipayApiException
     */
    public String pay(AlipayBean alipayBean) throws AlipayApiException {
        // 1、获得初始化的AlipayClient   支付需要的参数准备
        String serverUrl = AlipayProperties.getGatewayUrl();
        String appId = AlipayProperties.getAppId();
        String privateKey = AlipayProperties.getPrivateKey();
        String format = "json";
        String charset = AlipayProperties.getCharset();
        String alipayPublicKey = AlipayProperties.getPublicKey();
        String signType = AlipayProperties.getSignType();
        String returnUrl = AlipayProperties.getReturnUrl();
        String notifyUrl = AlipayProperties.getNotifyUrl();
        AlipayClient alipayClient = new DefaultAlipayClient(serverUrl, appId, privateKey, format, charset, alipayPublicKey, signType);
        // 2、设置请求参数
        AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
        // 页面跳转同步通知页面路径
        alipayRequest.setReturnUrl(returnUrl);
        // 服务器异步通知页面路径
        alipayRequest.setNotifyUrl(notifyUrl);
        // 封装参数
        alipayRequest.setBizContent(JSON.toJSONString(alipayBean));
        // 3、请求支付宝进行付款,并获取支付结果
        String result = alipayClient.pageExecute(alipayRequest).getBody();
        // 返回付款信息
        return result;
    }
}

2.5 上面步骤---2.3订单接口需要的订单相关信息参数

package cn.huangsong.util.pay.ali;

/**
 * 支付实体对象
 * 根据支付宝接口协议,其中的属性名,必须使用下划线,不能修改
 * @author Louis
 * @date Dec 12, 2018
 */
public class AlipayBean {
    
    /**
     * 商户订单号,必填
     * 
     */
    private String out_trade_no;
    /**
     * 订单名称,必填
     */
    private String subject;
    /**
     * 付款金额,必填
     * 根据支付宝接口协议,必须使用下划线
     */
    private String total_amount;
    /**
     * 商品描述,可空
     */
    private String body;
    /**
     * 超时时间参数
     */
    private String timeout_express= "10m";
    /**
     * 产品编号
     */
    private String product_code= "FAST_INSTANT_TRADE_PAY";

    public String getOut_trade_no() {
        return out_trade_no;
    }
    public void setOut_trade_no(String out_trade_no) {
        this.out_trade_no = out_trade_no;
    }
    public String getSubject() {
        return subject;
    }
    public void setSubject(String subject) {
        this.subject = subject;
    }
    public String getTotal_amount() {
        return total_amount;
    }
    public void setTotal_amount(String total_amount) {
        this.total_amount = total_amount;
    }
    public String getBody() {
        return body;
    }
    public void setBody(String body) {
        this.body = body;
    }
    public String getTimeout_express() {
        return timeout_express;
    }
    public void setTimeout_express(String timeout_express) {
        this.timeout_express = timeout_express;
    }
    public String getProduct_code() {
        return product_code;
    }
    public void setProduct_code(String product_code) {
        this.product_code = product_code;
    }

}

2.6 增加一个 PropertiesListener 监听器,监听AlipayProperties 类在应用启动时加载配置文件属性。

package cn.huangsong.util.pay.ali;

import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;


/**
 * 配置文件监听器,用来加载自定义配置文件
 * @author Louis
 * @date Dec 12, 2018
 */
@Component
public class PropertiesListener implements ApplicationListener<ApplicationStartedEvent> {

    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        AlipayProperties.loadProperties();
    }
}

2.7 上面步骤---2.4支付接口需要的应用相关参数

应用启动加载配置文件中的配置到propertiesMap中,在通过getter获取数据。

package cn.huangsong.util.pay.ali;

import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.stereotype.Component;

/**
 * 应用启动加载文件
 * @author Louis
 * @date Dec 12, 2018
 */
@Component
public class AlipayProperties {
    
    public static final String APP_ID = "appId";
    public static final String PRIVARY_KEY = "privateKey";
    public static final String PUBLIC_KEY = "publicKey";
    public static final String NOTIFY_URL = "notifyUrl";
    public static final String RETURN_URL = "returnUrl";
    public static final String SIGN_TYPE = "signType";
    public static final String CHARSET = "charset";
    public static final String GATEWAY_URL = "gatewayUrl";

    /**
     * 保存加载配置参数
     */
    private static Map<String, String> propertiesMap = new HashMap<String, String>();

    /**
     * 加载属性
     */
    public static void loadProperties() {
        // 获得PathMatchingResourcePatternResolver对象
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        try {
            // 加载resource文件(也可以加载resources)
            Resource resources = resolver.getResource("classpath:alipay.properties");
            PropertiesFactoryBean config = new PropertiesFactoryBean();
            config.setLocation(resources);
            config.afterPropertiesSet();
            Properties prop = config.getObject();
            // 循环遍历所有得键值对并且存入集合
            for (String key : prop.stringPropertyNames()) {
                propertiesMap.put(key, (String) prop.get(key));
            }
        } catch (Exception e) {
            new Exception("配置文件加载失败");
        }
    }

    /**
     * 获取配置参数值
     * @param key
     * @return
     */
    public static String getKey(String key) {
        return propertiesMap.get(key);
    }

    public static String getAppId() {
        return propertiesMap.get(APP_ID);
    }

    public static String getPrivateKey() {
        return propertiesMap.get(PRIVARY_KEY);
    }

    public static String getPublicKey() {
        return propertiesMap.get(PUBLIC_KEY);
    }

    public static String getNotifyUrl() {
        return propertiesMap.get(NOTIFY_URL);
    }

    public static String getReturnUrl() {
        return propertiesMap.get(RETURN_URL);
    }

    public static String getSignType() {
        return propertiesMap.get(SIGN_TYPE);
    }

    public static String getCharset() {
        return propertiesMap.get(CHARSET);
    }

    public static String getGatewayUrl() {
        return propertiesMap.get(GATEWAY_URL);
    }


}

2.8 拿到沙箱环境下的支付账号,虚拟的。(测试需要用)


image.png

三、测试页面 放到static/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<H1>支付测试</H1>
<hr>
<div class="form-container">
    <form id="form" action="http://localhost:8080/order/alipay" method="post">
        *商户订单 :
        <input type="text" name="outTradeNo" value="dzcp100010001"><br>
        *订单名称 :
        <input type="text" name="subject" value="华为手机"><br>
        *付款金额 :
        <input type="text" name="totalAmount" value="6666" ><br>
        *商品描述 :
        <input type="text" name="body" value="华为手机"><br>
        <input type="button" value="支付宝支付" onclick="submitForm('order/alipay')">
    </form>
</div>
</body>

<script language="javascript">
    function submitForm(action) {
        /*订单接口*/
        document.getElementById("form").action = "http://localhost:8080/order/alipay";
        document.getElementById("form").submit()
    }
</script>

<style>
    .form-container {
        padding-top:10px;
    }
    input {
        margin:10px;

    }
</style>
</html>

测试结果

image.png

自动跳转到

image.png

提交支付跳转

image.png

支付成功后跳转到你指定的页面-->returnUrl: http://dj29ri.natappfree.cc

image.png

异步通知到你写的接口

image.png
image.png

整个流程结束。

注意:
如果出现了这个情况,你只需要退出登录的支付宝账号,或者换个浏览器就可以了。


image.png

本地项目端口映射到外网
可以用natapp。百度一下流程,下载打开,在免费注册账号获取一个免费的隧道就可以了。


image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容