支付宝支付-APP支付服务端详解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/fengshizty/article/details/53215196

支付宝APP支付服务端详解

前面接了微信支付,相比微信支付,支付宝APP支付提供了支付分装类,下面将实现支付宝APP支付、订单查询、支付结果异步通知、APP支付申请参数说明,以及服务端返回APP端发起支付的签名、商户私钥、支付宝公钥的配置使用等。

支付注意事项

1、APP支付不能在沙箱测试、只能申请上线测试
2、需要创建RSA密钥设置文档,设置后上传rsa_public_key.pem【开发者公钥,上传时需要去掉公钥的头和尾】上传成功后换取支付宝公钥,为项目的alipay_public_key.pem
3、rsa_private_key_pkcs8.pem【开发者私钥】,去掉头和尾为项目的alipay_private_key_pkcs8.pem
4、需要导入所需支付包:alipay-sdk-java.jar 和 commons-logging.jar,具体参考:服务端SDK

支付流程

支付文档参考:支付文档支付文档2

APP支付:服务器端按照文档【统一收单交易支付接口】创建支付OrderStr返回APP端——-APP端拿到OrderStr发起支付—–支付宝服务器端回调服务端异步通知接口——-服务器端按照【App支付结果异步通知】校验签名等做业务逻辑处理

APP支付订单查询:服务器端调用【统一收单线下交易查询】查询支付订单

APP支付申请退款:每笔支付可以申请多次退款,但退款总金额不能超过支付金额,调用【统一收单交易退款接口】发起退款申请

APP支付退款查询:服务端调用【 统一收单交易退款查询】查询退款订单信息

支付项目结构

支付项目demo结构如下

支付宝支付demo

支付代码实现

支付代码

PayController.java :包含支付、支付查询、异步通知、退款申请、退款查询

package org.andy.alipay.controller;

import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.andy.alipay.model.JsonResult;
import org.andy.alipay.model.ResponseData;
import org.andy.alipay.util.AlipayUtil;
import org.andy.alipay.util.DatetimeUtil;
import org.andy.alipay.util.PayUtil;
import org.andy.alipay.util.SerializerFeatureUtil;
import org.andy.alipay.util.StringUtil;
import org.andy.alipay.util.WebUtil;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import com.alibaba.fastjson.JSON;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayConstants;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradeFastpayRefundQueryRequest;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.request.AlipayTradeRefundRequest;
import com.alipay.api.response.AlipayTradeFastpayRefundQueryResponse;
import com.alipay.api.response.AlipayTradeQueryResponse;
import com.alipay.api.response.AlipayTradeRefundResponse;

/**
 * 创建时间:2016年11月2日 下午4:16:32
 * 
 * @author andy
 * @version 2.2
 */
@Controller
@RequestMapping("/order")
public class PayController {

    private static final Logger LOG = Logger.getLogger(PayController.class);

