PayPal+Java->支付接口开发

做全球性的支付,选用paypal!为什么选择paypal? 因为paypal是目前全球最大的在线支付工具,就像国内的支付宝一样,是一个基于买卖双方的第三方平台。买家只需知道你的paypal账号,即可在线直接把钱汇入你的账户,即时到账,简单方便快捷。

查看进入支付页面的历史记录:

PayPal+Java->支付接口开发_第1张图片

在集成paypal支付接口之前,首先要有一系列的准备,开发者账号啊、sdk、测试环境等等先要有,然后再码代码。集成的步骤如下:

PayPal开发者:https://developer.paypal.com/dashboard/accounts

sandbox测试账号登录的测试网址:https://www.sandbox.paypal.com

一、环境准备

注册paypal账号

注册paypal开发者账号

创建两个测试用户

创建应用,生成用于测试的clientID 和 密钥

二、代码集成

springboot环境

pom引进paypal-sdk的jar包

码代码

测试

目录

注册paypal账号

注册paypal开发者账号

创建应用,生成用于测试的clientID 和 密钥

springboot环境搭建

pom引进paypal-sdk的jar包

码代码

(1)Application.java

(2)PaypalConfig.java

(3)PaypalPaymentIntent.java

(4)PaypalPaymentMethod.java

(5)PaymentController.java

PayPalRESTExcption:

(6)PaypalService.java

(7)URLUtils.java

我的Restful风格controller:

Payment数据内容:

(8)cancel.html

(9)index.html

(10)success.html

(11)application.properties

测试

报错


现在开始

注册paypal账号

(1)在浏览器输入“安全海淘国际支付平台_安全收款外贸平台-PayPal CN” 跳转到如下界面,点击右上角的注册

PayPal+Java->支付接口开发_第2张图片

(2)选择,”创建商家用户”,根据要求填写信息,一分钟的事,注册完得去邮箱激活

PayPal+Java->支付接口开发_第3张图片

注册paypal开发者账号

(1)在浏览器输入“https://developer.paypal.com”,点击右上角的“Log into Dashboard”,用上一步创建好的账号登录

PayPal+Java->支付接口开发_第4张图片

  • 创建两个测试用户

(1)登录成功后,在左边的导航栏中点击 Sandbox 下的 Accounts

PayPal+Java->支付接口开发_第5张图片

(2)进入Acccouts界面后,可以看到系统有两个已经生成好的测试账号,但是我们不要用系统给的测试账号,很卡的,自己创建两个

(3)点击右上角的“Create Account”,创建测试用户

PayPal+Java->支付接口开发_第6张图片


<1> 先创建一个“ PERSONAL”类型的用户,国家一定要选“China”,账户余额自己填写

PayPal+Java->支付接口开发_第7张图片



 

PayPal+Java->支付接口开发_第8张图片

<2> 接着创建一个“BUSINESS”类型的用户,国家一定要选“China”,账户余额自己填写

PayPal+Java->支付接口开发_第9张图片



<3>创建好之后可以点击测试账号下的”Profile“,可以查看信息,如果没加载出来,刷新

<4>用测试账号登录测试网站查看,注意!这跟paypal官网不同!不是同一个地址,在浏览器输入:安全海淘国际支付平台_安全收款外贸平台-PayPal CN 在这里登陆测试账户

创建应用,生成用于测试的clientID 和 密钥

(1)点击左边导航栏Dashboard下的My Apps & Credentials,创建一个Live账号,下图是我已经创建好的

PayPal+Java->支付接口开发_第10张图片



(2)然后再到下边创建App

PayPal+Java->支付接口开发_第11张图片


这是我创建好的“Test”App

(3)点击刚刚创建好的App“Test”,注意看到”ClientID“ 和”Secret“(Secret如果没显示,点击下面的show就会看到,点击后show变为hide)

PayPal+Java->支付接口开发_第12张图片

springboot环境搭建

(1)新建几个包,和目录,项目结构如下

PayPal+Java->支付接口开发_第13张图片

pom引进paypal-sdk的jar包

PayPal V1

        
            com.paypal.sdk
            rest-api-sdk
            1.4.2
        

PayPal V2

            
                com.paypal.sdk
                rest-api-sdk
                1.4.2
            
            
                com.paypal.sdk
                checkout-sdk
                1.0.2
            

代码(PayPal V1)

(1)Application.java

package com.masasdani.paypal;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@EnableAutoConfiguration
@Configuration
@ComponentScan
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}


(2)PaypalConfig.java

package com.masasdani.paypal.config;
import java.util.HashMap;import java.util.Map;
import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;
import com.paypal.base.rest.APIContext;import com.paypal.base.rest.OAuthTokenCredential;import com.paypal.base.rest.PayPalRESTException;

@Configurationpublic class PaypalConfig {

    @Value("${paypal.client.app}")
    private String clientId;

    @Value("${paypal.client.secret}")
    private String clientSecret;

    @Value("${paypal.mode}")
    private String mode;

    @Bean
    public Map paypalSdkConfig(){
        Map sdkConfig = new HashMap<>();
        sdkConfig.put("mode", mode);
        return sdkConfig;
    }


    @Bean
    public OAuthTokenCredential authTokenCredential(){
        return new OAuthTokenCredential(clientId, clientSecret, paypalSdkConfig());
    }


    @Bean
    public APIContext apiContext() throws PayPalRESTException{
        APIContext apiContext = new APIContext(authTokenCredential().getAccessToken());
        apiContext.setConfigurationMap(paypalSdkConfig());
        return apiContext;
    }

}


(3)PaypalPaymentIntent.java

在 PayPal API 中,PaypalPaymentIntent 表示支付的意图或目的。

以下是 PayPal Payment Intent 的四种可能取值:

  1. sale:表示该支付是一次性的销售交易,即直接从付款方账户扣款到收款方账户。
  2. authorize:表示该支付是一个预授权,即授权收款方在未来某个时间点从付款方账户中扣款。
  3. order:表示该支付是创建一个订单,但并不立即扣款。在付款方确认订单后,可以选择扣款操作。
  4. subscription:表示该支付是用于定期订阅付款的意图,通常用于定期收取付款方的费用。
