当前位置:   article > 正文

PayPal+Java->支付接口开发_paypal支付java'对接

paypal支付java'对接

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

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

在集成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” 跳转到如下界面,点击右上角的注册

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

注册paypal开发者账号

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

  • 创建两个测试用户

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

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

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


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



 

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



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

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

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

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



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


这是我创建好的“Test”App

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

springboot环境搭建

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

pom引进paypal-sdk的jar包

PayPal V1

  1. <dependency>
  2. <groupId>com.paypal.sdk</groupId>
  3. <artifactId>rest-api-sdk</artifactId>
  4. <version>1.4.2</version>
  5. </dependency>

PayPal V2

  1. <dependency>
  2. <groupId>com.paypal.sdk</groupId>
  3. <artifactId>rest-api-sdk</artifactId>
  4. <version>1.4.2</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>com.paypal.sdk</groupId>
  8. <artifactId>checkout-sdk</artifactId>
  9. <version>1.0.2</version>
  10. </dependency>

代码(PayPal V1)

(1)Application.java

  1. package com.masasdani.paypal;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
  4. import org.springframework.context.annotation.ComponentScan;
  5. import org.springframework.context.annotation.Configuration;
  6. @EnableAutoConfiguration
  7. @Configuration
  8. @ComponentScan
  9. public class Application {
  10. public static void main(String[] args) {
  11. SpringApplication.run(Application.class, args);
  12. }
  13. }


(2)PaypalConfig.java

  1. package com.masasdani.paypal.config;
  2. import java.util.HashMap;import java.util.Map;
  3. import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;
  4. import com.paypal.base.rest.APIContext;import com.paypal.base.rest.OAuthTokenCredential;import com.paypal.base.rest.PayPalRESTException;
  5. @Configurationpublic class PaypalConfig {
  6. @Value("${paypal.client.app}")
  7. private String clientId;
  8. @Value("${paypal.client.secret}")
  9. private String clientSecret;
  10. @Value("${paypal.mode}")
  11. private String mode;
  12. @Bean
  13. public Map<String, String> paypalSdkConfig(){
  14. Map<String, String> sdkConfig = new HashMap<>();
  15. sdkConfig.put("mode", mode);
  16. return sdkConfig;
  17. }
  18. @Bean
  19. public OAuthTokenCredential authTokenCredential(){
  20. return new OAuthTokenCredential(clientId, clientSecret, paypalSdkConfig());
  21. }
  22. @Bean
  23. public APIContext apiContext() throws PayPalRESTException{
  24. APIContext apiContext = new APIContext(authTokenCredential().getAccessToken());
  25. apiContext.setConfigurationMap(paypalSdkConfig());
  26. return apiContext;
  27. }
  28. }


(3)PaypalPaymentIntent.java

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

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

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