    /**
     * 支付下订单
     * 
     * @param request
     * @param response
     * @param cashnum
     *            支付金额
     * @param mercid
     *            商品id
     * @param callback
     */
    @RequestMapping(value = "/pay", method = RequestMethod.POST)
    public void orderPay(HttpServletRequest request, HttpServletResponse response,
            @RequestParam(required = false, defaultValue = "0") Double cashnum, String mercid, String callback) {
        LOG.info("[/order/pay]");
        if (!"001".equals(mercid)) {
            WebUtil.response(response, WebUtil.packJsonp(callback, JSON
                    .toJSONString(new JsonResult(-1, "商品不存在", new ResponseData()), SerializerFeatureUtil.FEATURES)));
        }

        Map<String, String> param = new HashMap<>();
        // 公共请求参数
        param.put("app_id", AlipayUtil.ALIPAY_APPID);// 商户订单号
        param.put("method", "alipay.trade.app.pay");// 交易金额
        param.put("format", AlipayConstants.FORMAT_JSON);
        param.put("charset", AlipayConstants.CHARSET_UTF8);
        param.put("timestamp", DatetimeUtil.formatDateTime(new Date()));
        param.put("version", "1.0");
        param.put("notify_url", "https://www.andy.org/alipay/order/pay/notify.shtml"); // 支付宝服务器主动通知商户服务
        param.put("sign_type", AlipayConstants.SIGN_TYPE_RSA);

        Map<String, Object> pcont = new HashMap<>();
        // 支付业务请求参数
        pcont.put("out_trade_no", PayUtil.getTradeNo()); // 商户订单号
        pcont.put("total_amount", String.valueOf(cashnum));// 交易金额
        pcont.put("subject", "测试支付"); // 订单标题
        pcont.put("body", "Andy");// 对交易或商品的描述
        pcont.put("product_code", "QUICK_MSECURITY_PAY");// 销售产品码

        param.put("biz_content", JSON.toJSONString(pcont)); // 业务请求参数  不需要对json字符串转义 
        Map<String, String> payMap = new HashMap<>();
        try {
            param.put("sign", PayUtil.getSign(param, AlipayUtil.APP_PRIVATE_KEY)); // 业务请求参数
            payMap.put("orderStr", PayUtil.getSignEncodeUrl(param, true));
        } catch (Exception e) {
            e.printStackTrace();
        }

        WebUtil.response(response, WebUtil.packJsonp(callback, JSON.toJSONString(
                new JsonResult(1, "订单获取成功", new ResponseData(null, payMap)), SerializerFeatureUtil.FEATURES)));
    }

    /**
     * 
     * @param request
     * @param response
     * @param tradeno
     *            支付宝订单交易编号
     * @param orderno
     *            商家交易编号
     * @param callback
     */
    @RequestMapping(value = "/pay/query", method = RequestMethod.POST)
    public void orderPayQuery(HttpServletRequest request, HttpServletResponse response, String tradeno, String orderno,
            String callback) {
        LOG.info("[/order/pay/query]");
        if (StringUtil.isEmpty(tradeno) && StringUtil.isEmpty(orderno)) {
            WebUtil.response(response, WebUtil.packJsonp(callback, JSON
                    .toJSONString(new JsonResult(-1, "订单号不能为空", new ResponseData()), SerializerFeatureUtil.FEATURES)));
        }

        AlipayTradeQueryRequest alipayRequest = new AlipayTradeQueryRequest(); // 统一收单线下交易查询
        // 只需要传入业务参数
        Map<String, Object> param = new HashMap<>();
        param.put("out_trade_no", orderno); // 商户订单号
        param.put("trade_no", tradeno);// 交易金额
        alipayRequest.setBizContent(JSON.toJSONString(param)); // 不需要对json字符串转义 

        Map<String, String> restmap = new HashMap<String, String>();// 返回提交支付宝订单交易查询信息
        boolean flag = false; // 查询状态
        try {
            AlipayTradeQueryResponse alipayResponse = AlipayUtil.getAlipayClient().execute(alipayRequest);
            if (alipayResponse.isSuccess()) {
                // 调用成功,则处理业务逻辑
                if ("10000".equals(alipayResponse.getCode())) {
                    // 订单创建成功
                    flag = true;
                    restmap.put("order_no", alipayResponse.getOutTradeNo());
                    restmap.put("trade_no", alipayResponse.getTradeNo());
                    restmap.put("buyer_logon_id", alipayResponse.getBuyerLogonId());
                    restmap.put("trade_status", alipayResponse.getTradeStatus());
                    LOG.info("订单查询结果:" + alipayResponse.getTradeStatus());
                } else {
                    LOG.info("订单查询失败:" + alipayResponse.getMsg() + ":" + alipayResponse.getSubMsg());
                }
            }
        } catch (AlipayApiException e) {
            e.printStackTrace();
        }

        if (flag) {
            // 订单查询成功
            WebUtil.response(response,
                    WebUtil.packJsonp(callback,
                            JSON.toJSONString(new JsonResult(1, "订单查询成功", new ResponseData(null, restmap)),
                                    SerializerFeatureUtil.FEATURES)));
        } else { // 订单查询失败
            WebUtil.response(response, WebUtil.packJsonp(callback, JSON
                    .toJSONString(new JsonResult(-1, "订单查询失败", new ResponseData()), SerializerFeatureUtil.FEATURES)));
        }
    }