package com.masasdani.paypal.config;
public enum PaypalPaymentIntent {
    sale, authorize, order
}


(4)PaypalPaymentMethod.java

在 PayPal API 中,PaypalPaymentMethod 表示支付所使用的支付方式。

以下是 PayPal Payment Method 的一些可能取值:

  1. credit_card:信用卡支付。
  2. paypal:通过 PayPal 账户余额或链接的银行账户进行支付。
  3. paypal_credit:通过 PayPal Credit 进行支付,这是 PayPal 提供的一种信用融资服务。
  4. pay_upon_invoice:表示支付将在发票上进行,通常用于线下支付或后付款方式。
package com.masasdani.paypal.config;
public enum PaypalPaymentMethod {
    credit_card, paypal
}


(5)PaymentController.java

package com.masasdani.paypal.controller;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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.masasdani.paypal.config.PaypalPaymentIntent;
import com.masasdani.paypal.config.PaypalPaymentMethod;
import com.masasdani.paypal.service.PaypalService;
import com.masasdani.paypal.util.URLUtils;
import com.paypal.api.payments.Links;
import com.paypal.api.payments.Payment;
import com.paypal.base.rest.PayPalRESTException;

@Controller
@RequestMapping("/")
public class PaymentController {

    public static final String PAYPAL_SUCCESS_URL = "pay/success";

    public static final String PAYPAL_CANCEL_URL = "pay/cancel";

    private Logger log = LoggerFactory.getLogger(getClass());

    @Autowired
    private PaypalService paypalService;

    @RequestMapping(method = RequestMethod.GET)
    public String index(){
        return "index";
    }

    @RequestMapping(method = RequestMethod.POST, value = "pay")
    public String pay(HttpServletRequest request){ // RestFul风格里也能传入HttpServletRequest

        String cancelUrl = URLUtils.getBaseURl(request) + "/" + PAYPAL_CANCEL_URL;
        String successUrl = URLUtils.getBaseURl(request) + "/" + PAYPAL_SUCCESS_URL;

        try {
            Payment payment = paypalService.createPayment(
                    500.00,
                    "USD",
                    PaypalPaymentMethod.paypal,
                    PaypalPaymentIntent.sale,
                    "payment description",
                    cancelUrl,
                    successUrl);

            for(Links links : payment.getLinks()){

                if(links.getRel().equals("approval_url")){
                    // 输出跳转到paypal支付页面的网站,eg:https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=EC-8HL7026676482401S
                    logger.info("links is {}", links.getHref());
                    return "redirect:" + links.getHref();
                }
            }
        } catch (PayPalRESTException e) {    
            // PayPalRESTException是PayPal的REST API客户端中的一个异常类,它用于处理与PayPal REST API相关的异常情况
            // 当使用 PayPal 的 REST API 进行付款处理、交易查询、退款请求等操作时,如果发生错误或异常情况,PayPalRestException 将被抛出,以便开发者可以捕获并处理这些异常
            log.error(e.getMessage());
        }
        return "redirect:/";
    }

    @RequestMapping(method = RequestMethod.GET, value = PAYPAL_CANCEL_URL)
    public String cancelPay(){
        return "cancel";
    }


    @RequestMapping(method = RequestMethod.GET, value = PAYPAL_SUCCESS_URL)
    public String successPay(@RequestParam("paymentId") String paymentId, @RequestParam("PayerID") String payerId){ // 一定是PayerID,PayPal通常使用"PayerID"(ID和P都大小写)作为参数名称

        try {

            Payment payment = paypalService.executePayment(paymentId, payerId);
            if(payment.getState().equals("approved")){
                log.info("支付成功Payment:" + payment);
                return "success";
            }

        } catch (PayPalRESTException e) {
            log.error(e.getMessage());
        }
        return "redirect:/";
    }
}

PayPalRESTExcption:

PayPalRESTException 类中包含以下常见的属性:

  1. getMessage(): 返回异常的详细错误消息。
  2. getResponseCode(): 返回 HTTP 响应的状态码。
  3. getDetails(): 返回一个 ErrorDetails 对象,其中包含有关异常的更多详细信息。
  4. getInformationLink(): 返回一个 URL,提供有关异常的更多信息和解决方案的链接。

PayPalRESTException 是 PayPal REST API SDK 中的一个异常类,用于处理与 PayPal REST API 请求和响应相关的错误和异常情况。这个异常类通常用于开发者在使用 PayPal REST API 时捕获和处理错误,包括但不限于如下作用:

  1. 认证错误:当提供的 PayPal 客户端ID和秘密无效或过期时,可能会引发异常。

  2. 请求错误:如果请求的数据不符合 PayPal REST API 的要求,如无效的金额、货币或请求格式,也可能引发异常。

  3. 支付处理错误:在创建支付、执行支付或查询支付状态时,可能会出现与支付相关的问题,例如支付已取消或已拒绝等情况。

  4. 通信问题:如果与 PayPal 服务器之间的通信中断或出现问题,也可以引发异常。

认证错误

  • 无效的客户端ID或客户端秘密:如果你提供的 PayPal 客户端ID或客户端秘密无效,可能会抛出认证错误。

PayPalRESTException: Authentication failed with the provided credentials. 

请求错误

  • 无效的货币:如果请求中指定了无效的货币,会引发请求错误。
PayPalRESTException: Request failed with status code 400 and message: Currency is not supported.
  • 无效的金额:如果请求中的金额不合法,也可能引发请求错误。

PayPalRESTException: Request failed with status code 400 and message: Invalid total amount.

支付处理错误

  • 支付已取消:如果用户在支付页面上取消了支付,你可能会收到支付已取消的错误。
PayPalRESTException: Payment has been canceled by the user.
  • 支付已拒绝:如果支付被 PayPal 拒绝,你会收到支付已拒绝的错误。
PayPalRESTException: Payment has been declined by PayPal.

网络问题

  • 网络连接问题:当发生网络连接问题时,可能会引发网络异常。

PayPalRESTException: Network error: Connection timed out.
  • 服务器通信错误:如果 PayPal 服务器无法响应请求,也可能引发异常。

PayPalRESTException: Network error: Unable to communicate with PayPal servers.