(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:表示支付将在发票上进行,通常用于线下支付或后付款方式。
  1. package com.masasdani.paypal.config;
  2. public enum PaypalPaymentMethod {
  3. credit_card, paypal
  4. }


(5)PaymentController.java

  1. package com.masasdani.paypal.controller;
  2. import javax.servlet.http.HttpServletRequest;
  3. import org.slf4j.Logger;
  4. import org.slf4j.LoggerFactory;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.stereotype.Controller;
  7. import org.springframework.web.bind.annotation.RequestMapping;
  8. import org.springframework.web.bind.annotation.RequestMethod;
  9. import org.springframework.web.bind.annotation.RequestParam;
  10. import com.masasdani.paypal.config.PaypalPaymentIntent;
  11. import com.masasdani.paypal.config.PaypalPaymentMethod;
  12. import com.masasdani.paypal.service.PaypalService;
  13. import com.masasdani.paypal.util.URLUtils;
  14. import com.paypal.api.payments.Links;
  15. import com.paypal.api.payments.Payment;
  16. import com.paypal.base.rest.PayPalRESTException;
  17. @Controller
  18. @RequestMapping("/")
  19. public class PaymentController {
  20. public static final String PAYPAL_SUCCESS_URL = "pay/success";
  21. public static final String PAYPAL_CANCEL_URL = "pay/cancel";
  22. private Logger log = LoggerFactory.getLogger(getClass());
  23. @Autowired
  24. private PaypalService paypalService;
  25. @RequestMapping(method = RequestMethod.GET)
  26. public String index(){
  27. return "index";
  28. }
  29. @RequestMapping(method = RequestMethod.POST, value = "pay")
  30. public String pay(HttpServletRequest request){ // RestFul风格里也能传入HttpServletRequest
  31. String cancelUrl = URLUtils.getBaseURl(request) + "/" + PAYPAL_CANCEL_URL;
  32. String successUrl = URLUtils.getBaseURl(request) + "/" + PAYPAL_SUCCESS_URL;
  33. try {
  34. Payment payment = paypalService.createPayment(
  35. 500.00,
  36. "USD",
  37. PaypalPaymentMethod.paypal,
  38. PaypalPaymentIntent.sale,
  39. "payment description",
  40. cancelUrl,
  41. successUrl);
  42. for(Links links : payment.getLinks()){
  43. if(links.getRel().equals("approval_url")){
  44. // 输出跳转到paypal支付页面的网站,eg:https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=EC-8HL7026676482401S
  45. logger.info("links is {}", links.getHref());
  46. return "redirect:" + links.getHref();
  47. }
  48. }
  49. } catch (PayPalRESTException e) {
  50. // PayPalRESTException是PayPal的REST API客户端中的一个异常类,它用于处理与PayPal REST API相关的异常情况
  51. // 当使用 PayPal 的 REST API 进行付款处理、交易查询、退款请求等操作时,如果发生错误或异常情况,PayPalRestException 将被抛出,以便开发者可以捕获并处理这些异常
  52. log.error(e.getMessage());
  53. }
  54. return "redirect:/";
  55. }
  56. @RequestMapping(method = RequestMethod.GET, value = PAYPAL_CANCEL_URL)
  57. public String cancelPay(){
  58. return "cancel";
  59. }
  60. @RequestMapping(method = RequestMethod.GET, value = PAYPAL_SUCCESS_URL)
  61. public String successPay(@RequestParam("paymentId") String paymentId, @RequestParam("PayerID") String payerId){ // 一定是PayerID,PayPal通常使用"PayerID"(ID和P都大小写)作为参数名称
  62. try {
  63. Payment payment = paypalService.executePayment(paymentId, payerId);
  64. if(payment.getState().equals("approved")){
  65. log.info("支付成功Payment:" + payment);
  66. return "success";
  67. }
  68. } catch (PayPalRESTException e) {
  69. log.error(e.getMessage());
  70. }
  71. return "redirect:/";
  72. }
  73. }

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

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

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

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

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

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

PayPalRESTException的使用:

  1. import com.paypal.api.payments.*;
  2. import com.paypal.base.rest.APIContext;
  3. import com.paypal.base.rest.PayPalRESTException;
  4. public class PayPalExample {
  5. public static void main(String[] args) {
  6. // 设置 PayPal REST API 的认证信息
  7. String clientId = "YOUR_CLIENT_ID";
  8. String clientSecret = "YOUR_CLIENT_SECRET";
  9. APIContext apiContext = new APIContext(clientId, clientSecret, "sandbox");
  10. // 创建一个 PayPal 支付对象
  11. Payment payment = new Payment();
  12. // 设置支付相关信息,例如总金额、货币等
  13. Amount amount = new Amount();
  14. amount.setCurrency("USD");
  15. amount.setTotal("100.00");
  16. payment.setAmount(amount);
  17. // 设置支付的执行链接
  18. RedirectUrls redirectUrls = new RedirectUrls();
  19. redirectUrls.setReturnUrl("http://example.com/return");
  20. redirectUrls.setCancelUrl("http://example.com/cancel");
  21. payment.setRedirectUrls(redirectUrls);
  22. // 设置支付方式为PayPal
  23. payment.setIntent("sale");
  24. payment.setPayer(new Payer("paypal"));
  25. try {
  26. Payment createdPayment = payment.create(apiContext);
  27. // 创建支付请求,并处理响应
  28. } catch (PayPalRESTException e) {
  29. // 处理异常情况
  30. System.err.println(e.getDetails());
  31. }
  32. }
  33. }

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

  1. import org.springframework.http.ResponseEntity;
  2. import org.springframework.http.HttpStatus;
  3. import com.paypal.base.rest.PayPalRESTException;
  4. public ResponseEntity<String> processPayPalPayment() {
  5. try {
  6. // 执行 PayPal 支付操作
  7. // ...
  8. // 如果一切正常,返回成功响应
  9. return ResponseEntity.status(HttpStatus.OK).body("支付成功");
  10. } catch (PayPalRESTException e) {
  11. String errorMessage = e.getMessage();
  12. if (errorMessage.contains("Authentication failed with the provided credentials")) {
  13. // 无效的客户端ID或客户端秘密
  14. return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("认证失败");
  15. } else if (errorMessage.contains("Currency is not supported")) {
  16. // 无效的货币
  17. return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("无效的货币");
  18. } else if (errorMessage.contains("Invalid total amount")) {
  19. // 无效的金额
  20. return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("无效的金额");
  21. } else if (errorMessage.contains("Payment has been canceled by the user")) {
  22. // 支付已取消
  23. return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body("支付已取消");
  24. } else if (errorMessage.contains("Payment has been declined by PayPal")) {
  25. // 支付已拒绝
  26. return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body("支付已拒绝");
  27. } else if (errorMessage.contains("Network error: Connection timed out")) {
  28. // 网络连接问题
  29. return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body("网络连接超时");
  30. } else {
  31. // 其他异常情况
  32. return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("支付失败,请联系客服");
  33. }
  34. }
  35. }

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

  1. package com.masasdani.paypal.service;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.stereotype.Service;
  6. import com.masasdani.paypal.config.PaypalPaymentIntent;
  7. import com.masasdani.paypal.config.PaypalPaymentMethod;
  8. import com.paypal.api.payments.Amount;
  9. import com.paypal.api.payments.Payer;
  10. import com.paypal.api.payments.Payment; // 并不是自己写的实体类Payment
  11. import com.paypal.api.payments.PaymentExecution;
  12. import com.paypal.api.payments.RedirectUrls;
  13. import com.paypal.api.payments.Transaction;
  14. import com.paypal.base.rest.APIContext;
  15. import com.paypal.base.rest.PayPalRESTException;
  16. @Service
  17. public class PaypalService {
  18. @Autowired
  19. private APIContext apiContext;
  20. // 创建支付
  21. public Payment createPayment(
  22. Double total,
  23. String currency,
  24. PaypalPaymentMethod method,
  25. PaypalPaymentIntent intent,
  26. String description,
  27. String cancelUrl,
  28. String successUrl) throws PayPalRESTException{
  29. // 接受参数包括总金额(total)、货币类型(currency)、支付方法(method)、支付意图(intent)、描述(description)、取消 URL(cancelUrl)和成功 URL(successUrl)。在方法内部,它使用这些参数创建一个支付请求,并返回创建的 Payment 对象
  30. Amount amount = new Amount();
  31. amount.setCurrency(currency);
  32. amount.setTotal(String.format("%.2f", total));
  33. Transaction transaction = new Transaction();
  34. transaction.setDescription(description);
  35. transaction.setAmount(amount);
  36. List<Transaction> transactions = new ArrayList<>();
  37. transactions.add(transaction);
  38. Payer payer = new Payer();
  39. payer.setPaymentMethod(method.toString());
  40. Payment payment = new Payment();
  41. payment.setIntent(intent.toString());
  42. payment.setPayer(payer);
  43. payment.setTransactions(transactions);
  44. RedirectUrls redirectUrls = new RedirectUrls();
  45. // Paypal取消支付回调链接
  46. redirectUrls.setCancelUrl(cancelUrl);
  47. // Paypal付完款回调链接
  48. redirectUrls.setReturnUrl(successUrl);
  49. // Paypal付完款回调链接:如果要其他数据作为参数传递给成功支付后的回调URL即控制器类中的successPay方法,则对回调URL进行拼接:redirectUrls.setReturnUrl(successUrl + "?param1=" + param1 + "¶m2=" + param2 + "¶m3=" + paaram3);
  50. redirectUrls.setReturnUrl(successUrl + "?userId=" + entity.getUserId() + "&totalFee=" + entity.getTotalFee() + "&payFrom=" + entity.getPayFrom());
  51. payment.setRedirectUrls(redirectUrls);
  52. return payment.create(apiContext);
  53. }
  54. // 执行支付
  55. public Payment executePayment(String paymentId, String payerId) throws PayPalRESTException{
  56. // 接受支付 ID(paymentId)和付款人 ID(payerId)作为参数。在方法内部,它使用这些参数创建一个 PaymentExecution 对象,并使用支付 ID 和付款人 ID 执行支付请求,返回执行支付后的 Payment 对象
  57. Payment payment = new Payment();
  58. payment.setId(paymentId);
  59. PaymentExecution paymentExecute = new PaymentExecution();
  60. paymentExecute.setPayerId(payerId);
  61. return payment.execute(apiContext, paymentExecute);
  62. }
  63. }


(7)URLUtils.java

  1. package com.masasdani.paypal.util;
  2. import javax.servlet.http.HttpServletRequest;
  3. public class URLUtils {
  4. public static String getBaseURl(HttpServletRequest request) {
  5. String scheme = request.getScheme(); // 获取请求的协议,如 "http" 或 "https"
  6. String serverName = request.getServerName(); // 获取服务器名称
  7. int serverPort = request.getServerPort();// 获取服务器端口
  8. String contextPath = request.getContextPath();// 获取上下文路径
  9. // 建议在此处插入以下代码查看上下文路径是否符合期望,因为有时候可能部署之类导致不对
  10. System.out.println("contextPanth: " + contextPath);
  11. StringBuffer url = new StringBuffer();
  12. url.append(scheme).append("://").append(serverName);
  13. // 判断服务器端口是否为标准的 HTTP(80)或 HTTPS(443)端口,决定是否将端口号添加到 URL 中
  14. // 当浏览器发起 HTTP 请求时,通常会使用默认的端口号,即 HTTP 使用 80 端口,HTTPS 使用 443 端口。这些端口号是默认的,因此在 URL 中不需要显式指定
  15. // 然而,如果应用程序部署在非默认的端口上,例如使用自定义的端口号(例如 8080、3000 等),则需要将该端口号包含在 URL 中,以确保浏览器能够正确地访问应用程序
  16. if ((serverPort != 80) && (serverPort != 443)) {
  17. url.append(":").append(serverPort);
  18. }
  19. url.append(contextPath);
  20. if(url.toString().endsWith("/")){ // URL 是否以斜杠结尾,如果不是,则添加斜杠
  21. url.append("/");
  22. }
  23. return url.toString(); // 获取当前请求的完整 URL
  24. }
  25. }

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

我的Restful风格controller:

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

  1. package com.harmony.supreme.modular.paypal.controller;
  2. import com.harmony.supreme.modular.paypal.entity.PaypalOrderParam;
  3. import com.harmony.supreme.modular.paypal.service.PaypalService;
  4. import com.harmony.supreme.modular.paypal.util.URLUtils;
  5. import com.paypal.api.payments.Links;
  6. import com.paypal.api.payments.Payment;
  7. import com.paypal.base.rest.PayPalRESTException;
  8. import org.slf4j.Logger;
  9. import org.slf4j.LoggerFactory;
  10. import org.springframework.beans.factory.annotation.Autowired;
  11. import org.springframework.web.bind.annotation.*;
  12. import javax.servlet.http.HttpServletRequest;
  13. import javax.servlet.http.HttpServletResponse;
  14. import java.io.IOException;
  15. import java.util.Date;
  16. @RestController
  17. @RequestMapping("/marketing")
  18. public class PaypalController {
  19. public static final String PAYPAL_SUCCESS_URL = "/marketing/paypal/success";
  20. public static final String PAYPAL_CANCEL_URL = "/marketing/paypal/cancel";
  21. private Logger logger = LoggerFactory.getLogger(getClass());
  22. @Autowired
  23. private PaypalService paypalService;
  24. /**
  25. * 生成订单
  26. */
  27. @PostMapping("/paypal")
  28. public CommonResult<String> paypal(@RequestBody PaypalOrderParam entity, HttpServletRequest request, HttpServletResponse response) {
  29. String cancelUrl = URLUtils.getBaseUrl(request) + '/' +PAYPAL_CANCEL_URL;
  30. logger.info("cancelUrl is {}", cancelUrl); // 日志打印
  31. String successUrl = URLUtils.getBaseUrl(request) + '/' +PAYPAL_SUCCESS_URL;
  32. logger.info("successUrl is {}", successUrl); // 日志打印
  33. try {
  34. //Payment payment = paypalService.createPayment(entity.getTotalFee(), entity.getUserId(), entity.getContext(),
  35. Payment payment = paypalService.createPayment(5.0, "1686590877167329281", "视频",
  36. new Date(), cancelUrl, successUrl);
  37. for (Links links : payment.getLinks()) {
  38. if (links.getRel().equals("approval_url")) {
  39. logger.info("links.getHref() is {}", links.getHref());
  40. logger.info("支付订单返回paymentId:" + payment.getId());
  41. logger.info("支付订单状态state:" + payment.getState());
  42. logger.info("支付订单创建时间:" + payment.getCreateTime());
  43. String href = links.getHref();
  44. return CommonResult.data(href);
  45. }
  46. }
  47. } catch (PayPalRESTException e) {
  48. logger.error(e.getMessage());
  49. return new CommonResult<>(e.getResponsecode(), e.getMessage(), null);
  50. }
  51. return CommonResult.error("错误");
  52. }
  53. /**
  54. * 取消支付
  55. */
  56. @GetMapping("/paypal/cancel")
  57. public CommonResult<String> cancelPay(){
  58. return CommonResult.data("用户取消支付");
  59. }
  60. /**
  61. * 支付操作
  62. */
  63. @GetMapping("/paypal/success")
  64. public CommonResult<String> successPay(@RequestParam("paymentId") String paymentId, @RequestParam("PayerID") String payerId) { // 一定是PayerID,PayPal通常使用"PayerID"(ID和P都大小写)作为参数名称
  65. try {
  66. Payment payment = paypalService.executePayment(paymentId, payerId);
  67. // 支付成功
  68. if(payment.getState().equals("approved")){
  69. // 订单号
  70. String saleId = payment.getTransactions().get(0).getRelatedResources().get(0).getSale().getId();
  71. paypalService.addPay(request, saleId);
  72. logger.info("PDT通知:交易成功回调");
  73. logger.info("付款人账户:"+payment.getPayer().getPayerInfo().getEmail());
  74. logger.info("支付订单Id: {}",paymentId);
  75. logger.info("支付订单状态state:" + payment.getState());
  76. logger.info("交易订单Id: {}",saleId);
  77. logger.info("交易订单状态state:"+payment.getTransactions().get(0).getRelatedResources().get(0).getSale().getState());
  78. logger.info("交易订单支付时间:"+payment.getTransactions().get(0).getRelatedResources().get(0).getSale().getCreateTime());
  79. return CommonResult.data("支付成功");
  80. }
  81. } catch (PayPalRESTException e) {
  82. logger.info(e.getMessage());
  83. return new CommonResult(e.getResponsecode(), e.getMessage(), null);
  84. }
  85. return CommonResult.data("支付失败");
  86. }
  87. }

Payment数据内容:

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

  1. 支付成功Payment:{
  2. "id": "PAYID-MUY7X3I47036021V43838732",
  3. "intent": "sale",
  4. "payer": {
  5. "payment_method": "paypal",
  6. "status": "VERIFIED",
  7. "payer_info": {
  8. "email": "sb-kuob927781461@personal.example.com",
  9. "first_name": "John",
  10. "last_name": "Doe",
  11. "payer_id": "PRKXLARWJVWQL",
  12. "country_code": "C2",
  13. "shipping_address": {
  14. "recipient_name": "Doe John",
  15. "line1": "NO 1 Nan Jin Road",
  16. "city": "Shanghai",
  17. "country_code": "C2",
  18. "postal_code": "200000",
  19. "state": "Shanghai"
  20. }
  21. }
  22. },
  23. "cart": "92948520FV7438203",
  24. "transactions": [
  25. {
  26. "transactions": [],
  27. "related_resources": [
  28. {
  29. "sale": {
  30. "id": "9GJ58424N8173751G",
  31. "amount": {
  32. "currency": "USD",
  33. "total": "5.00",
  34. "details": {
  35. "shipping": "0.00",
  36. "subtotal": "5.00",
  37. "handling_fee": "0.00",
  38. "insurance": "0.00",
  39. "shipping_discount": "0.00"
  40. }
  41. },
  42. "payment_mode": "INSTANT_TRANSFER",
  43. "state": "completed",
  44. "protection_eligibility": "ELIGIBLE",
  45. "protection_eligibility_type": "ITEM_NOT_RECEIVED_ELIGIBLE,UNAUTHORIZED_PAYMENT_ELIGIBLE",
  46. "transaction_fee": {
  47. "currency": "USD",
  48. "value": "0.47"
  49. },
  50. "parent_payment": "PAYID-MUY7X3I47036021V43838732",
  51. "create_time": "2023-10-20T07:04:41Z",
  52. "update_time": "2023-10-20T07:04:41Z",
  53. "links": [
  54. {
  55. "href": "https://api.sandbox.paypal.com/v1/payments/sale/9GJ58424N8173751G",
  56. "rel": "self",
  57. "method": "GET"
  58. },
  59. {
  60. "href": "https://api.sandbox.paypal.com/v1/payments/sale/9GJ58424N8173751G/refund",
  61. "rel": "refund",
  62. "method": "POST"
  63. },
  64. {
  65. "href": "https://api.sandbox.paypal.com/v1/payments/payment/PAYID-MUY7X3I47036021V43838732",
  66. "rel": "parent_payment",
  67. "method": "GET"
  68. }
  69. ]
  70. }
  71. }
  72. ],
  73. "amount": {
  74. "currency": "USD",
  75. "total": "5.00",
  76. "details": {
  77. "shipping": "0.00",
  78. "subtotal": "5.00",
  79. "handling_fee": "0.00",
  80. "insurance": "0.00",
  81. "shipping_discount": "0.00"
  82. }
  83. },
  84. "payee": {
  85. "email": "sb-gdewt15359677@business.example.com",
  86. "merchant_id": "AZ5ZMSER4CFS2"
  87. },
  88. "description": "视频",
  89. "item_list": {
  90. "items": [],
  91. "shipping_address": {
  92. "recipient_name": "Doe John",
  93. "line1": "NO 1 Nan Jin Road",
  94. "city": "Shanghai",
  95. "country_code": "C2",
  96. "postal_code": "200000",
  97. "state": "Shanghai"
  98. }
  99. }
  100. }
  101. ],
  102. "failed_transactions": [],
  103. "state": "approved",
  104. "create_time": "2023-10-20T04:02:53Z",
  105. "update_time": "2023-10-20T07:04:41Z",
  106. "links": [
  107. {
  108. "href": "https://api.sandbox.paypal.com/v1/payments/payment/PAYID-MUY7X3I47036021V43838732",
  109. "rel": "self",
  110. "method": "GET"
  111. }
  112. ]
  113. }

或者:

  1. 支付成功Payment:{
  2. "id": "PAYID-MUZC3FI3NX78464UJ4562005",
  3. "intent": "sale",
  4. "payer": {
  5. "payment_method": "paypal",
  6. "status": "VERIFIED",
  7. "payer_info": {
  8. "email": "sb-kuob927781461@personal.example.com",
  9. "first_name": "John",
  10. "last_name": "Doe",
  11. "payer_id": "PRKXLARWJVWQL",
  12. "country_code": "C2",
  13. "shipping_address": {
  14. "recipient_name": "Doe John",
  15. "line1": "NO 1 Nan Jin Road",
  16. "city": "Shanghai",
  17. "country_code": "C2",
  18. "postal_code": "200000",
  19. "state": "Shanghai"
  20. }
  21. }
  22. },
  23. "cart": "8HL7026676482401S",
  24. "transactions": [
  25. {
  26. "transactions": [],
  27. "related_resources": [
  28. {
  29. "sale": {
  30. "id": "0KC07001909543205",
  31. "amount": {
  32. "currency": "USD",
  33. "total": "5.00",
  34. "details": {
  35. "shipping": "0.00",
  36. "subtotal": "5.00",
  37. "handling_fee": "0.00",
  38. "insurance": "0.00",
  39. "shipping_discount": "0.00"
  40. }
  41. },
  42. "payment_mode": "INSTANT_TRANSFER",
  43. "state": "completed",
  44. "protection_eligibility": "ELIGIBLE",
  45. "protection_eligibility_type": "ITEM_NOT_RECEIVED_ELIGIBLE,UNAUTHORIZED_PAYMENT_ELIGIBLE",
  46. "transaction_fee": {
  47. "currency": "USD",
  48. "value": "0.47"
  49. },
  50. "parent_payment": "PAYID-MUZC3FI3NX78464UJ4562005",
  51. "create_time": "2023-10-20T07:35:21Z",
  52. "update_time": "2023-10-20T07:35:21Z",
  53. "links": [
  54. {
  55. "href": "https://api.sandbox.paypal.com/v1/payments/sale/0KC07001909543205",
  56. "rel": "self",
  57. "method": "GET"
  58. },
  59. {
  60. "href": "https://api.sandbox.paypal.com/v1/payments/sale/0KC07001909543205/refund",
  61. "rel": "refund",
  62. "method": "POST"
  63. },
  64. {
  65. "href": "https://api.sandbox.paypal.com/v1/payments/payment/PAYID-MUZC3FI3NX78464UJ4562005",
  66. "rel": "parent_payment",
  67. "method": "GET"
  68. }
  69. ]
  70. }
  71. }
  72. ],
  73. "amount": {
  74. "currency": "USD",
  75. "total": "5.00",
  76. "details": {
  77. "shipping": "0.00",
  78. "subtotal": "5.00",
  79. "handling_fee": "0.00",
  80. "insurance": "0.00",
  81. "shipping_discount": "0.00"
  82. }
  83. },
  84. "payee": {
  85. "email": "sb-gdewt15359677@business.example.com",
  86. "merchant_id": "AZ5ZMSER4CFS2"
  87. },
  88. "description": "合恕支付充值",
  89. "item_list": {
  90. "items": [],
  91. "shipping_address": {
  92. "recipient_name": "Doe John",
  93. "line1": "NO 1 Nan Jin Road",
  94. "city": "Shanghai",
  95. "country_code": "C2",
  96. "postal_code": "200000",
  97. "state": "Shanghai"
  98. }
  99. }
  100. }
  101. ],
  102. "failed_transactions": [],
  103. "state": "approved",
  104. "create_time": "2023-10-20T07:34:44Z",
  105. "update_time": "2023-10-20T07:35:21Z",
  106. "links": [
  107. {
  108. "href": "https://api.sandbox.paypal.com/v1/payments/payment/PAYID-MUZC3FI3NX78464UJ4562005",
  109. "rel": "self",
  110. "method": "GET"
  111. }
  112. ]
  113. }

在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

  1. <!DOCTYPE html><html><head><meta charset="UTF-8" />
  2. <title>Insert title here</title>
  3. </head>
  4. <body>
  5. <h1>Canceled by user</h1>
  6. </body>
  7. </html>


(9)index.html

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8" />
  5. <title>Insert title here</title>
  6. </head>
  7. <body>
  8. <form method="post" th:action="@{/pay}">
  9. <button type="submit">
  10. <img src="images/paypal.jpg" width="100px;" height="30px;"/>
  11. </button>
  12. </form>
  13. </body>
  14. </html>


(10)success.html

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8" />
  5. <title>Insert title here</title>
  6. </head>
  7. <body>
  8. <h1>Payment Success</h1>
  9. </body>
  10. </html>


(11)application.properties

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

  1. server.port: 8088
  2. spring.thymeleaf.cache=false
  3. paypal.mode=sandbox
  4. paypal.client.app=AeVqmY_pxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxKYniPwzfL1jGR
  5. paypal.client.secret=ELibZhExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxUsWOA_-

代码(PayPal V2)

(1)Controller.java

  1. import cn.dev33.satoken.stp.StpUtil;
  2. import com.alibaba.excel.util.StringUtils;
  3. import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
  4. import com.github.xiaoymin.knife4j.annotations.ApiSupport;
  5. import com.paypal.api.payments.Payment;
  6. import com.paypal.base.rest.PayPalRESTException;
  7. import com.paypal.http.HttpResponse;
  8. import com.paypal.orders.*;
  9. import io.swagger.annotations.Api;
  10. import io.swagger.annotations.ApiOperation;
  11. import org.springframework.beans.factory.annotation.Value;
  12. import org.springframework.validation.annotation.Validated;
  13. import org.springframework.web.bind.annotation.*;
  14. import javax.annotation.Resource;
  15. import javax.servlet.http.HttpServletRequest;
  16. import javax.servlet.http.HttpServletResponse;
  17. import java.io.IOException;
  18. @RestController
  19. public class PaypalV2Controller {
  20. @Value("${paypal.client.mode}")
  21. private String mode;
  22. @Value("${paypal.client.app}")
  23. private String clientId;
  24. @Value("${paypal.client.secret}")
  25. private String secret;
  26. @Resource
  27. private PaypalV2Service paypalV2Service;
  28. /**
  29. * 生成订单
  30. */
  31. @ApiOperationSupport(order = 1)
  32. @ApiOperation("生成订单")
  33. @PostMapping("/pay/paypal")
  34. public CommonResult<String> paypalV2(@RequestBody PaypalOrderParam entity, HttpServletRequest request, HttpServletResponse response) {
  35. // 在哪里部署就写哪个域名xxxxx,因为在服务器上如按PayPal V1中所写的方法结果xxxxx还是localhost,此路径在服务器上将报错
  36. String cancelUrl = "http://xxxxx:xxxx/pay/cancel";
  37. String successUrl = "http://xxxxx:xxxx/pay/success";
  38. if (StringUtils.isBlank(entity.getUserId())) {
  39. entity.setUserId(StpUtil.getLoginIdAsString());
  40. }
  41. try {
  42. String href = paypalV2Service.createPayment(entity, cancelUrl, successUrl);
  43. return CommonResult.data(href);
  44. } catch (PayPalRESTException e) {
  45. return new CommonResult<>(e.getResponsecode(), e.getMessage(), null);
  46. }
  47. }
  48. /**
  49. * 取消支付
  50. */
  51. @ApiOperationSupport(order = 2)
  52. @ApiOperation("取消支付")
  53. @GetMapping("/pay/cancel")
  54. public String cancelPay(){
  55. return "已取消支付,请返回上一级页面";
  56. }
  57. /**
  58. * 支付成功
  59. */
  60. @ApiOperationSupport(order = 3)
  61. @ApiOperation("支付成功")
  62. @GetMapping("/pay/success")
  63. public String successPay(@RequestParam("token") String token, @RequestParam("userId") String userId, @RequestParam("beanNum") String beanNum) {
  64. //捕获订单 进行支付
  65. HttpResponse<Order> response = null;
  66. OrdersCaptureRequest ordersCaptureRequest = new OrdersCaptureRequest(token);
  67. ordersCaptureRequest.requestBody(new OrderRequest());
  68. PayPalClient payPalClient = new PayPalClient();
  69. try {
  70. //环境判定sandbox 或 live
  71. response = payPalClient.client(mode, clientId, secret).execute(ordersCaptureRequest);
  72. for (PurchaseUnit purchaseUnit : response.result().purchaseUnits()) {
  73. for (Capture capture : purchaseUnit.payments().captures()) {
  74. if ("COMPLETED".equals(capture.status())) {
  75. //支付成功
  76. // 订单号
  77. String saleId = capture.id();
  78. String fee = capture.amount().value();
  79. paypalV2Service.addPay(fee, saleId, userId, beanNum);
  80. return "支付成功,请返回上一级页面";
  81. }
  82. }
  83. }
  84. } catch (IOException e) {
  85. throw new RuntimeException(e);
  86. }
  87. return "支付失败,请返回上一级页面";
  88. }
  89. }

(2)ServiceImpl.java

  1. public interface PaypalV2Service {
  2. /**
  3. * 创建支付:实体类、取消支付时的重定向 URL、支付成功时的重定向 URL
  4. */
  5. public String createPayment(PaypalOrderParam entity, String cancelUrl, String successUrl) throws PayPalRESTException;
  6. /**
  7. * 支付成功生成订单
  8. */
  9. void addPay(String fee, String saleId, String userId, String beanNum);
  10. }
  11. import cn.dev33.satoken.stp.StpUtil;
  12. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  13. import com.fhs.common.utils.StringUtil;
  14. import com.paypal.core.PayPalHttpClient;
  15. import com.paypal.http.HttpResponse;
  16. import com.paypal.orders.*;
  17. import com.paypal.orders.Order;
  18. import org.springframework.beans.factory.annotation.Value;
  19. import org.springframework.stereotype.Service;
  20. import javax.annotation.Resource;
  21. import javax.servlet.http.HttpServletRequest;
  22. import java.io.IOException;
  23. import java.math.BigDecimal;
  24. import java.util.*;
  25. @Service
  26. public class PaypalV2ServiceImpl implements PaypalV2Service {
  27. @Value("${paypal.client.mode}")
  28. private String mode;
  29. @Value("${paypal.client.app}")
  30. private String clientId;
  31. @Value("${paypal.client.secret}")
  32. private String secret;
  33. @Resource
  34. private UserPayService userPayService;
  35. @Resource
  36. private UserOrderService userOrderService;
  37. @Resource
  38. private SysRechargePlanService sysRechargePlanService;
  39. @Override
  40. public String createPayment(PaypalOrderParam entity, String cancelUrl, String successUrl) {
  41. PayPalClient payPalClient = new PayPalClient();
  42. // 设置环境沙盒或生产
  43. PayPalHttpClient client = payPalClient.client(mode, clientId, secret);
  44. //回调参数(支付成功success路径所携带的参数)
  45. Map<String, String> sParaTemp = new HashMap<String, String>();
  46. BigDecimal bigDecimal = new BigDecimal("100");
  47. String totalMoney = String.valueOf(entity.getTotalFee().divide(bigDecimal));
  48. // 回调的参数可以多设置几个
  49. // 插入用户USERID
  50. if (StringUtil.isEmpty(entity.getUserId())) {
  51. entity.setUserId(StpUtil.getLoginIdAsString());
  52. }
  53. sParaTemp.put("userId", entity.getUserId());
  54. // 根据价钱查找方案
  55. QueryWrapper<SysRechargePlan> wrapper = new QueryWrapper<>();
  56. wrapper.eq("TYPE", "BALANCE")
  57. .eq("FEE", Float.valueOf(totalMoney));
  58. SysRechargePlan one = sysRechargePlanService.getOne(wrapper);
  59. // 插入所得金豆数
  60. sParaTemp.put("beanNum", String.valueOf(one.getUnit()));
  61. String url = successUrl + paramsConvertUrl(sParaTemp);
  62. // eg:回调链接:http://localhost:xxxx/pay/success?beanNum=150&userId=1655776615868264450
  63. System.out.println("回调链接:"+url);
  64. // 配置请求参数
  65. OrderRequest orderRequest = new OrderRequest();
  66. orderRequest.checkoutPaymentIntent("CAPTURE");
  67. List<PurchaseUnitRequest> purchaseUnits = new ArrayList<>();
  68. purchaseUnits.add(new PurchaseUnitRequest().amountWithBreakdown(new AmountWithBreakdown().currencyCode("USD").value(totalMoney)));
  69. orderRequest.purchaseUnits(purchaseUnits);
  70. orderRequest.applicationContext(new ApplicationContext().returnUrl(url).cancelUrl(cancelUrl));
  71. OrdersCreateRequest request = new OrdersCreateRequest().requestBody(orderRequest);
  72. HttpResponse<Order> response;
  73. try {
  74. response = client.execute(request);
  75. Order order = response.result();
  76. String payHref = null;
  77. String status = order.status();
  78. if (status.equals("CREATED")) {
  79. List<LinkDescription> links = order.links();
  80. for (LinkDescription linkDescription : links) {
  81. if (linkDescription.rel().equals("approve")) {
  82. payHref = linkDescription.href();
  83. }
  84. }
  85. }
  86. return payHref;
  87. } catch (IOException e) {
  88. e.printStackTrace();
  89. }
  90. return null;
  91. }
  92. private static String paramsConvertUrl(Map<String, String> params) {
  93. StringBuilder urlParams = new StringBuilder("?");
  94. Set<Map.Entry<String, String>> entries = params.entrySet();
  95. for (Map.Entry<String, String> entry : params.entrySet()) {
  96. urlParams.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
  97. }
  98. String urlParamsStr = urlParams.toString();
  99. return urlParamsStr.substring(0, urlParamsStr.length()-1);
  100. }
  101. @Override
  102. public void addPay(String fee, String saleId, String userId, String beanNum) {
  103. UserPay userPay = new UserPay();
  104. userPay.setUserId(userId);
  105. userPay.setOrderNo(saleId);
  106. userPay.setPayFrom("PayPal");
  107. userPay.setPayType("YES");
  108. userPay.setTotalFee(new BigDecimal(beanNum));
  109. userPay.setCreateTime(new Date());
  110. userPay.setSuccessTime(new Date());
  111. userPay.setCreateUser(userId);
  112. userPayService.addPay(userPay);
  113. // 金豆
  114. UserOrder userOrder = new UserOrder();
  115. userOrder.setUserId(userId);
  116. userOrder.setOrderNo(saleId);
  117. userOrder.setPaySource("COST");
  118. userOrder.setPayStatus("INCOME");
  119. userOrder.setType("BALANCE");
  120. userOrder.setUnit(new BigDecimal(beanNum));
  121. userOrder.setCreateUser(userId);
  122. userOrderService.addPaypal(userOrder);
  123. }
  124. }

(3)PayPalClient.java

  1. import com.paypal.core.PayPalEnvironment;
  2. import com.paypal.core.PayPalHttpClient;
  3. public class PayPalClient {
  4. public PayPalHttpClient client(String mode, String clientId, String clientSecret) {
  5. PayPalEnvironment environment = mode.equals("live") ? new PayPalEnvironment.Live(clientId, clientSecret) : new PayPalEnvironment.Sandbox(clientId, clientSecret);
  6. return new PayPalHttpClient(environment);
  7. }
  8. }

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

  1. // 将控制器方法中的返回值类型设置为 String ,将 return 中的内容修改为如下:
  2. // 即成功后访问页面,1秒后跳转回上一级(前端)页面
  3. // 支付成功(返回一级后将返回到支付链接:https://www.sandbox.paypal.com/checkoutnow?token=7LK561281D3524115,此时会显示PayPal支付结果
  4. return "<script>window.onload=function(){setTimeout(function(){history.go(-1);},1000);}</script>支付成功,返回上一级页面";
  5. // 支付失败(返回一级后将返回到支付链接:https://www.sandbox.paypal.com/checkoutnow?token=7LK561281D3524115,此时会显示PayPal支付结果
  6. return "<script>window.onload=function(){setTimeout(function(){history.go(-1);},1000);}</script>支付失败,返回上一级页面";
  7. // 上一级:即跳转后“支付成功,返回上一级页面”页面/“支付失败,返回上一级页面”页面的上一级
  8. // 上几级就负几,-2、-3...

测试

(1)启动项目

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

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

  1. Payment payment = paypalService.createPayment(
  2. 500.00,
  3. "USD",
  4. PaypalPaymentMethod.paypal,
  5. PaypalPaymentIntent.sale,
  6. "payment description",
  7. cancelUrl,
  8. successUrl);

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

报错

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:

  1. @Configuration
  2. public class PaypalConfig {
  3. @Value("${paypal.client.app}")
  4. private String clientId;
  5. @Value("${paypal.client.secret}")
  6. private String clientSecret;
  7. @Value("${paypal.client.mode}")
  8. private String mode;
  9. @Bean
  10. public Map<String, String> paypalSdkConfig() {
  11. Map<String, String> sdkConfig = new HashMap<>();
  12. sdkConfig.put("mode", mode);
  13. return sdkConfig;
  14. }
  15. public OAuthTokenCredential authTokenCredential() {
  16. return new OAuthTokenCredential(clientId, clientSecret, paypalSdkConfig());
  17. }
  18. public APIContext apiContext() throws PayPalRESTException {
  19. APIContext apiContext = new APIContext(authTokenCredential().getAccessToken());
  20. apiContext.setConfigurationMap(paypalSdkConfig());
  21. return apiContext;
  22. }
  23. }

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

  1. @Service
  2. public class PaypalServiceImpl implements PaypalService {
  3. @Resource
  4. private PaypalConfig paypalConfig;
  5. @Resource
  6. private UserPayService userPayService;
  7. @Resource
  8. private UserOrderService userOrderService;
  9. @Override
  10. public Payment createPayment(PaypalOrderParam entity, String cancelUrl, String successUrl) throws PayPalRESTException {
  11. Amount amount = new Amount();
  12. amount.setCurrency("USD"); // 美金
  13. // total 是以分为单位,所以得除以100
  14. BigDecimal divisor = new BigDecimal("100");
  15. amount.setTotal(String.format("%.2f", entity.getTotalFee().divide(divisor)));
  16. Transaction transaction = new Transaction();
  17. transaction.setAmount(amount);
  18. transaction.setDescription(StringUtils.isBlank(entity.getContext())?"合恕支付充值":entity.getContext());
  19. List<Transaction> transactionList = new ArrayList<>();
  20. transactionList.add(transaction);
  21. Payer payer = new Payer();
  22. payer.setPaymentMethod("paypal"); // paypal购买
  23. Payment payment = new Payment();
  24. payment.setIntent("sale");// 直接购买
  25. payment.setPayer(payer);
  26. payment.setTransactions(transactionList);
  27. RedirectUrls redirectUrls = new RedirectUrls();
  28. redirectUrls.setCancelUrl(cancelUrl);
  29. redirectUrls.setReturnUrl(successUrl + "?userId="+entity.getUserId()+"&totalFee="+entity.getTotalFee()+"&payFrom="+ entity.getPayFrom());
  30. payment.setRedirectUrls(redirectUrls);
  31. // 每次使用apiContext时生成新的requestId
  32. APIContext apiContext = paypalConfig.apiContext();
  33. return payment.create(apiContext);
  34. }
  35. @Override
  36. public Payment executePayment(String paymentId, String payerId) throws PayPalRESTException {
  37. Payment payment = new Payment();
  38. payment.setId(paymentId);
  39. PaymentExecution paymentExecution = new PaymentExecution();
  40. paymentExecution.setPayerId(payerId);
  41. // 每次使用apiContext时生成新的requestId
  42. APIContext apiContext = paypalConfig.apiContext();
  43. return payment.execute(apiContext, paymentExecution);
  44. }
  45. }
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/508046
推荐阅读
相关标签
  

闽ICP备14008679号