    /**
     * 订单支付微信服务器异步通知
     * 
     * @param request
     * @param response
     */
    @RequestMapping(value = "/pay/notify", method = RequestMethod.POST)
    public void orderPayNotify(HttpServletRequest request, HttpServletResponse response) {
        LOG.info("[/order/pay/notify]");
        // 获取到返回的所有参数 先判断是否交易成功trade_status 再做签名校验
        // 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
        // 2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
        // 3、校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email),
        // 4、验证app_id是否为该商户本身。上述1234有任何一个验证不通过,则表明本次通知是异常通知,务必忽略。在上述验证通过后商户必须根据支付宝不同类型的业务通知,正确的进行不同的业务处理,并且过滤重复的通知结果数据。在支付宝的业务通知中,只有交易通知状态为TRADE_SUCCESS或TRADE_FINISHED时,支付宝才会认定为买家付款成功。
        if ("TRADE_SUCCESS".equals(request.getParameter("trade_status"))) {
            Enumeration<?> pNames = request.getParameterNames();
            Map<String, String> param = new HashMap<String, String>();
            try {
                while (pNames.hasMoreElements()) {
                    String pName = (String) pNames.nextElement();
                    param.put(pName, request.getParameter(pName));
                }

                boolean signVerified = AlipaySignature.rsaCheckV1(param, AlipayUtil.ALIPAY_PUBLIC_KEY,
                        AlipayConstants.CHARSET_UTF8); // 校验签名是否正确
                if (signVerified) {
                    // TODO 验签成功后
                    // 按照支付结果异步通知中的描述,对支付结果中的业务内容进行1\2\3\4二次校验,校验成功后在response中返回success,校验失败返回failure
                    LOG.info("订单支付成功:" + JSON.toJSONString(param)); 
                } else {
                    // TODO 验签失败则记录异常日志,并在response中返回failure.
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 订单退款
     * 
     * @param request
     * @param response
     * @param tradeno
     *            支付宝交易订单号
     * @param orderno
     *            商家交易订单号
     * @param callback
     */
    @RequestMapping(value = "/pay/refund", method = RequestMethod.POST)
    public void orderPayRefund(HttpServletRequest request, HttpServletResponse response, String tradeno, String orderno,
            String callback) {
        LOG.info("[/pay/refund]");
        if (StringUtil.isEmpty(tradeno) && StringUtil.isEmpty(orderno)) {
            WebUtil.response(response, WebUtil.packJsonp(callback, JSON
                    .toJSONString(new JsonResult(-1, "订单号不能为空", new ResponseData()), SerializerFeatureUtil.FEATURES)));
        }

        AlipayTradeRefundRequest alipayRequest = new AlipayTradeRefundRequest(); // 统一收单交易退款接口
        // 只需要传入业务参数
        Map<String, Object> param = new HashMap<>();
        param.put("out_trade_no", orderno); // 商户订单号
        param.put("trade_no", tradeno);// 交易金额
        param.put("refund_amount", 0.01);// 退款金额
        param.put("refund_reason", "测试支付退款");// 退款金额
        param.put("out_request_no", PayUtil.getRefundNo()); //退款单号
        alipayRequest.setBizContent(JSON.toJSONString(param)); // 不需要对json字符串转义 

        Map<String, Object> restmap = new HashMap<>();// 返回支付宝退款信息
        boolean flag = false; // 查询状态
        try {
            AlipayTradeRefundResponse alipayResponse = AlipayUtil.getAlipayClient().execute(alipayRequest);
            if (alipayResponse.isSuccess()) {
                // 调用成功,则处理业务逻辑
                if ("10000".equals(alipayResponse.getCode())) {
                    // 订单创建成功
                    flag = true;
                    restmap.put("out_trade_no", alipayResponse.getOutTradeNo());
                    restmap.put("trade_no", alipayResponse.getTradeNo());
                    restmap.put("buyer_logon_id", alipayResponse.getBuyerLogonId());// 用户的登录id
                    restmap.put("gmt_refund_pay", alipayResponse.getGmtRefundPay()); // 退看支付时间
                    restmap.put("buyer_user_id", alipayResponse.getBuyerUserId());// 买家在支付宝的用户id
                    LOG.info("订单退款结果:退款成功");
                } else {
                    LOG.info("订单查询失败:" + alipayResponse.getMsg() + ":" + alipayResponse.getSubMsg());
                }
            }
        } catch (AlipayApiException e) {
            e.printStackTrace();
        }

        if (flag) {
            // 订单查询成功
            WebUtil.response(response,
                    WebUtil.packJsonp(callback,
                            JSON.toJSONString(new JsonResult(1, "订单退款成功", new ResponseData(null, restmap)),
                                    SerializerFeatureUtil.FEATURES)));
        } else { // 订单查询失败
            WebUtil.response(response, WebUtil.packJsonp(callback, JSON
                    .toJSONString(new JsonResult(-1, "订单退款失败", new ResponseData()), SerializerFeatureUtil.FEATURES)));
        }
    }

    /**
     * 
     * @param request
     * @param response
     * @param orderno
     *            商家订单号
     * @param tradeno
     *            支付宝订单号
     * @param callback
     */
    @RequestMapping(value = "/pay/refund/query", method = RequestMethod.POST)
    public void orderPayRefundQuery(HttpServletRequest request, HttpServletResponse response, String orderno,
            String tradeno, String callback) {
        LOG.info("[/pay/refund/query]");
        if (StringUtil.isEmpty(orderno) && StringUtil.isEmpty(tradeno)) {
            WebUtil.response(response,
                    WebUtil.packJsonp(callback,
                            JSON.toJSONString(new JsonResult(-1, "商家订单号或支付宝订单号不能为空", new ResponseData()),
                                    SerializerFeatureUtil.FEATURES)));
        }

        AlipayTradeFastpayRefundQueryRequest alipayRequest = new AlipayTradeFastpayRefundQueryRequest(); // 统一收单交易退款查询
        // 只需要传入业务参数
        Map<String, Object> param = new HashMap<>();
        param.put("out_trade_no", orderno); // 商户订单号
        param.put("trade_no", tradeno);// 交易金额
        param.put("out_request_no", orderno);// 请求退款接口时,传入的退款请求号,如果在退款请求时未传入,则该值为创建交易时的外部交易号
        alipayRequest.setBizContent(JSON.toJSONString(param)); // 不需要对json字符串转义 

        Map<String, Object> restmap = new HashMap<>();// 返回支付宝退款信息
        boolean flag = false; // 查询状态
        try {
            AlipayTradeFastpayRefundQueryResponse alipayResponse = AlipayUtil.getAlipayClient().execute(alipayRequest);
            if (alipayResponse.isSuccess()) {
                // 调用成功,则处理业务逻辑
                if ("10000".equals(alipayResponse.getCode())) {
                    // 订单创建成功
                    flag = true;
                    restmap.put("out_trade_no", alipayResponse.getOutTradeNo());
                    restmap.put("trade_no", alipayResponse.getTradeNo());
                    restmap.put("out_request_no", alipayResponse.getOutRequestNo());// 退款订单号
                    restmap.put("refund_reason", alipayResponse.getRefundReason()); // 退款原因
                    restmap.put("total_amount", alipayResponse.getTotalAmount());// 订单交易金额
                    restmap.put("refund_amount", alipayResponse.getTotalAmount());// 订单退款金额
                    LOG.info("订单退款结果:退款成功");
                } else {
                    LOG.info("订单失败:" + alipayResponse.getMsg() + ":" + alipayResponse.getSubMsg());
                }
            }
        } catch (AlipayApiException e) {
            e.printStackTrace();
        }

        if (flag) {
            // 订单查询成功
            WebUtil.response(response,
                    WebUtil.packJsonp(callback,
                            JSON.toJSONString(new JsonResult(1, "订单退款成功", new ResponseData(null, restmap)),
                                    SerializerFeatureUtil.FEATURES)));
        } else { // 订单查询失败
            WebUtil.response(response, WebUtil.packJsonp(callback, JSON
                    .toJSONString(new JsonResult(-1, "订单退款失败", new ResponseData()), SerializerFeatureUtil.FEATURES)));
        }
    }

}

签名代码

SignUtils.java:支付宝支付签名实现

package org.andy.alipay.util;

import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;

public class SignUtils {

    private static final String ALGORITHM = "RSA";

    private static final String SIGN_ALGORITHMS = "SHA1WithRSA";

    private static final String DEFAULT_CHARSET = "UTF-8";

    public static String sign(String content, String privateKey) {
        try {
            PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(
                    Base64.decode(privateKey));
            KeyFactory keyf = KeyFactory.getInstance(ALGORITHM);
            PrivateKey priKey = keyf.generatePrivate(priPKCS8);

            java.security.Signature signature = java.security.Signature
                    .getInstance(SIGN_ALGORITHMS);

            signature.initSign(priKey);
            signature.update(content.getBytes(DEFAULT_CHARSET));

            byte[] signed = signature.sign();

            return new String(Base64.encode(signed));
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

}

Ali支付工具类

AlipayUtil.java:初始化AlipayClient、开发者私有、支付宝公钥等

package org.andy.alipay.util;

import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

import com.alipay.api.AlipayClient;
import com.alipay.api.AlipayConstants;
import com.alipay.api.DefaultAlipayClient;

/**
 * 创建时间:2016年11月10日 下午7:09:08
 * 
 * alipay支付
 * 
 * @author andy
 * @version 2.2
 */

public class AlipayUtil {

    public static final String ALIPAY_APPID = ConfigUtil.getProperty("alipay.appid"); // appid

    public static String APP_PRIVATE_KEY = null; // app支付私钥

    public static String ALIPAY_PUBLIC_KEY = null; // 支付宝公钥

    static {
        try {
            Resource resource = new ClassPathResource("alipay_private_key_pkcs8.pem");
            APP_PRIVATE_KEY = FileUtil.readInputStream2String(resource.getInputStream());
            resource = new ClassPathResource("alipay_public_key.pem");
            ALIPAY_PUBLIC_KEY = FileUtil.readInputStream2String(resource.getInputStream());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 统一收单交易创建接口
    private static AlipayClient alipayClient = null;

    public static AlipayClient getAlipayClient() {
        if (alipayClient == null) {
            synchronized (AlipayUtil.class) {
                if (null == alipayClient) {
                    alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", ALIPAY_APPID,
                            APP_PRIVATE_KEY, AlipayConstants.FORMAT_JSON, AlipayConstants.CHARSET_UTF8,
                            ALIPAY_PUBLIC_KEY);
                }
            }
        }
        return alipayClient;
    }
}

支付工具类

PayUtil.java:生成签名、订单生成等

package org.andy.alipay.util;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import com.alipay.api.AlipayConstants;

/**
 * 创建时间:2016年11月2日 下午7:12:44
 * 
 * @author andy
 * @version 2.2
 */

public class PayUtil {

    /**
     * 生成订单号
     * 
     * @return
     */
    public static String getTradeNo() {
        // 自增8位数 00000001
        return "TNO" + DatetimeUtil.formatDate(new Date(), DatetimeUtil.TIME_STAMP_PATTERN) + "00000001";
    }

    /**
     * 退款单号
     * 
     * @return
     */
    public static String getRefundNo() {
        // 自增8位数 00000001
        return "RNO" + DatetimeUtil.formatDate(new Date(), DatetimeUtil.TIME_STAMP_PATTERN) + "00000001";
    }

    /**
     * 退款单号
     * 
     * @return
     */
    public static String getTransferNo() {
        // 自增8位数 00000001
        return "TNO" + DatetimeUtil.formatDate(new Date(), DatetimeUtil.TIME_STAMP_PATTERN) + "00000001";
    }

    /**
     * 返回客户端ip
     * 
     * @param request
     * @return
     */
    public static String getRemoteAddrIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (StringUtil.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
            // 多次反向代理后会有多个ip值,第一个ip才是真实ip
            int index = ip.indexOf(",");
            if (index != -1) {
                return ip.substring(0, index);
            } else {
                return ip;
            }
        }
        ip = request.getHeader("X-Real-IP");
        if (StringUtil.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
            return ip;
        }
        return request.getRemoteAddr();
    }

    /**
     * 获取服务器的ip地址
     * 
     * @param request
     * @return
     */
    public static String getLocalIp(HttpServletRequest request) {
        return request.getLocalAddr();
    }

    /**
     * 创建支付随机字符串
     * 
     * @return
     */
    public static String getNonceStr() {
        return RandomUtil.randomString(RandomUtil.LETTER_NUMBER_CHAR, 32);
    }

    /**
     * 支付时间戳
     * 
     * @return
     */
    public static String payTimestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }

    /**
     * 返回签名编码拼接url
     * 
     * @param params
     * @param isEncode
     * @return
     */
    public static String getSignEncodeUrl(Map<String, String> map, boolean isEncode) {
        String sign = map.get("sign");
        String encodedSign = "";
        if (CollectionUtil.isNotEmpty(map)) {
            map.remove("sign");
            List<String> keys = new ArrayList<String>(map.keySet());
            // key排序
            Collections.sort(keys);

            StringBuilder authInfo = new StringBuilder();

            boolean first = true;// 是否第一个
            for (String key: keys) {
                if (first) {
                    first = false;
                } else {
                    authInfo.append("&");
                }
                authInfo.append(key).append("=");
                if (isEncode) {
                    try {
                        authInfo.append(URLEncoder.encode(map.get(key), AlipayConstants.CHARSET_UTF8));
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    }
                } else {
                    authInfo.append(map.get(key));
                }
            }

            try {
                encodedSign = authInfo.toString() + "&sign=" + URLEncoder.encode(sign, AlipayConstants.CHARSET_UTF8);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }

        return encodedSign.replaceAll("\\+", "%20");
    }

    /**
     * 对支付参数信息进行签名
     * 
     * @param map
     *            待签名授权信息
     * 
     * @return
     */
    public static String getSign(Map<String, String> map, String rsaKey) {
        List<String> keys = new ArrayList<String>(map.keySet());
        // key排序
        Collections.sort(keys);

        StringBuilder authInfo = new StringBuilder();
        boolean first = true;
        for (String key : keys) {
            if (first) {
                first = false;
            } else {
                authInfo.append("&");
            }
            authInfo.append(key).append("=").append(map.get(key)); 
        }

        return SignUtils.sign(authInfo.toString(), rsaKey);
    }

}

支付结果

以下为支付宝支付和支付宝退款
APP支付
APP支付退款

支付宝App支付测试完成

项目源码地址:支付宝APP支付

猜你喜欢

转载自blog.csdn.net/fengshizty/article/details/53215196