注意:以下是两种不同的处理异常的方式!

1. 使用 try-catch 块

        · 作用:try-catch 块用于捕获和处理可能发生的异常,以便在异常发生时采取适当的措施,如记录错误、向用户提供错误消息或执行其他操作。它允许你在同一方法内部处理异常,避免异常的传播到上层调用层次。

        · 使用场景:使用 try-catch 块来处理已知的、可以预测的异常,以确保程序能够正常继续执行,而不会中断。适用于在方法内部处理异常并采取特定的操作。

        - 对于try { } catch (Exception e) { } 的 catch 中的处理方式有两种:e.printStackTrace() 与 log.error(e.getMessage()):

                - e.printStackTrace() 是异常对象 e 的一个方法,它用于将异常的堆栈跟踪信息打印到标准错误流(通常是控制台)

                  这种方式通常用于调试目的,以便开发者可以看到详细的异常信息,包括异常发生的位置和方法调用链。这会输出异常的完整堆栈跟踪,包括方法名、类名、行号等信息。

                - log.error() 是一种记录日志的方式,通常使用日志框架(如Log4j、Logback、SLF4J等)提供的方法来记录异常信息。

                  e.getMessage() 只获取异常的消息部分,通常包含异常的简要说明。这种方式更适用于生产环境,以便将异常信息记录到日志文件中,以便后续的故障排除和监控。

Logger log = LoggerFactory.getLogger(getClass());
或者:
Logger log = LoggerFactory.getLogger(YourClass.class);

