一、注册账号
1.注册PayPal账号,注册地址:安全海淘国际支付平台_安全收款外贸平台-PayPal CN
2.注册完以后登录开发者平台:5https://developer.paypal.com/,输入刚注册的账户密码。
3.创建沙箱应用,这里记录一下Client ID、Secret 后面配置文件会用到
4.沙箱测试账号
点击沙箱地址会显示详细的沙箱账户信息以及沙箱环境登录地址:安全海淘国际支付平台_安全收款外贸平台-PayPal CN
二、java代码
1.application.yml配置文件
pay:
paypal:
clientId: ***************************
clientSecret: ***********************
#测试的mode
mode: sandbox
#正式的mode
# mode: live
#取消订单地址 外网能访问
cancelUrl: http://79ecf61.r6.cpolar.top/payPal/cancel
#支付成功地址 外网能访问
successUrl: http://79ecf61.r6.cpolar.top/payPal/success
本地调试可以使用内网穿透,具体教程可以看我之前的文章有具体怎么操作:springboot集成微信支付V3 SDK_springboot接入微信支付注入不了微信支付的sdk包-CSDN博客
2.pom依赖
<!--PayPal 支付-->
<dependency>
<groupId>com.paypal.sdk</groupId>
<artifactId>rest-api-sdk</artifactId>
<version>1.4.2</version>
</dependency>
<dependency>
<groupId>com.paypal.sdk</groupId>
<artifactId>checkout-sdk</artifactId>
<version>1.0.2</version>
</dependency>
3.PayPalConfig配置类
package com.qz.client.modular.pay.config;
import com.paypal.base.rest.APIContext;
import com.paypal.base.rest.OAuthTokenCredential;
import com.paypal.base.rest.PayPalRESTException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* @author lihao
* @create 2024-05-30 10:40
* @desc payp支付配置类
**/
@Configuration
public class PayPalConfig {
@Value("${pay.paypal.clientId}")
private String clientId;
@Value("${pay.paypal.clientSecret}")
private String clientSecret;
@Value("${pay.paypal.mode}")
private String mode;
@Bean
public Map<String, String> paypalSdkConfig(){
Map<String, String> 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;
}
}
4.状态枚举类
package com.qz.client.modular.pay.enums;
import lombok.Getter;
/**
* @author lihao
* @create 2024-05-30 10:41
* @desc 支付的意图或目的
**/
@Getter
public enum PayPalPaymentIntent {
/**
* sale:表示该支付是一次性的销售交易,即直接从付款方账户扣款到收款方账户。
* authorize:表示该支付是一个预授权,即授权收款方在未来某个时间点从付款方账户中扣款。
* order:表示该支付是创建一个订单,但并不立即扣款。在付款方确认订单后,可以选择扣款操作。
* subscription:表示该支付是用于定期订阅付款的意图,通常用于定期收取付款方的费用。
**/
SALE("sale"), AUTHORIZE("authorize"), ORDER("order"),SUBSCRIPTION("subscription");
private final String value;
PayPalPaymentIntent(String key) {
this.value = key;
}
}
package com.qz.client.modular.pay.enums;
import lombok.Getter;
/**
* @author lihao
* @create 2024-05-30 10:42
* @desc 支付所使用的支付方式
**/
@Getter
public enum PayPalPaymentMethod {
/**
* credit_card:信用卡支付。
* paypal:通过 PayPal 账户余额或链接的银行账户进行支付。
* paypal_credit:通过 PayPal Credit 进行支付,这是 PayPal 提供的一种信用融资服务。
* pay_upon_invoice:表示支付将在发票上进行,通常用于线下支付或后付款方式。
**/
CREDITCARD("credit_card"), PAYPAL("paypal"),PAYPALCREDIT("paypal_credit"),PAYUPONINVOICE("pay_upon_invoice");
private final String value;
PayPalPaymentMethod(String key) {
this.value = key;
}
}
5.请求参数类
package com.qz.client.modular.pay.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
/**
* @author:denghua
* @create: 2023-01-09 14:39
* @Description: 支付传参
*/
@Getter
@Setter
public class PayParam {
@ApiModelProperty(value = "订单介绍")
private String description;
@ApiModelProperty(value = "订单id")
@NotBlank(message = "订单id 不能为空")
private String orderId;
}
package com.qz.client.modular.pay.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @author lihao
* @create 2024-05-31 9:59
* @desc 贝宝退款参数
**/
@Data
public class PayPalRefundParam {
@ApiModelProperty(value = "交易订单Id",required = true)
private String saleId;
@ApiModelProperty(value = "退款金额",required = true)
private Double money;
@ApiModelProperty(value = "货币类型",required = true)
private String currency;
@ApiModelProperty(value = "退款原因",required = true)
private String reason;
}
6.controller
package com.qz.client.modular.pay.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.alibaba.fastjson.JSONObject;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import com.paypal.api.payments.Links;
import com.paypal.api.payments.Payment;
import com.paypal.api.payments.Refund;
import com.paypal.base.rest.PayPalRESTException;
import com.qz.api.PlatOrderApi;
import com.qz.auth.core.annotation.SaPlatCheckLogin;
import com.qz.client.modular.pay.enums.PayPalPaymentIntent;
import com.qz.client.modular.pay.enums.PayPalPaymentMethod;
import com.qz.client.modular.pay.param.PayPalRefundParam;
import com.qz.client.modular.pay.param.WeChatPayParam;
import com.qz.client.modular.pay.service.PayPalService;
import com.qz.client.modular.pay.util.PayToolUtil;
import com.qz.common.exception.CommonException;
import com.qz.common.pojo.CommonResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.math.BigDecimal;
/**
* @author lihao
* @create 2024-05-29 16:14
* @desc paypal支付
**/
@Api(tags = "贝宝支付")
@ApiSupport(author = "lihao")
@Slf4j
@RestController
@RequestMapping(value = "/payPal")
public class PaypalController {
@Value("${${pay.paypal.cancelUrl}}")
protected String cancelUrl;
@Value("${pay.paypal.successUrl}")
protected String successUrl;
@Autowired
private PayPalService payPalService ;
/**
* 生成订单
*/
@ApiOperation("生成订单")
@PostMapping("/pay")
public CommonResult<String> paypal(@RequestBody WeChatPayParam payParam) {
try {
BigDecimal money = ;//获取订单金额业务方法
if(money ==null){
return CommonResult.error("获取订单支付金额失败!");
}
String strRandom = String.valueOf(this.buildRandom(4));
//注意生成订单号唯一
Payment payment = payPalService.createPayment(payParam.getOrderId()+strRandom,money.doubleValue(), "USD", PayPalPaymentMethod.PAYPAL, PayPalPaymentIntent.SALE,payParam.getDescription(), cancelUrl, successUrl);
for (Links links : payment.getLinks()) {
if (links.getRel().equals("approval_url")) {
log.info("links.getHref() is {}", links.getHref());
log.info("支付订单返回paymentId:" + payment.getId());
log.info("支付订单状态state:" + payment.getState());
log.info("支付订单创建时间:" + payment.getCreateTime());
String href = links.getHref();
return CommonResult.data(href);
}
}
} catch (PayPalRESTException e) {
log.error(e.getMessage());
return new CommonResult<>(e.getResponsecode(), e.getMessage(), null);
}
return CommonResult.error("生成订单错误!");
}
@ApiOperation("退款申请测试")
@SaCheckPermission("/payPal/refund")
@PostMapping("/refund")
public CommonResult<String> refund(@RequestBody @Valid PayPalRefundParam refundParam) throws PayPalRESTException {
Refund refund = payPalService.refund(refundParam.getSaleId(),refundParam.getMoney(),refundParam.getCurrency(),refundParam.getReason());
log.info("========>退款返回结果:{}", JSONObject.toJSONString(refund));
return CommonResult.data("");
}
/**
* 取消支付
*/
@ApiOperation("取消支付")
@GetMapping("/cancel")
public CommonResult<String> cancelPay(@RequestParam("orderId") String orderId){
log.info("==========>用户取消支付,订单号:{}",orderId);
return CommonResult.data("用户取消支付");
}
/**
* 支付操作
*/
@ApiOperation("支付操作")
@GetMapping("/success")
public String successPay(@RequestParam("paymentId") String paymentId, @RequestParam("PayerID") String payerId, @RequestParam("orderId") String orderId) { // 一定是PayerID,PayPal通常使用"PayerID"(ID和P都大小写)作为参数名称
try {
//支付成功执行PayPal扣款
Payment payment = payPalService.executePayment(paymentId, payerId);
// 支付成功
if(payment.getState().equals("approved")){
// 订单号
String saleId = payment.getTransactions().get(0).getRelatedResources().get(0).getSale().getId();
String money = payment.getTransactions().get(0).getRelatedResources().get(0).getSale().getAmount().getTotal();
//执行自己支付成功的业务方法
log.info("PDT通知:交易成功回调");
log.info("付款人账户:"+payment.getPayer().getPayerInfo().getEmail());
log.info("付款金额:"+money);
log.info("支付订单Id: {}",paymentId);
log.info("支付订单状态state:" + payment.getState());
log.info("交易订单Id: {}",saleId);
log.info("交易订单状态state:"+payment.getTransactions().get(0).getRelatedResources().get(0).getSale().getState());
log.info("交易订单支付时间:"+payment.getTransactions().get(0).getRelatedResources().get(0).getSale().getCreateTime());
return "<script>window.onload=function(){setTimeout(function(){history.go(-1);},1000);}</script>支付成功,返回上一级页面";
}
} catch (PayPalRESTException e) {
log.info(e.getMessage());
throw new RuntimeException(e.getMessage());
}
return "<script>window.onload=function(){setTimeout(function(){history.go(-1);},1000);}</script>支付失败,返回上一级页面";
}
/**
* 取出一个指定长度大小的随机正整数
* @param length int 设定所取出随机数的长度。length小于11
* @return int 返回生成的随机数。
*/
public int buildRandom(int length) {
int num = 1;
double random = Math.random();
if (random < 0.1) {
random = random + 0.1;
}
for (int i = 0; i < length; i++) {
num = num * 10;
}
return (int) ((random * num));
}
}
7.service
package com.qz.client.modular.pay.service;
import com.paypal.api.payments.Payment;
import com.paypal.api.payments.Refund;
import com.paypal.base.rest.PayPalRESTException;
import com.qz.client.modular.pay.enums.PayPalPaymentIntent;
import com.qz.client.modular.pay.enums.PayPalPaymentMethod;
/**
* @author lihao
* @create 2024-05-30 10:47
* @desc
**/
public interface PayPalService {
Payment createPayment(String orderId,Double total, String currency, PayPalPaymentMethod method, PayPalPaymentIntent intent, String description, String cancelUrl, String successUrl) throws PayPalRESTException;
/**
* 执行扣款
* @author lihao
* @date 2024/5/31 9:56
* @param paymentId 支付订单Id
* @param payerId
* @return com.paypal.api.payments.Payment
**/
Payment executePayment(String paymentId, String payerId) throws PayPalRESTException;
/**
*
* @author lihao
* @date 2024/5/31 9:55
* @param saleId 交易订单Id
* @param money 退款金额
* @param currency 货币类型
* @param reason 退款原因
* @return com.paypal.api.payments.Refund
**/
Refund refund(String saleId, Double money, String currency, String reason) throws PayPalRESTException;
}
package com.qz.client.modular.pay.service.Impl;
import com.paypal.api.payments.*;
import com.paypal.base.rest.APIContext;
import com.paypal.base.rest.PayPalRESTException;
import com.qz.api.PlatOrderApi;
import com.qz.client.modular.pay.enums.PayPalPaymentIntent;
import com.qz.client.modular.pay.enums.PayPalPaymentMethod;
import com.qz.client.modular.pay.service.PayPalService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.*;
/**
* @author lihao
* @create 2024-05-30 10:51
* @desc
**/
@Service
public class PayPalServiceImpl implements PayPalService {
@Autowired
private APIContext apiContext;
// 创建支付
@Override
public Payment createPayment(String orderId,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 对象
String requestId = generateRequestId(); // 生成唯一的 PayPal-Request-Id 值
// 设置 PayPal-Request-Id 头部
Map<String, String> headers = new HashMap<>();
headers.put("PayPal-Request-Id", requestId);
apiContext.setHTTPHeaders(headers);
Amount amount = new Amount();
amount.setCurrency(currency);
amount.setTotal(String.format("%.2f", total));
Transaction transaction = new Transaction();
transaction.setDescription(description);
transaction.setAmount(amount);
List<Transaction> transactions = new ArrayList<>();
transactions.add(transaction);
Payer payer = new Payer();
payer.setPaymentMethod(method.getValue());
Payment payment = new Payment();
payment.setIntent(intent.getValue());
payment.setPayer(payer);
payment.setTransactions(transactions);
RedirectUrls redirectUrls = new RedirectUrls();
// Paypal取消支付回调链接
redirectUrls.setCancelUrl(cancelUrl+"?orderId=" + orderId);
// Paypal付完款回调链接
// redirectUrls.setReturnUrl(successUrl);
// Paypal付完款回调链接:如果要其他数据作为参数传递给成功支付后的回调URL即控制器类中的successPay方法,则对回调URL进行拼接:redirectUrls.setReturnUrl(successUrl + "?param1=" + param1 + "¶m2=" + param2 + "¶m3=" + paaram3);
redirectUrls.setReturnUrl(successUrl + "?orderId=" + orderId);
payment.setRedirectUrls(redirectUrls);
return payment.create(apiContext);
}
// 执行支付
@Override
public Payment executePayment(String paymentId, String payerId) throws PayPalRESTException {
String requestId = generateRequestId(); // 生成唯一的 PayPal-Request-Id 值
// 设置 PayPal-Request-Id 头部
Map<String, String> headers = new HashMap<>();
headers.put("PayPal-Request-Id", requestId);
apiContext.setHTTPHeaders(headers);
// 接受支付 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);
}
@Override
public Refund refund(String saleId,Double money, String currency,String reason) throws PayPalRESTException {
String requestId = generateRequestId(); // 生成唯一的 PayPal-Request-Id 值
// 设置 PayPal-Request-Id 头部
Map<String, String> headers = new HashMap<>();
headers.put("PayPal-Request-Id", requestId);
apiContext.setHTTPHeaders(headers);
Refund refund = new Refund();
Amount amount = new Amount();
amount.setCurrency(currency);
amount.setTotal(String.valueOf(money));
refund.setAmount(amount);
refund.setReason(reason);
Sale sale = new Sale();
sale.setId(saleId);
Refund refund1 = sale.refund(apiContext,refund);
return refund1;
}
// 生成唯一的 PayPal-Request-Id 值
private String generateRequestId() {
return UUID.randomUUID().toString();
}
}