背景说明 :业务需求,需要联合APP完成Paypal的支付接入,于是开始爬坑
第一步:注册获取到paypal的商家账号(注册流程我也不知道)
第二步:登录Paypal开发者中心,创建APP应用获取到clientId和secret

android移动支付PayPal支付 安卓怎么使用paypal_编码格式


android移动支付PayPal支付 安卓怎么使用paypal_编码格式_02

第三步:创建沙盒环境下的商户及消费者账号并给消费账号设置余额 9999.99

android移动支付PayPal支付 安卓怎么使用paypal_安卓_03


!! 我没有使用paypal默认的商家及消费者账号,如果账号有问题建议自己创建对应账号

第四步:编写代码完成支付流程

1.安卓端拿到第二步中创建的 clientId 及商户账号就可以唤起Paypal的支付页面了,所以需要为支付回调做准备
2.安卓端完成商品信息展示,用户点击付款按钮后向后端申请订单,后端生成预付订单信息并返回商品对应信息及订单号
3.安卓端SDK有"customer"字段,这个字段在支付回调时Paypal会返回至回调接口,所以可以用来完成业务逻辑,本人只传了订单号

第五步:设置IPN回调地址并编写回调接口(以下皆以沙盒环境为说明)

沙盒:https://www.sandbox.paypal.com/businessmanage/account/notifications
正式环境:https://www.paypal.com/mep/dashboard

1.设置回调地址,沙盒环境是允许http的,但是正式环境得是https

android移动支付PayPal支付 安卓怎么使用paypal_编码格式_04

分割符--------

android移动支付PayPal支付 安卓怎么使用paypal_编码格式_05

3.编写支付回调的代码

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Enumeration;
    /**
     * Paypal IPN异步回调接口
     *
     * @param request
     * @param response
     * @Atuthor yixiu
     * @since 2020/9/25
     * @throws Exception
     */
 public void receivePaypalStatus(HttpServletRequest request, HttpServletResponse response) throws Exception {
        LOGGER.info("receivePaypalStatus >>>>>>>>>> 进入paypal后台支付通知");
        PrintWriter out = response.getWriter();
        try {
            /**
             * 获取paypal请求参数,并拼接验证参数
             */
            Enumeration<String> en = request.getParameterNames();
            String str = "cmd=_notify-validate";
            while (en.hasMoreElements()) {
                String paramName = en.nextElement();
                String paramValue = request.getParameter(paramName);
                //此处的编码一定要和自己的网站编码一致,不然会出现乱码,paypal回复的通知为‘INVALID’
                str = str + "&" + paramName + "=" + URLEncoder.encode(paramValue, "utf-8");
            }
            //建议在此将接受到的信息 str 记录到日志文件中以确认是否收到 IPN 信息
            LOGGER.info("paypal后台支付通知:" + str);
            /**
             * 将信息 POST 回给 PayPal 进行验证,确认回调信息的安全性
             */
            String webSrc="https://www.sandbox.paypal.com/cgi-bin/webscr";// 沙盒环境
//            String webSrc="https://www.paypal.com/cgi-bin/webscr";// 正式环境(本人是配置文件注入)
            URL u = new URL(webSrc);
            HttpURLConnection uc = (HttpURLConnection) u.openConnection();
            uc.setRequestMethod("POST");
            uc.setDoOutput(true);
            uc.setDoInput(true);
            uc.setUseCaches(false);
            //设置 HTTP 的头信息
            uc.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            PrintWriter pw = new PrintWriter(uc.getOutputStream());
            pw.println(str);
            pw.close();

            /**
             * 接受 PayPal 对 IPN 回发的回复信息
             */
            BufferedReader in = new BufferedReader(new InputStreamReader(uc.getInputStream()));
            String res = in.readLine();
            in.close();

            /**
             * 将 POST 信息分配给本地变量,可以根据您的需要添加
             */
            // 交易状态 Completed 代表交易成功
            String paymentStatus = URLEncoder.encode(request.getParameter("payment_status"), "utf-8");
            // 交易时间
            String paymentDate = request.getParameter("payment_date");
            // 交易id
            String txnId = request.getParameter("txn_id");
            // 父交易id
            String parentTxnId = request.getParameter("parent_txn_id");
            // 收款人email, 对你自己的商户
            String receiverEmail = request.getParameter("receiver_email");
            // 收款人id
            String receiverId = request.getParameter("receiver_id");
            // 付款人email
            String payerEmail = request.getParameter("payer_email");
            // 付款人id
            String payerId = request.getParameter("payer_id");
            // 交易金额
            String mcGross = request.getParameter("mc_gross");
            // 手续费
            String paymentFee = request.getParameter("payment_fee");
            // 自定义字段,我存放的订单号
            String custom = request.getParameter("custom");
            LOGGER.info("自定义参数 = " + custom);
            if (StringUtil.isBlank(res)) {
                res = "0";
            }
            LOGGER.info("paypal支付回调:  回调校验结果为  "+res);
            /**
             * 获取 PayPal 对回发信息的回复信息,判断刚才的通知是否为 PayPal 发出的
             * paymentStatus:Refunded 退款回调,Completed 支付完成回调
             */
            if ("VERIFIED".equalsIgnoreCase(res)) {
                //  订单交易成功
                if (paymentStatus.equalsIgnoreCase("Completed")) {
                    LOGGER.info("paypal支付回调:校验通过,准备介入自己业务");
                    try {
                        System.out.println("paypal支付回调:这是支付完成的回调");
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                } else {
                    LOGGER.info("paypal支付回调:回调类型是 -- " + paymentStatus);
                }
            } else if ("INVALID".equalsIgnoreCase(res)) {
                //非法信息,可以将此记录到您的日志文件中以备调查
                LOGGER.info("paypal完成支付发送IPN通知返回状态非法,请联系管理员" + str);
                out.println("confirmError");
            } else {
                //处理其他错误
                LOGGER.info("paypal完成支付发送IPN通知发生其他异常,请联系管理员,请求参数:" + str);
                out.println("confirmError");
            }
        } catch (Exception e) {
            LOGGER.info("确认付款信息发生IO异常   " + e.getMessage());
            out.println("confirmError");
        }
        out.flush();
        out.close();
    }

爬坑重点:完成上诉类容以后,支付完成并且回调进来了,但是在" if (“VERIFIED”.equalsIgnoreCase(res)) {" 时Paypal一直返回校验不成功,后来提交工单联系Paypal的技术人员得知是因为编码格式的原因,虽然上面进行了UTF-8转码,但是在Paypal端解析时是按照设置的编码格式解码,所以需要去Paypal端设置编码格式(本人设置的UTF-8):
正式环境 https://www.paypal.com/cgi-bin/customerprofileweb?cmd=_profile-language-encoding
沙箱环境:https://www.sandbox.paypal.com/cgi-bin/customerprofileweb?cmd=_profile-language-encoding
更多编码选项中找到IPN