2. 使用 throw 关键字抛出异常:

        · 作用:throw 关键字用于主动抛出异常,将异常传递到方法的调用者,以便在上层调用层次中进行处理。它用于表示方法无法处理异常,需要由调用者来处理。(用于将异常传播到调用方的方式,而不是用于捕获和处理异常

        · 使用场景:使用 throw 来通知调用者方法内部发生了异常,并将异常传递给调用者,由调用者负责处理异常。适用于方法内部无法处理的异常,或者需要将异常传递给上层调用层次的情况。

PayPalRESTException 提供了错误的详细信息,包括错误消息、错误代码、响应状态等,开发者可以使用这些信息来识别问题的性质和原因,并采取适当的措施来处理异常情况。

PayPalRESTException的使用:

import com.paypal.api.payments.*;
import com.paypal.base.rest.APIContext;
import com.paypal.base.rest.PayPalRESTException;

public class PayPalExample {
    public static void main(String[] args) {
        // 设置 PayPal REST API 的认证信息
        String clientId = "YOUR_CLIENT_ID";
        String clientSecret = "YOUR_CLIENT_SECRET";
        APIContext apiContext = new APIContext(clientId, clientSecret, "sandbox");

        // 创建一个 PayPal 支付对象
        Payment payment = new Payment();
        // 设置支付相关信息,例如总金额、货币等
        Amount amount = new Amount();
        amount.setCurrency("USD");
        amount.setTotal("100.00");
        payment.setAmount(amount);

        // 设置支付的执行链接
        RedirectUrls redirectUrls = new RedirectUrls();
        redirectUrls.setReturnUrl("http://example.com/return");
        redirectUrls.setCancelUrl("http://example.com/cancel");
        payment.setRedirectUrls(redirectUrls);

        // 设置支付方式为PayPal
        payment.setIntent("sale");
        payment.setPayer(new Payer("paypal"));

        try {
            Payment createdPayment = payment.create(apiContext);
            // 创建支付请求,并处理响应
        } catch (PayPalRESTException e) {
            // 处理异常情况
            System.err.println(e.getDetails());
        }
    }
}

如何根据异常的消息不同返回不同的文字给 ResponseEntity

import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpStatus;
import com.paypal.base.rest.PayPalRESTException;

public ResponseEntity processPayPalPayment() {
    try {
        // 执行 PayPal 支付操作
        // ...

        // 如果一切正常,返回成功响应
        return ResponseEntity.status(HttpStatus.OK).body("支付成功");
    } catch (PayPalRESTException e) {
        String errorMessage = e.getMessage();

        if (errorMessage.contains("Authentication failed with the provided credentials")) {
            // 无效的客户端ID或客户端秘密
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("认证失败");
        } else if (errorMessage.contains("Currency is not supported")) {
            // 无效的货币
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("无效的货币");
        } else if (errorMessage.contains("Invalid total amount")) {
            // 无效的金额
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("无效的金额");
        } else if (errorMessage.contains("Payment has been canceled by the user")) {
            // 支付已取消
            return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body("支付已取消");
        } else if (errorMessage.contains("Payment has been declined by PayPal")) {
            // 支付已拒绝
            return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body("支付已拒绝");
        } else if (errorMessage.contains("Network error: Connection timed out")) {
            // 网络连接问题
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body("网络连接超时");
        } else {
            // 其他异常情况
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("支付失败,请联系客服");
        }
    }
}

HttpStatus 是 Spring Framework 提供了一组常用的 HTTP 状态码,以便在构建 RESTful API 或 Web应用程序时使用。这些常用的状态码位于 org.springframework.http.HttpStatus 枚举中,包括 200 OK、201 CREATED、400 BAD_REQUEST、401 UNAUTHORIZED、404 NOT_FOUND、500 INTERNAL_SERVER_ERROR 等等。你可以直接使用这些常量来设置响应的状态码,而无需自行定义。也可以直接:

return new CommonResult(e.getResponsecode(), e.getMessage(), null);


(6)PaypalService.java

package com.masasdani.paypal.service;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.masasdani.paypal.config.PaypalPaymentIntent;
import com.masasdani.paypal.config.PaypalPaymentMethod;
import com.paypal.api.payments.Amount;
import com.paypal.api.payments.Payer;
import com.paypal.api.payments.Payment; // 并不是自己写的实体类Payment
import com.paypal.api.payments.PaymentExecution;
import com.paypal.api.payments.RedirectUrls;
import com.paypal.api.payments.Transaction;
import com.paypal.base.rest.APIContext;
import com.paypal.base.rest.PayPalRESTException;

@Service
public class PaypalService {

    @Autowired
    private APIContext apiContext;

    // 创建支付
    public Payment createPayment(
            Double total,
            String currency,
            PaypalPaymentMethod method,
            PaypalPaymentIntent intent,
            String description,
            String cancelUrl,
            String successUrl) throws PayPalRESTException{
        // 接受参数包括总金额(total)、货币类型(currency)、支付方法(method)、支付意图(intent)、描述(description)、取消 URL(cancelUrl)和成功 URL(successUrl)。在方法内部,它使用这些参数创建一个支付请求,并返回创建的 Payment 对象

        Amount amount = new Amount();
        amount.setCurrency(currency);
        amount.setTotal(String.format("%.2f", total));

        Transaction transaction = new Transaction();
        transaction.setDescription(description);
        transaction.setAmount(amount);

        List transactions = new ArrayList<>();
        transactions.add(transaction);

        Payer payer = new Payer();
        payer.setPaymentMethod(method.toString());

        Payment payment = new Payment();
        payment.setIntent(intent.toString());
        payment.setPayer(payer);
        payment.setTransactions(transactions);

        RedirectUrls redirectUrls = new RedirectUrls();

        // Paypal取消支付回调链接
        redirectUrls.setCancelUrl(cancelUrl);

        // Paypal付完款回调链接
        redirectUrls.setReturnUrl(successUrl);

        // Paypal付完款回调链接:如果要其他数据作为参数传递给成功支付后的回调URL即控制器类中的successPay方法,则对回调URL进行拼接:redirectUrls.setReturnUrl(successUrl + "?param1=" + param1 + "¶m2=" + param2 + "¶m3=" + paaram3);
        redirectUrls.setReturnUrl(successUrl + "?userId=" + entity.getUserId() + "&totalFee=" + entity.getTotalFee() + "&payFrom=" + entity.getPayFrom());

        payment.setRedirectUrls(redirectUrls);

        return payment.create(apiContext);
    }

    // 执行支付
public Payment executePayment(String paymentId, String payerId) throws PayPalRESTException{

        // 接受支付 ID(paymentId)和付款人 ID(payerId)作为参数。在方法内部,它使用这些参数创建一个 PaymentExecution 对象,并使用支付 ID 和付款人 ID 执行支付请求,返回执行支付后的 Payment 对象
        Payment payment = new Payment();
        payment.setId(paymentId);

        PaymentExecution paymentExecute = new PaymentExecution();
        paymentExecute.setPayerId(payerId);

        return payment.execute(apiContext, paymentExecute);
    }
}


(7)URLUtils.java

package com.masasdani.paypal.util;
import javax.servlet.http.HttpServletRequest;
public class URLUtils {

    public static String getBaseURl(HttpServletRequest request) {

        String scheme = request.getScheme(); // 获取请求的协议,如 "http" 或 "https"
        String serverName = request.getServerName(); // 获取服务器名称
        int serverPort = request.getServerPort();// 获取服务器端口
        String contextPath = request.getContextPath();// 获取上下文路径

        // 建议在此处插入以下代码查看上下文路径是否符合期望,因为有时候可能部署之类导致不对
        System.out.println("contextPanth: " + contextPath);
        StringBuffer url =  new StringBuffer();
        url.append(scheme).append("://").append(serverName);

        // 判断服务器端口是否为标准的 HTTP(80)或 HTTPS(443)端口,决定是否将端口号添加到 URL 中

        // 当浏览器发起 HTTP 请求时,通常会使用默认的端口号,即 HTTP 使用 80 端口,HTTPS 使用 443 端口。这些端口号是默认的,因此在 URL 中不需要显式指定
        // 然而,如果应用程序部署在非默认的端口上,例如使用自定义的端口号(例如 8080、3000 等),则需要将该端口号包含在 URL 中,以确保浏览器能够正确地访问应用程序

        if ((serverPort != 80) && (serverPort != 443)) {
            url.append(":").append(serverPort);
        }
        url.append(contextPath);

        if(url.toString().endsWith("/")){ // URL 是否以斜杠结尾,如果不是,则添加斜杠
            url.append("/");
        }
        return url.toString(); // 获取当前请求的完整 URL
    }
}

比如我的controller中的cancelUrl与successUrl就需要改成这样,因为在上面方法这种获取到的context为空,重定向之后就会报404路径不存在,可能是其他配置问题:

我的Restful风格controller:

先生成预订单,将 url 返回给前端,前端进行跳转,之后即可获得取消/成功回调。

package com.harmony.supreme.modular.paypal.controller;
import com.harmony.supreme.modular.paypal.entity.PaypalOrderParam;
import com.harmony.supreme.modular.paypal.service.PaypalService;
import com.harmony.supreme.modular.paypal.util.URLUtils;
import com.paypal.api.payments.Links;
import com.paypal.api.payments.Payment;
import com.paypal.base.rest.PayPalRESTException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;


@RestController
@RequestMapping("/marketing")
public class PaypalController {

    public static final String PAYPAL_SUCCESS_URL = "/marketing/paypal/success";
    public static final String PAYPAL_CANCEL_URL = "/marketing/paypal/cancel";

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private PaypalService paypalService;

    /**
     * 生成订单
     */
    @PostMapping("/paypal")

public CommonResult paypal(@RequestBody PaypalOrderParam entity, HttpServletRequest request, HttpServletResponse response) {


        String cancelUrl = URLUtils.getBaseUrl(request) + '/' +PAYPAL_CANCEL_URL;
        logger.info("cancelUrl is {}", cancelUrl); // 日志打印
        String successUrl = URLUtils.getBaseUrl(request) + '/' +PAYPAL_SUCCESS_URL;
        logger.info("successUrl is {}", successUrl); // 日志打印

        try {
            //Payment payment = paypalService.createPayment(entity.getTotalFee(), entity.getUserId(), entity.getContext(),
            Payment payment = paypalService.createPayment(5.0, "1686590877167329281", "视频",
                    new Date(), cancelUrl, successUrl);
            for (Links links : payment.getLinks()) {
                if (links.getRel().equals("approval_url")) {
                    logger.info("links.getHref() is {}", links.getHref());
                    logger.info("支付订单返回paymentId:" + payment.getId());
                    logger.info("支付订单状态state:" + payment.getState());
                    logger.info("支付订单创建时间:" + payment.getCreateTime());

                    String href = links.getHref();
                    return CommonResult.data(href);
                }
            }
        } catch (PayPalRESTException e) {
            logger.error(e.getMessage());
            return new CommonResult<>(e.getResponsecode(), e.getMessage(), null);
        }
        return CommonResult.error("错误");
    }

    /**
     * 取消支付
     */
    @GetMapping("/paypal/cancel")
    public CommonResult cancelPay(){
        return CommonResult.data("用户取消支付");
    }

    /**
     * 支付操作
     */
    @GetMapping("/paypal/success")
    public CommonResult successPay(@RequestParam("paymentId") String paymentId, @RequestParam("PayerID") String payerId) { // 一定是PayerID,PayPal通常使用"PayerID"(ID和P都大小写)作为参数名称
        try {
            Payment payment = paypalService.executePayment(paymentId, payerId);
            // 支付成功
            if(payment.getState().equals("approved")){
                // 订单号
                String saleId = payment.getTransactions().get(0).getRelatedResources().get(0).getSale().getId();
                paypalService.addPay(request, saleId);
                logger.info("PDT通知:交易成功回调");
                logger.info("付款人账户:"+payment.getPayer().getPayerInfo().getEmail());
                logger.info("支付订单Id: {}",paymentId);
                logger.info("支付订单状态state:" + payment.getState());
                logger.info("交易订单Id: {}",saleId);
                logger.info("交易订单状态state:"+payment.getTransactions().get(0).getRelatedResources().get(0).getSale().getState());
                logger.info("交易订单支付时间:"+payment.getTransactions().get(0).getRelatedResources().get(0).getSale().getCreateTime());
                return CommonResult.data("支付成功");
            }
        } catch (PayPalRESTException e) {
            logger.info(e.getMessage());
            return new CommonResult(e.getResponsecode(), e.getMessage(), null);
        }
        return CommonResult.data("支付失败");
    }
}

Payment数据内容:

最后支付成功后返回的Payment数据如下:

支付成功Payment:{
  "id": "PAYID-MUY7X3I47036021V43838732",
  "intent": "sale",
  "payer": {
    "payment_method": "paypal",
    "status": "VERIFIED",
    "payer_info": {
      "email": "[email protected]",
      "first_name": "John",
      "last_name": "Doe",
      "payer_id": "PRKXLARWJVWQL",
      "country_code": "C2",
      "shipping_address": {
        "recipient_name": "Doe John",
        "line1": "NO 1 Nan Jin Road",
        "city": "Shanghai",
        "country_code": "C2",
        "postal_code": "200000",
        "state": "Shanghai"
      }
    }
  },
  "cart": "92948520FV7438203",
  "transactions": [
    {
      "transactions": [],
      "related_resources": [
        {
          "sale": {
            "id": "9GJ58424N8173751G",
            "amount": {
              "currency": "USD",
              "total": "5.00",
              "details": {
                "shipping": "0.00",
                "subtotal": "5.00",
                "handling_fee": "0.00",
                "insurance": "0.00",
                "shipping_discount": "0.00"
              }
            },
            "payment_mode": "INSTANT_TRANSFER",
            "state": "completed",
            "protection_eligibility": "ELIGIBLE",
            "protection_eligibility_type": "ITEM_NOT_RECEIVED_ELIGIBLE,UNAUTHORIZED_PAYMENT_ELIGIBLE",
            "transaction_fee": {
              "currency": "USD",
              "value": "0.47"
            },
            "parent_payment": "PAYID-MUY7X3I47036021V43838732",
            "create_time": "2023-10-20T07:04:41Z",
            "update_time": "2023-10-20T07:04:41Z",
            "links": [
              {
                "href": "https://api.sandbox.paypal.com/v1/payments/sale/9GJ58424N8173751G",
                "rel": "self",
                "method": "GET"
              },
              {
                "href": "https://api.sandbox.paypal.com/v1/payments/sale/9GJ58424N8173751G/refund",
                "rel": "refund",
                "method": "POST"
              },
              {
                "href": "https://api.sandbox.paypal.com/v1/payments/payment/PAYID-MUY7X3I47036021V43838732",
                "rel": "parent_payment",
                "method": "GET"
              }
            ]
          }
        }
      ],
      "amount": {
        "currency": "USD",
        "total": "5.00",
        "details": {
          "shipping": "0.00",
          "subtotal": "5.00",
          "handling_fee": "0.00",
          "insurance": "0.00",
          "shipping_discount": "0.00"
        }
      },
      "payee": {
        "email": "[email protected]",
        "merchant_id": "AZ5ZMSER4CFS2"
      },
      "description": "视频",
      "item_list": {
        "items": [],
        "shipping_address": {
          "recipient_name": "Doe John",
          "line1": "NO 1 Nan Jin Road",
          "city": "Shanghai",
          "country_code": "C2",
          "postal_code": "200000",
          "state": "Shanghai"
        }
      }
    }
  ],
  "failed_transactions": [],
  "state": "approved",
  "create_time": "2023-10-20T04:02:53Z",
  "update_time": "2023-10-20T07:04:41Z",
  "links": [
    {
      "href": "https://api.sandbox.paypal.com/v1/payments/payment/PAYID-MUY7X3I47036021V43838732",
      "rel": "self",
      "method": "GET"
    }
  ]
}

或者:

支付成功Payment:{
  "id": "PAYID-MUZC3FI3NX78464UJ4562005",
  "intent": "sale",
  "payer": {
    "payment_method": "paypal",
    "status": "VERIFIED",
    "payer_info": {
      "email": "[email protected]",
      "first_name": "John",
      "last_name": "Doe",
      "payer_id": "PRKXLARWJVWQL",
      "country_code": "C2",
      "shipping_address": {
        "recipient_name": "Doe John",
        "line1": "NO 1 Nan Jin Road",
        "city": "Shanghai",
        "country_code": "C2",
        "postal_code": "200000",
        "state": "Shanghai"
      }
    }
  },
  "cart": "8HL7026676482401S",
  "transactions": [
    {
      "transactions": [],
      "related_resources": [
        {
          "sale": {
            "id": "0KC07001909543205",
            "amount": {
              "currency": "USD",
              "total": "5.00",
              "details": {
                "shipping": "0.00",
                "subtotal": "5.00",
                "handling_fee": "0.00",
                "insurance": "0.00",
                "shipping_discount": "0.00"
              }
            },
            "payment_mode": "INSTANT_TRANSFER",
            "state": "completed",
            "protection_eligibility": "ELIGIBLE",
            "protection_eligibility_type": "ITEM_NOT_RECEIVED_ELIGIBLE,UNAUTHORIZED_PAYMENT_ELIGIBLE",
            "transaction_fee": {
              "currency": "USD",
              "value": "0.47"
            },
            "parent_payment": "PAYID-MUZC3FI3NX78464UJ4562005",
            "create_time": "2023-10-20T07:35:21Z",
            "update_time": "2023-10-20T07:35:21Z",
            "links": [
              {
                "href": "https://api.sandbox.paypal.com/v1/payments/sale/0KC07001909543205",
                "rel": "self",
                "method": "GET"
              },
              {
                "href": "https://api.sandbox.paypal.com/v1/payments/sale/0KC07001909543205/refund",
                "rel": "refund",
                "method": "POST"
              },
              {
                "href": "https://api.sandbox.paypal.com/v1/payments/payment/PAYID-MUZC3FI3NX78464UJ4562005",
                "rel": "parent_payment",
                "method": "GET"
              }
            ]
          }
        }
      ],
      "amount": {
        "currency": "USD",
        "total": "5.00",
        "details": {
          "shipping": "0.00",
          "subtotal": "5.00",
          "handling_fee": "0.00",
          "insurance": "0.00",
          "shipping_discount": "0.00"
        }
      },
      "payee": {
        "email": "[email protected]",
        "merchant_id": "AZ5ZMSER4CFS2"
      },
      "description": "合恕支付充值",
      "item_list": {
        "items": [],
        "shipping_address": {
          "recipient_name": "Doe John",
          "line1": "NO 1 Nan Jin Road",
          "city": "Shanghai",
          "country_code": "C2",
          "postal_code": "200000",
          "state": "Shanghai"
        }
      }
    }
  ],
  "failed_transactions": [],
  "state": "approved",
  "create_time": "2023-10-20T07:34:44Z",
  "update_time": "2023-10-20T07:35:21Z",
  "links": [
    {
      "href": "https://api.sandbox.paypal.com/v1/payments/payment/PAYID-MUZC3FI3NX78464UJ4562005",
      "rel": "self",
      "method": "GET"
    }
  ]
}

在PayPal的Payment对象中,两种状态:支付状态(Payment Status)、交易状态(Transaction Status)。

支付状态(Payment Status

取值:payment.getId()

  1. created(已创建):表示支付订单已创建,但尚未获得批准或处理。这通常是支付的初始状态。
  2. approved(已批准):表示支付订单已被批准,但尚未完成。在此状态下,您可以继续完成支付流程。
  3. failed(失败):表示支付订单支付失败或被取消。通常由于付款信息不正确、余额不足、支付提供商问题或其他原因。
  4. canceled(已取消):表示支付已被取消。
  5. expired(已过期):表示支付订单已过期,支付未完成。

交易状态(Transaction Status):

取值:payment.getTransactions().get(0).getRelatedResources().get(0).getSale().getId()

(其中:PaypalPaymentIntent有order、sale、authorize三种状态,所以获取时分别将getSale()换为getOrder()、getAuthorize()即可)

  1. Pending(待处理):交易正在处理中,等待进一步的操作或确认。
  2. Completed(已完成):交易已成功完成。
  3. Refunded(已退款):交易金额已被全部或部分退款。
  4. Reversed(已撤销):交易金额已被撤销。
  5. Denied(已拒绝):交易请求被拒绝。

交易订单id为:payment.getTransactions().get(0).getRelatedResources().get(0).getSale().getId()或修改Sale为其他两种状态。


(8)cancel.html


    Insert title here


    

Canceled by user


(9)index.html





    Insert title here



(10)success.html





    Insert title here


    

Payment Success


(11)application.properties

paypal.client.app是App的CilentID, paypal.client.secret是Secret

server.port: 8088
spring.thymeleaf.cache=false

paypal.mode=sandbox
paypal.client.app=AeVqmY_pxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxKYniPwzfL1jGR
paypal.client.secret=ELibZhExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxUsWOA_-

代码(PayPal V2)

(1)Controller.java

import cn.dev33.satoken.stp.StpUtil;
import com.alibaba.excel.util.StringUtils;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import com.paypal.api.payments.Payment;
import com.paypal.base.rest.PayPalRESTException;
import com.paypal.http.HttpResponse;
import com.paypal.orders.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


@RestController
public class PaypalV2Controller {

    @Value("${paypal.client.mode}")
    private String mode;

    @Value("${paypal.client.app}")
    private String clientId;

    @Value("${paypal.client.secret}")
    private String secret;


    @Resource
    private PaypalV2Service paypalV2Service;

    /**
     * 生成订单
     */
    @ApiOperationSupport(order = 1)
    @ApiOperation("生成订单")
    @PostMapping("/pay/paypal")
    public CommonResult paypalV2(@RequestBody PaypalOrderParam entity, HttpServletRequest request, HttpServletResponse response) {

        // 在哪里部署就写哪个域名xxxxx,因为在服务器上如按PayPal V1中所写的方法结果xxxxx还是localhost,此路径在服务器上将报错
        String cancelUrl = "http://xxxxx:xxxx/pay/cancel";
        String successUrl = "http://xxxxx:xxxx/pay/success";

        if (StringUtils.isBlank(entity.getUserId())) {
            entity.setUserId(StpUtil.getLoginIdAsString());
        }

        try {
            String href = paypalV2Service.createPayment(entity, cancelUrl, successUrl);
            return CommonResult.data(href);
        } catch (PayPalRESTException e) {
            return new CommonResult<>(e.getResponsecode(), e.getMessage(), null);
        }
    }

    /**
     * 取消支付
     */
    @ApiOperationSupport(order = 2)
    @ApiOperation("取消支付")
    @GetMapping("/pay/cancel")
    public String cancelPay(){
        return "已取消支付,请返回上一级页面";
    }

    /**
     * 支付成功
     */
    @ApiOperationSupport(order = 3)
    @ApiOperation("支付成功")
    @GetMapping("/pay/success")
    public String successPay(@RequestParam("token") String token, @RequestParam("userId") String userId, @RequestParam("beanNum") String beanNum) {

        //捕获订单 进行支付
        HttpResponse response = null;
        OrdersCaptureRequest ordersCaptureRequest = new OrdersCaptureRequest(token);
        ordersCaptureRequest.requestBody(new OrderRequest());

        PayPalClient payPalClient = new PayPalClient();
        try {
            //环境判定sandbox 或 live
            response = payPalClient.client(mode, clientId, secret).execute(ordersCaptureRequest);

            for (PurchaseUnit purchaseUnit : response.result().purchaseUnits()) {
                for (Capture capture : purchaseUnit.payments().captures()) {
                    if ("COMPLETED".equals(capture.status())) {
                        //支付成功
                        // 订单号
                        String saleId = capture.id();
                        String fee = capture.amount().value();
                        paypalV2Service.addPay(fee, saleId, userId, beanNum);
                        return "支付成功,请返回上一级页面";
                    }
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return "支付失败,请返回上一级页面";
    }
}

(2)ServiceImpl.java

public interface PaypalV2Service {

    /**
     * 创建支付:实体类、取消支付时的重定向 URL、支付成功时的重定向 URL
     */
    public String createPayment(PaypalOrderParam entity, String cancelUrl, String successUrl) throws PayPalRESTException;

    /**
     * 支付成功生成订单
     */
    void addPay(String fee, String saleId, String userId, String beanNum);

}



import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fhs.common.utils.StringUtil;
import com.paypal.core.PayPalHttpClient;
import com.paypal.http.HttpResponse;
import com.paypal.orders.*;
import com.paypal.orders.Order;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;

@Service
public class PaypalV2ServiceImpl implements PaypalV2Service {

    @Value("${paypal.client.mode}")
    private String mode;

    @Value("${paypal.client.app}")
    private String clientId;

    @Value("${paypal.client.secret}")
    private String secret;

    @Resource
    private UserPayService userPayService;

    @Resource
    private UserOrderService userOrderService;

    @Resource
    private SysRechargePlanService sysRechargePlanService;

    @Override
    public String createPayment(PaypalOrderParam entity, String cancelUrl, String successUrl) {

        PayPalClient payPalClient = new PayPalClient();
        // 设置环境沙盒或生产
        PayPalHttpClient client = payPalClient.client(mode, clientId, secret);

        //回调参数(支付成功success路径所携带的参数)
        Map sParaTemp = new HashMap();

        BigDecimal bigDecimal = new BigDecimal("100");
        String totalMoney = String.valueOf(entity.getTotalFee().divide(bigDecimal));
        // 回调的参数可以多设置几个
        // 插入用户USERID
        if (StringUtil.isEmpty(entity.getUserId())) {
            entity.setUserId(StpUtil.getLoginIdAsString());
        }
        sParaTemp.put("userId", entity.getUserId());

        // 根据价钱查找方案
        QueryWrapper wrapper = new QueryWrapper<>();
        wrapper.eq("TYPE", "BALANCE")
                .eq("FEE", Float.valueOf(totalMoney));
        SysRechargePlan one = sysRechargePlanService.getOne(wrapper);
        // 插入所得金豆数
        sParaTemp.put("beanNum", String.valueOf(one.getUnit()));

        String url = successUrl + paramsConvertUrl(sParaTemp);

        // eg:回调链接:http://localhost:xxxx/pay/success?beanNum=150&userId=1655776615868264450
        System.out.println("回调链接:"+url);

        // 配置请求参数
        OrderRequest orderRequest = new OrderRequest();
        orderRequest.checkoutPaymentIntent("CAPTURE");
        List purchaseUnits = new ArrayList<>();
        purchaseUnits.add(new PurchaseUnitRequest().amountWithBreakdown(new AmountWithBreakdown().currencyCode("USD").value(totalMoney)));
        orderRequest.purchaseUnits(purchaseUnits);
        orderRequest.applicationContext(new ApplicationContext().returnUrl(url).cancelUrl(cancelUrl));
        OrdersCreateRequest request = new OrdersCreateRequest().requestBody(orderRequest);

        HttpResponse response;
        try {
            response = client.execute(request);
            Order order = response.result();
            String payHref = null;
            String status = order.status();
            if (status.equals("CREATED")) {
                List links = order.links();
                for (LinkDescription linkDescription : links) {
                    if (linkDescription.rel().equals("approve")) {
                        payHref = linkDescription.href();
                    }
                }
            }
            return payHref;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    private static String paramsConvertUrl(Map params) {
        StringBuilder urlParams = new StringBuilder("?");
        Set> entries = params.entrySet();
        for (Map.Entry entry : params.entrySet()) {
            urlParams.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
        }
        String urlParamsStr = urlParams.toString();
        return urlParamsStr.substring(0, urlParamsStr.length()-1);
    }

    @Override
    public void addPay(String fee, String saleId,  String userId, String beanNum) {
        UserPay userPay = new UserPay();
        userPay.setUserId(userId);
        userPay.setOrderNo(saleId);
        userPay.setPayFrom("PayPal");
        userPay.setPayType("YES");
        userPay.setTotalFee(new BigDecimal(beanNum));
        userPay.setCreateTime(new Date());
        userPay.setSuccessTime(new Date());
        userPay.setCreateUser(userId);
        userPayService.addPay(userPay);

        // 金豆
        UserOrder userOrder = new UserOrder();
        userOrder.setUserId(userId);
        userOrder.setOrderNo(saleId);
        userOrder.setPaySource("COST");
        userOrder.setPayStatus("INCOME");
        userOrder.setType("BALANCE");
        userOrder.setUnit(new BigDecimal(beanNum));
        userOrder.setCreateUser(userId);
        userOrderService.addPaypal(userOrder);
    }
}

(3)PayPalClient.java

import com.paypal.core.PayPalEnvironment;
import com.paypal.core.PayPalHttpClient;

public class PayPalClient {

    public PayPalHttpClient client(String mode, String clientId, String clientSecret) {
        PayPalEnvironment environment = mode.equals("live") ? new PayPalEnvironment.Live(clientId, clientSecret) : new PayPalEnvironment.Sandbox(clientId, clientSecret);
        return new PayPalHttpClient(environment);
    }
}

支付成功/失败后返回上一级(前端)页面

// 将控制器方法中的返回值类型设置为 String ,将 return 中的内容修改为如下:
// 即成功后访问页面,1秒后跳转回上一级(前端)页面

// 支付成功(返回一级后将返回到支付链接:https://www.sandbox.paypal.com/checkoutnow?token=7LK561281D3524115,此时会显示PayPal支付结果
return "支付成功,返回上一级页面";
// 支付失败(返回一级后将返回到支付链接:https://www.sandbox.paypal.com/checkoutnow?token=7LK561281D3524115,此时会显示PayPal支付结果
return "支付失败,返回上一级页面";

// 上一级:即跳转后“支付成功,返回上一级页面”页面/“支付失败,返回上一级页面”页面的上一级
// 上几级就负几,-2、-3...

测试

(1)启动项目

PayPal+Java->支付接口开发_第14张图片

(2)在浏览器输入localhost:8088

PayPal+Java->支付接口开发_第15张图片

(3)点击paypal后,会跳到paypal的登录界面,登录测试账号(PRESONAL)后点击继续即可扣费,扣500$(具体数额可在controller中自定义)

Payment payment = paypalService.createPayment(
                    500.00,
                    "USD",
                    PaypalPaymentMethod.paypal,
                    PaypalPaymentIntent.sale,
                    "payment description",
                    cancelUrl,
                    successUrl);

PayPal+Java->支付接口开发_第16张图片

(4)到安全海淘国际支付平台_安全收款外贸平台-PayPal CN 登录测试账号看看余额有没有变化

PayPal+Java->支付接口开发_第17张图片

报错

Error code : 400 with response : {"name":"DUPLICATE_REQUEST_ID","message":"The value of PayPal-Request-Id header has already been used","information_link":"https://developer.paypal.com/docs/api/payments/v1/#error-DUPLICATE_REQUEST_ID","debug_id":"a3d876b7ebd44"}

服务器未知异常:response-code: 400 details: name: DUPLICATE_REQUEST_ID message: The value of PayPal-Request-Id header has already been used details: null debug-id: a3d876b7ebd44 information-link: https://developer.paypal.com/docs/api/payments/v1/#error-DUPLICATE_REQUEST_ID, 请求地址:http://localhost:82/marketing/paypal/success

原因:

报错的原因是请求中的 PayPal-Request-Id 标头的值已经被使用过,导致请求被认为是重复的。

PayPal-Request-Id 是一个用于标识 PayPal API 请求的唯一标识符。每次进行 PayPal API 请求时,应该使用一个新的、唯一的 PayPal-Request-Id 值。如果重复使用相同的 PayPal-Request-Id 值进行请求,PayPal 服务器会将其视为重复请求,并返回 DUPLICATE_REQUEST_ID 错误。

解决:

修改PaypalConfig:

@Configuration
public class PaypalConfig {

    @Value("${paypal.client.app}")
    private String clientId;
    @Value("${paypal.client.secret}")
    private String clientSecret;
    @Value("${paypal.client.mode}")
    private String mode;

    @Bean
    public Map paypalSdkConfig() {
        Map sdkConfig = new HashMap<>();
        sdkConfig.put("mode", mode);
        return sdkConfig;
    }

    public OAuthTokenCredential authTokenCredential() {
        return new OAuthTokenCredential(clientId, clientSecret, paypalSdkConfig());
    }

    public APIContext apiContext() throws PayPalRESTException {
        APIContext apiContext = new APIContext(authTokenCredential().getAccessToken());
        apiContext.setConfigurationMap(paypalSdkConfig());
        return apiContext;
    }
}

修改PaypalService,每次每次使用apiContext时生成新的requestId:

@Service
public class PaypalServiceImpl implements PaypalService {

    @Resource
    private PaypalConfig paypalConfig;

    @Resource
    private UserPayService userPayService;

    @Resource
    private UserOrderService userOrderService;

    @Override
    public Payment createPayment(PaypalOrderParam entity, String cancelUrl, String successUrl) throws PayPalRESTException {

        Amount amount = new Amount();
        amount.setCurrency("USD");  // 美金
        // total 是以分为单位,所以得除以100
        BigDecimal divisor = new BigDecimal("100");
        amount.setTotal(String.format("%.2f", entity.getTotalFee().divide(divisor)));

        Transaction transaction = new Transaction();
        transaction.setAmount(amount);
        transaction.setDescription(StringUtils.isBlank(entity.getContext())?"合恕支付充值":entity.getContext());

        List transactionList = new ArrayList<>();
        transactionList.add(transaction);

        Payer payer = new Payer();
        payer.setPaymentMethod("paypal"); // paypal购买

        Payment payment = new Payment();
        payment.setIntent("sale");// 直接购买
        payment.setPayer(payer);
        payment.setTransactions(transactionList);

        RedirectUrls redirectUrls = new RedirectUrls();
        redirectUrls.setCancelUrl(cancelUrl);
        redirectUrls.setReturnUrl(successUrl + "?userId="+entity.getUserId()+"&totalFee="+entity.getTotalFee()+"&payFrom="+ entity.getPayFrom());
        payment.setRedirectUrls(redirectUrls);

        // 每次使用apiContext时生成新的requestId
        APIContext apiContext = paypalConfig.apiContext();

        return payment.create(apiContext);
    }

    @Override
    public Payment executePayment(String paymentId, String payerId) throws PayPalRESTException {
        Payment payment = new Payment();
        payment.setId(paymentId);
        PaymentExecution paymentExecution = new PaymentExecution();
        paymentExecution.setPayerId(payerId);

        // 每次使用apiContext时生成新的requestId
        APIContext apiContext = paypalConfig.apiContext();
        return payment.execute(apiContext, paymentExecution);
    }
}

你可能感兴趣的:(#,支付,Java,java,开发语言,1024程序员节)