PayPal支付介面開發java版
前言
經過n次debug和無數的查詢資料,終於摸清除了PayPal支付,請聽我一一道來
程式碼
PaymentController
@Controller
@RequestMapping("/")
public class PaymentController {
@Autowired
private APIContext apiContext;
@Autowired
private PaypalConfig paypalConfig;
@Autowired
private PaypalService paypalService;
private ConcurrentHashMap<String, String> orderPaymMap = new ConcurrentHashMap();// orderId與paymentId的對應關係
private Logger log = LoggerFactory.getLogger(getClass());
/**
* 首頁
*/
@RequestMapping(method = RequestMethod.GET)
public String index() {
return "index";
}
/**
* 建立訂單請求
* 建立成功返回payment物件,儲存本地OrderId和paymentId對應關係
*/
@RequestMapping(method = RequestMethod.POST, value = "pay")
public String pay(HttpServletRequest request) {
log.info("=========================================================================================");
String orderId = "2020110200001";// 本地系統Id
String cancelUrl = URLUtils.getBaseURl(request) + "/" + PaypalConfig.CANCEL_URL + "?orderId=" + orderId;// http://localhost:8080/pay/cancel
String successUrl = URLUtils.getBaseURl(request) + "/" + PaypalConfig.SUCCESS_URL;
try {
//呼叫交易方法
Payment payment = paypalService.createPayment(
orderId,
300.00,
"USD",
PaypalPaymentMethod.paypal,
PaypalPaymentIntent.authorize,
"這是一筆300美元的交易",
cancelUrl,
successUrl);
for (Links links : payment.getLinks()) {
if (links.getRel().equals("approval_url")) {
// 客戶付款登陸地址【判斷幣種CNY無法交易】
String paymentId = payment.getId();
orderPaymMap.put(orderId, paymentId);// 儲存本地OrderId和paymentId對應關係
log.info("建立支付訂單返回paymentId : " + paymentId);
log.info("支付訂單狀態state : " + payment.getState());
log.info("支付訂單建立時間create_time : " + payment.getCreateTime());
log.info("=========================================================================================");
return "redirect:" + links.getHref();
}
}
} catch (PayPalRESTException e) {
log.error(e.getMessage());// 支付失敗【使用CNY】
}
log.info("=========================================================================================");
return "redirect:/";
}
/**
* 失敗回撥
* 觸發回撥情景:
* 1、使用者在支付頁面點選取消支付
* 2、使用者支付成功後點選網頁回退,再點選返回商家按鈕觸發
* 判斷是否使用者主動取消付款邏輯:
* 1、設定回撥地址的時候拼接本地訂單ID:?orderId=XXXX
* 2、然後根據orderId查詢paymentId,繼而呼叫sdk查詢訂單支付狀態
* * http://localhost:8080/pay/cancel?orderId=2020110200001&token=EC-70674489LL9806126
*/
@RequestMapping(method = RequestMethod.GET, value = PaypalConfig.CANCEL_URL)
public String cancelPay(@RequestParam("token") String token, @RequestParam("orderId") String orderId) {
try {
String paymentId = orderPaymMap.get(orderId);
Payment payment = Payment.get(apiContext, paymentId);
String state = payment.getState();
log.info("交易取消回撥:支付訂單狀態:{} ", state);
if (state.equals("approved")) {// 已支付
return "success";
}
} catch (PayPalRESTException e) {
e.printStackTrace();
}
return "cancel";
}
/**
* 成功回撥 + 支付 + PDT同步通知
* 買家確認付款,執行支付並直接返回通知
*/
@RequestMapping(method = RequestMethod.GET, value = PaypalConfig.SUCCESS_URL)
public String successPay(@RequestParam("paymentId") String paymentId, @RequestParam("PayerID") String payerId, @RequestParam("token") String token) {
log.info("=========================================================================================");
try {
/**
* 執行支付
*/
Payment payment = paypalService.executePayment(paymentId, payerId);
if (payment.getState().equals("approved")) {
String id = ""; // 交易ID,transactionId
String state = ""; // 交易訂單狀態
String time = ""; // 交易時間
String custom = ""; // 本地OrderId
if (payment.getIntent().equals(PaypalPaymentIntent.authorize.toString())) {
id = payment.getTransactions().get(0).getRelatedResources().get(0).getAuthorization().getId();
state = payment.getTransactions().get(0).getRelatedResources().get(0).getAuthorization().getState();
time = payment.getTransactions().get(0).getRelatedResources().get(0).getAuthorization().getCreateTime();
custom = payment.getTransactions().get(0).getCustom();
} else if (payment.getIntent().equals(PaypalPaymentIntent.sale.toString())) {
id = payment.getTransactions().get(0).getRelatedResources().get(0).getSale().getId();
state = payment.getTransactions().get(0).getRelatedResources().get(0).getSale().getState();
time = payment.getTransactions().get(0).getRelatedResources().get(0).getSale().getCreateTime();
custom = payment.getTransactions().get(0).getCustom();
} else if (payment.getIntent().equals(PaypalPaymentIntent.order.toString())) {
id = payment.getTransactions().get(0).getRelatedResources().get(0).getOrder().getId();
state = payment.getTransactions().get(0).getRelatedResources().get(0).getOrder().getState();
time = payment.getTransactions().get(0).getRelatedResources().get(0).getOrder().getCreateTime();
custom = payment.getTransactions().get(0).getCustom();
}
log.info("PDT通知:交易成功回撥");
log.info("付款人賬戶:" + payment.getPayer().getPayerInfo().getEmail());
log.info("支付訂單Id {}", paymentId);
log.info("支付訂單狀態state : " + payment.getState());
log.info("交易訂單Id:{}", id);
log.info("交易訂單狀態state : " + state);
log.info("交易訂單支付時間:" + time);
log.info("本地系統OrderId:{}", custom);
log.info("=========================================================================================");
return "success";
}
} catch (PayPalRESTException e) {
// 如果同步通知返回異常,可根據paymentId 來查詢重新整理訂單狀態
// 同時IPN非同步通知也可以更新訂單狀態
log.error(e.getMessage());
}
return "redirect:/";
}
/**
* IPN非同步通知
* 觸發情景:
* 1、買家支付成功
* 2、賣家確認收取授權或訂單款項
* 3、賣家發放退款
*/
@RequestMapping(method = RequestMethod.POST, value = "/notificationIPN")
public void receivePaypalStatus(HttpServletRequest request, HttpServletResponse response) throws Exception {
log.info("=========================================================================================");
log.info("IPN通知:交易成功非同步回撥");
PrintWriter out = response.getWriter();
try {
Enumeration<String> en = request.getParameterNames();
/**
* 修改訂單狀態
* 儲存失敗則不驗籤,繼續接受paypal非同步回撥直至儲存成功【或者用MQ】
*/
String paymentStatus = request.getParameter("payment_status").toUpperCase(); // 交易狀態
String paymentDate = request.getParameter("payment_date"); // 交易時間
String custom = request.getParameter("custom"); // 本地系統訂單ID
String auth_id = request.getParameter("auth_id"); // transactionId
String txnId = request.getParameter("txn_id"); // 當前回撥資料id【具體邏輯檢視 .md文件】
String parentTxnId = request.getParameter("parent_txn_id"); // 父id
String receiverEmail = request.getParameter("receiver_email"); // 收款人email
String receiverId = request.getParameter("receiver_id"); // 收款人id
String payerEmail = request.getParameter("payer_email"); // 付款人email
String payerId = request.getParameter("payer_id"); // 付款人id
String mcGross = request.getParameter("mc_gross"); // 交易金額
String item_name = request.getParameter("item_name");
log.info("paymentStatus = " + paymentStatus);
log.info("txnId = " + txnId);
log.info("parentTxnId = " + parentTxnId);
log.info("authId(transactionId)= " + auth_id);
log.info("custom(orderId)= " + custom);
log.info("item_name= " + item_name);
/**
* 驗證
* 作用:
* 訂單狀態修改成功,告訴paypal停止回撥
* 實現:
* 在原引數的基礎上加cmd=_notify-validate,然後對https://www.sandbox.paypal.com/cgi-bin/webscr發起POST驗證請求
*/
String str = "cmd=_notify-validate";
while (en.hasMoreElements()) {
String paramName = en.nextElement();
String paramValue = request.getParameter(paramName);
//此處的編碼一定要和自己的網站編碼一致,不然會出現亂碼,paypal回覆的通知為"INVALID"
str = str + "&" + paramName + "=" + URLEncoder.encode(paramValue, "utf-8");
}
log.info("paypal傳遞過來的交易資訊:" + str);// 建議在此將接受到的資訊 str 記錄到日誌檔案中以確認是否收到 IPN 資訊
URL url = new URL(paypalConfig.getWebscr());
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setUseCaches(false);
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");//設定 HTTP 的頭資訊
PrintWriter pw = new PrintWriter(connection.getOutputStream());
pw.println(str);
pw.close();
/**
* 回覆
* 接受PayPal對驗證的回覆資訊
*/
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String resp = in.readLine();
in.close();
resp = StringUtils.isEmpty(resp) ? "0" : resp;
log.info("resp = " + resp);
/**
* 驗證返回狀態
*/
if (PaypalConfig.PAYMENT_IPN_VERIFIED.equalsIgnoreCase(resp)) {
/**
* 修改訂單狀態
* 根據訂單狀態paymentStatus確定當前回撥的型別
*/
switch (paymentStatus) {
case PaypalConfig.PAYMENT_STATUS_PENDING:
// 商家待領取狀態
break;
case PaypalConfig.PAYMENT_STATUS_VOIDED:
// 商家作廢(30天以內,且必須是授權付款型別 或 訂單付款型別),款項原路返回買家
break;
case PaypalConfig.PAYMENT_STATUS_COMPLETED:
// 商家領取
String captureId = txnId; // 實際領取物件ID【授權付款 和 訂單付款需要商家領取】
break;
case PaypalConfig.PAYMENT_STATUS_REFUNDED:
// 商家退款,需扣除費用
String refundId = txnId;
String captureId2 = parentTxnId;
break;
}
} else if (PaypalConfig.PAYMENT_IPN_INVALID.equalsIgnoreCase(resp)) {
// 非法資訊,可以將此記錄到您的日誌檔案中以備調查
log.error("IPN通知返回狀態非法,請聯絡管理員,請求引數:" + str);
log.error("Class: " + this.getClass().getName() + " method: " + Thread.currentThread().getStackTrace()[1].getMethodName());
out.println("confirmError");
} else {// 處理其他錯誤
log.error("IPN通知返回狀態非法,請聯絡管理員,請求引數:" + str);
log.error("Class: " + this.getClass().getName() + " method: " + Thread.currentThread().getStackTrace()[1].getMethodName());
out.println("confirmError");
}
} catch (Exception e) {
log.error("IPN通知發生IO異常" + e.getMessage());
log.error("Class: " + this.getClass().getName() + " method: " + Thread.currentThread().getStackTrace()[1].getMethodName());
out.println("confirmError");
e.printStackTrace();
}
out.flush();
out.close();
log.info("=========================================================================================");
}
/**
* 檢視已付款賬單的狀態
*/
@RequestMapping(method = RequestMethod.GET, value = "test")
@ResponseBody
public String selectTransactionState(@RequestParam("paymentId") String paymentId) {
log.info("=========================================================================================");
String state = "未產生支付資訊";
String custom = "";
try {
Payment payment = Payment.get(apiContext, paymentId);
if (payment.getTransactions().size() > 0 && payment.getTransactions().get(0).getRelatedResources().size() > 0) {
if (payment.getIntent().equals(PaypalPaymentIntent.sale.toString())) {// 交易訂單
state = payment.getTransactions().get(0).getRelatedResources().get(0).getSale().getState();
custom = payment.getTransactions().get(0).getCustom();
} else if (payment.getIntent().equals(PaypalPaymentIntent.authorize.toString())) {// 授權訂單
state = payment.getTransactions().get(0).getRelatedResources().get(0).getAuthorization().getState();
custom = payment.getTransactions().get(0).getCustom();
} else if (payment.getIntent().equals(PaypalPaymentIntent.order.toString())) {// 授權訂單
state = payment.getTransactions().get(0).getRelatedResources().get(0).getOrder().getState();
custom = payment.getTransactions().get(0).getCustom();
}
}
} catch (PayPalRESTException e) {
e.printStackTrace();
}
log.info("訂單狀態:{} ", state);
log.info(custom);
log.info("=========================================================================================");
return state;
}
}
PaypalService
/**
* 支付service類
*/
@Service
public class PaypalService {
// 注入憑證資訊bean
@Autowired
private APIContext apiContext;
/**
* 建立支付訂單
* @param total 交易金額
* @param currency 貨幣型別
* @param method 付款型別
* @param intent 收款方式
* @param description 交易描述
* @param cancelUrl 取消後回撥地址
* @param successUrl 成功後回撥地址
*/
public Payment createPayment(
String orderId,
Double total,
String currency,
PaypalPaymentMethod method,
PaypalPaymentIntent intent,
String description,
String cancelUrl,
String successUrl) throws PayPalRESTException {
// 設定金額和單位物件
Amount amount = new Amount();
amount.setCurrency(currency);
amount.setTotal(String.format("%.2f", total));
// 設定具體的交易物件
Transaction transaction = new Transaction();
transaction.setDescription(description);
transaction.setAmount(amount);
transaction.setCustom(orderId);
// 交易集合-可以新增多個交易物件
List<Transaction> 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);
// 設定反饋url
RedirectUrls redirectUrls = new RedirectUrls();
redirectUrls.setCancelUrl(cancelUrl);
redirectUrls.setReturnUrl(successUrl);
// 加入反饋物件
payment.setRedirectUrls(redirectUrls);
// 加入認證並建立交易
return payment.create(apiContext);
}
/**
* 執行支付
* 獲取支付訂單,和買家ID,執行支付
*/
public Payment executePayment(String paymentId, String payerId) throws PayPalRESTException{
Payment payment = new Payment();
payment.setId(paymentId);
PaymentExecution paymentExecute = new PaymentExecution();
paymentExecute.setPayerId(payerId);
return payment.execute(apiContext, paymentExecute);
}
}
URLUtils
/**
* 獲取請求url的util
*/
public class URLUtils {
public static String getBaseURl(HttpServletRequest request) {
String scheme = request.getScheme();
String serverName = request.getServerName();
int serverPort = request.getServerPort();
String contextPath = request.getContextPath();
StringBuffer url = new StringBuffer();
url.append(scheme).append("://").append(serverName);
if ((serverPort != 80) && (serverPort != 443)) {
url.append(":").append(serverPort);
}
url.append(contextPath);
if(url.toString().endsWith("/")){
url.append("/");
}
return url.toString();
}
}
PaypalConfig
/**
* 配置類,注入PayPal需要的認證資訊
*/
@Configuration
@Data
public class PaypalConfig {
@Value("${paypal.client.app}")
private String clientId;
@Value("${paypal.client.secret}")
private String clientSecret;
@Value("${paypal.mode}")
private String mode;
@Value("${paypal.webscr}")
private String webscr;
/**
* 建立支付回撥地址引數
*/
public static final String SUCCESS_URL = "pay/success"; // 成功回撥地址PDT
public static final String CANCEL_URL = "pay/cancel"; // 取消回撥地址PDT
/**
* IPN非同步驗證返回
*/
public static final String PAYMENT_IPN_VERIFIED = "VERIFIED";
public static final String PAYMENT_IPN_COMPLETED_STATUS = "COMPLETED_STATUS";
public static final String PAYMENT_IPN_REFUNDED_STATUS = "REFUNDED_STATUS";
public static final String PAYMENT_IPN_INVALID = "INVALID";
/**
* IPN非同步通知返回通知訊息型別
*/
public static final String PAYMENT_STATUS_PENDING = "PENDING";
public static final String PAYMENT_STATUS_VOIDED = "VOIDED ";
public static final String PAYMENT_STATUS_COMPLETED = "COMPLETED ";
public static final String PAYMENT_STATUS_REFUNDED = "REFUNDED ";
/**
* APP的認證資訊
* clientId、Secret,開發者賬號建立APP時提供
*/
@Bean
public APIContext apiContext() throws PayPalRESTException {
APIContext apiContext = new APIContext(clientId, clientSecret, mode);
return apiContext;
}
}
PaypalPaymentIntent
下面展示一些 內聯程式碼片
。
/**
* 付款型別
* sale:直接付款
* authorize:授權付款
* order:訂單付款
*/
public enum PaypalPaymentIntent {
sale,
authorize,
order
}
PaypalPaymentMethod
/**
* 收款方式
* credit_card:信用卡收款
* paypal:餘額收款
*/
public enum PaypalPaymentMethod {
credit_card,
paypal
}
application.properties
server.port=8080
spring.thymeleaf.cache=false
paypal.mode=sandbox
paypal.client.app=請填寫你的應用
paypal.client.secret=請填寫你的金鑰
paypal.webscr=https://www.sandbox.paypal.com/cgi-bin/webscr
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.wan</groupId>
<artifactId>paypal</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>paypal</name>
<description>paypal</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- paypal 開發時需要的jar包 -->
<dependency>
<groupId>com.paypal.sdk</groupId>
<artifactId>rest-api-sdk</artifactId>
<version>1.14.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
相關文章
- 支付寶介面呼叫 -- JAVA版Java
- PayPal支付-Reaact框架框架
- 支付寶介面開發
- ASP做paypal支付介面詳細程式碼和例項
- PHP 對接 paypal 支付PHP
- PayPal支付對接phpPHP
- 微信支付介面開發過程
- Laravel 進行 paypal 支付 demoLaravel
- JavaEE PayPal 全球支付快速整合Java
- vue2接入paypal支付Vue
- 微信公眾號支付開發全過程(Java 版)Java
- 支付寶線上支付介面開發教程與總結
- 線上支付介面申請和開發(一)
- PayPal支付整合到自己Web網站Web網站
- php微信支付介面開發的實現程式PHP
- 支付寶介面對接開發過程
- [後端開發]支付寶支付介面除錯 (Python v3.6)後端除錯Python
- JAVA版微信支付V3—JSAPI支付JavaJSAPI
- 微信開發之JSSDK介面開發(Java)JSJava
- Facebook測試新支付工具 將挑戰PayPal
- GOOGLE線上支付Checkout落後於對手PayPalGo
- 微信開發超市全反系統,微信支付刷卡支付,微信介面簡單配置!
- java實現zabbix介面開發Java
- 銀聯線上Java介面開發Java
- 接入 paypal PHP-sdk 支付 / 回撥 / 退款全流程PHP
- PayPal尋求更快的加密貨幣支付技術加密
- C# 專案中【支付寶】介面開發整合案例C#
- 微信支付開發
- Java 版抖音解析介面Java
- PHP支付寶支付開發流程PHP
- 支付寶線上支付介面
- 美國支付寶Paypal將支援更多加密貨幣加密
- Jaeger開發入門(java版)Java
- PHP微信支付開發PHP
- 微信開發 微信支付
- Android 支付寶支付開發流程Android
- Django呼叫支付寶支付介面Django
- 那些年我們賺過的外快(POS(移動支付)介面開發)