paypal支付的主要流程:
1:創建支付訂單接口,接口會返回一個跳轉url,這是paypal支付頁面。如果沒登錄,則是登錄頁面,登錄成功后是支付界面。這一步只是用戶授權支付,然后錢還沒到商家賬號,需要調用第二接口,執行支付

2:支付執行接口,相當于把錢打到商家賬號。
3:分別可以登錄買家和賣家賬號可以看到收款信息
賣家:

買家:

1、首先打開地址:https://developer.paypal.com/,自己注冊一個賬號,登錄后頁面如下:
(1)按照上圖所示,在沙箱里面創建兩個帳號,一個商家,一個買家,參數自己設置,使用默認的帳號也可以。

(2) 接著按照下圖所示,創建一個沙箱app。

點擊該app,即可獲取到Client ID和Secret,如下圖所示:

到這里準備工作就完成了,下面開始Java和PayPal對接,完成一個完整的付款流程。
PayPal有v1、v2兩個版本的SDK,
首先當然是創建一個springboot項目,然后引入PayPal的依賴:
v1:rest-api-sdk
<dependency>
<groupId>com.paypal.sdk</groupId>
<artifactId>rest-api-sdk</artifactId>
<version>LATEST</version>
</dependency>
v2:checkout-sdk
<dependency>
<groupId>com.paypal.sdk</groupId>
<artifactId>checkout-sdk</artifactId>
<version>1.0.2</version>
</dependency>
(3)目前這里介紹v2版本
第一:maven依賴
<dependency>
<groupId>com.paypal.sdk</groupId>
<artifactId>checkout-sdk</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20180813</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.6</version>
</dependency>
第二:編寫代碼
1. PayPalClient(請求PayPal api的工具類)
package com.ratta.paypal.info; import com.paypal.core.PayPalEnvironment; import com.paypal.core.PayPalHttpClient; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.json.JSONArray; import org.json.JSONObject; import java.util.Iterator; @Slf4j public class PayPalClient { public PayPalHttpClient client(String mode, String clientId, String clientSecret) { log.info("mode={}, clientId={}, clientSecret={}", mode, clientId, clientSecret); PayPalEnvironment environment = mode.equals("live") ? new PayPalEnvironment.Live(clientId, clientSecret) : new PayPalEnvironment.Sandbox(clientId, clientSecret); return new PayPalHttpClient(environment); } /** * @param jo * @param pre * @return */ public String prettyPrint(JSONObject jo, String pre) { Iterator<?> keys = jo.keys(); StringBuilder pretty = new StringBuilder(); while (keys.hasNext()) { String key = (String) keys.next(); pretty.append(String.format("%s%s: ", pre, StringUtils.capitalize(key))); if (jo.get(key) instanceof JSONObject) { pretty.append(prettyPrint(jo.getJSONObject(key), pre + "\t")); } else if (jo.get(key) instanceof JSONArray) { int sno = 1; for (Object jsonObject : jo.getJSONArray(key)) { pretty.append(String.format("\n%s\t%d:\n", pre, sno++)); pretty.append(prettyPrint((JSONObject) jsonObject, pre + "\t\t")); } } else { pretty.append(String.format("%s\n", jo.getString(key))); } } return pretty.toString(); } }
2. CreateOrder (創建訂單)
package com.ratta.paypal.info; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.json.JSONObject; import com.paypal.http.HttpResponse; import com.paypal.http.serializer.Json; import com.paypal.orders.AddressPortable; import com.paypal.orders.AmountBreakdown; import com.paypal.orders.AmountWithBreakdown; import com.paypal.orders.ApplicationContext; import com.paypal.orders.Item; import com.paypal.orders.LinkDescription; import com.paypal.orders.Money; import com.paypal.orders.Name; import com.paypal.orders.Order; import com.paypal.orders.OrderRequest; import com.paypal.orders.OrdersCreateRequest; import com.paypal.orders.PurchaseUnitRequest; import com.paypal.orders.ShippingDetail; import lombok.extern.slf4j.Slf4j; @Slf4j public class CreateOrder { private String clientId = "AdTJ2en6Av42r1xoziJj6bJK-X4tRGDHACZId0OPXfGyXs8OyoYEmlm8bHjzrgd3UislDQR0iBP7x-wM"; private String clientSecret = "EC9DSrfAfVfo-c3K-1dILXA5iijnHtaunKwv2JzECSz9jcdy3t78rDFeAEgaixnnIYYlgQcipbZQWoCa"; private String mode = "sandbox"; public static final String CAPTURE = "CAPTURE"; /** * 該標簽將覆蓋PayPal網站上PayPal帳戶中的公司名稱 */ public static final String BRANDNAME = "Supernote"; /** * LOGIN。當客戶單擊PayPal Checkout時,客戶將被重定向到頁面以登錄PayPal并批準付款。 * BILLING。當客戶單擊PayPal Checkout時,客戶將被重定向到一個頁面,以輸入信用卡或借記卡以及完成購買所需的其他相關賬單信息 * NO_PREFERENCE。當客戶單擊“ PayPal Checkout”時,將根據其先前的交互方式將其重定向到頁面以登錄PayPal并批準付款,或重定向至頁面以輸入信用卡或借記卡以及完成購買所需的其他相關賬單信息使用PayPal。 * 默認值:NO_PREFERENCE */ public static final String LANDINGPAGE = "NO_PREFERENCE"; /** * CONTINUE。將客戶重定向到PayPal付款頁面后,將出現“ 繼續”按鈕。當結帳流程啟動時最終金額未知時,請使用此選項,并且您想將客戶重定向到商家頁面而不處理付款。 * PAY_NOW。將客戶重定向到PayPal付款頁面后,出現“ 立即付款”按鈕。當啟動結帳時知道最終金額并且您要在客戶單擊“ 立即付款”時立即處理付款時,請使用此選項。 */ public static final String USERACTION = "CONTINUE"; /** * GET_FROM_FILE。使用貝寶網站上客戶提供的送貨地址。 * NO_SHIPPING。從PayPal網站編輯送貨地址。推薦用于數字商品 * SET_PROVIDED_ADDRESS。使用商家提供的地址??蛻魺o法在PayPal網站上更改此地址 */ public static final String SHIPPINGPREFERENCE = "SET_PROVIDED_ADDRESS"; /** * 生成訂單主體信息 */ private OrderRequest buildRequestBody() { OrderRequest orderRequest = new OrderRequest(); orderRequest.checkoutPaymentIntent(CAPTURE); ApplicationContext applicationContext = new ApplicationContext() .brandName(BRANDNAME) .landingPage(LANDINGPAGE) .cancelUrl("https://www.example.com").returnUrl("https://www.example.com") .userAction(USERACTION) .shippingPreference(SHIPPINGPREFERENCE); orderRequest.applicationContext(applicationContext); List<PurchaseUnitRequest> purchaseUnitRequests = new ArrayList<PurchaseUnitRequest>(); @SuppressWarnings("serial") PurchaseUnitRequest purchaseUnitRequest = new PurchaseUnitRequest() .description("新一代讀寫一體,智能電子筆記本") .customId("P2020052514440001") .invoiceId("P2020052514440001") .amountWithBreakdown(new AmountWithBreakdown() .currencyCode("USD") .value("220.00")// value = itemTotal + shipping + handling + taxTotal + shippingDiscount; .amountBreakdown(new AmountBreakdown() .itemTotal(new Money().currencyCode("USD").value("220.00")) // itemTotal = Item[Supernote A6](value × quantity) + Item[帆布封套](value × quantity) .shipping(new Money().currencyCode("USD").value("0.00")) .handling(new Money().currencyCode("USD").value("0.00")) .taxTotal(new Money().currencyCode("USD").value("0.00")) .shippingDiscount(new Money().currencyCode("USD").value("0.00")))) .items(new ArrayList<Item>() { { add(new Item().name("Supernote A6").description("絲滑般流暢的書寫體驗") .unitAmount(new Money() .currencyCode("USD") .value("200.00")) .quantity("1")); add(new Item().name("帆布封套").description("黑色帆布保護封套") .unitAmount(new Money() .currencyCode("USD") .value("20.00")) .quantity("1")); } }) .shippingDetail(new ShippingDetail() .name(new Name().fullName("RATTA")) .addressPortable(new AddressPortable() .addressLine1("梅隴鎮") .addressLine2("集心路168號") .adminArea2("閔行區") .adminArea1("上海市") .postalCode("20000") .countryCode("CN"))); purchaseUnitRequests.add(purchaseUnitRequest); orderRequest.purchaseUnits(purchaseUnitRequests); return orderRequest; } /** * 創建訂單的方法 * @throws 收銀臺地址 */ public String createOrder() throws IOException { OrdersCreateRequest request = new OrdersCreateRequest(); request.header("prefer","return=representation"); request.requestBody(buildRequestBody()); PayPalClient payPalClient = new PayPalClient(); HttpResponse<Order> response = null; try { response = payPalClient.client(mode, clientId, clientSecret).execute(request); } catch (IOException e1) { try { log.error("第1次調用paypal訂單創建失敗"); response = payPalClient.client(mode, clientId, clientSecret).execute(request); } catch (Exception e) { try { log.error("第2次調用paypal訂單創建失敗"); response = payPalClient.client(mode, clientId, clientSecret).execute(request); } catch (Exception e2) { log.error("第3次調用paypal訂單創建失敗,失敗原因:{}", e2.getMessage()); } } } String approve = ""; if (response.statusCode() == 201) { log.info("Status Code = {}, Status = {}, OrderID = {}, Intent = {}", response.statusCode(), response.result().status(), response.result().id(), response.result().checkoutPaymentIntent()); for (LinkDescription link : response.result().links()) { log.info("Links-{}: {} \tCall Type: {}", link.rel(), link.href(), link.method()); if(link.rel().equals("approve")) { approve = link.href(); } } String totalAmount = response.result().purchaseUnits().get(0).amountWithBreakdown().currencyCode() + ":" + response.result().purchaseUnits().get(0).amountWithBreakdown().value(); log.info("Total Amount: {}", totalAmount); String json= new JSONObject(new Json().serialize(response.result())).toString(4); log.info("createOrder response body: {}", json); } return approve; } public static void main(String args[]) { try { String approveUrl = new CreateOrder().createOrder(); System.out.println("approveUrl = "+ approveUrl); } catch (com.paypal.http.exceptions.HttpException e) { System.out.println(e.getLocalizedMessage()); } catch (Exception e) { e.printStackTrace(); } } }
3. CaptureOrder(執行扣款)
用戶通過CreateOrder生成 approveUrl 跳轉paypal支付成功后,只是授權,并沒有將用戶的錢打入我們的paypal賬戶,我們需要通過 CaptureOrder接口,將錢打入我的PayPal賬戶
package com.ratta.paypal.info; import java.io.IOException; import com.paypal.orders.*; import lombok.extern.slf4j.Slf4j; import org.json.JSONObject; import com.paypal.http.HttpResponse; import com.paypal.http.serializer.Json; @Slf4j public class CaptureOrder extends PayPalClient { private String clientId = "AdTJ2en6Av42r1xoziJj6bJK-X4tRGDHACZId0OPXfGyXs8OyoYEmlm8bHjzrgd3UislDQR0iBP7x-wM"; private String clientSecret = "EC9DSrfAfVfo-c3K-1dILXA5iijnHtaunKwv2JzECSz9jcdy3t78rDFeAEgaixnnIYYlgQcipbZQWoCa"; private String mode = "sandbox"; public OrderRequest buildRequestBody() { return new OrderRequest(); } /** * 用戶授權支付成功,進行扣款操作 */ public HttpResponse<Order> captureOrder(String orderId) throws IOException { OrdersCaptureRequest request = new OrdersCaptureRequest(orderId); request.requestBody(new OrderRequest()); PayPalClient payPalClient = new PayPalClient(); HttpResponse<Order> response = null; try { response = payPalClient.client(mode, clientId, clientSecret).execute(request); } catch (IOException e1) { try { log.error("第1次調用paypal扣款失敗"); response = payPalClient.client(mode, clientId, clientSecret).execute(request); } catch (Exception e) { try { log.error("第2次調用paypal扣款失敗"); response = payPalClient.client(mode, clientId, clientSecret).execute(request); } catch (Exception e2) { log.error("第3次調用paypal扣款失敗,失敗原因 {}", e2.getMessage() ); } } } log.info("Status Code = {}, Status = {}, OrderID = {}", response.statusCode(), response.result().status(), response.result().id()); for (LinkDescription link : response.result().links()) { log.info("Links-{}: {} \tCall Type: {}", link.rel(), link.href(), link.method()); } for (PurchaseUnit purchaseUnit : response.result().purchaseUnits()) { for (Capture capture : purchaseUnit.payments().captures()) { log.info("Capture id: {}", capture.id()); log.info("status: {}", capture.status()); log.info("invoice_id: {}", capture.invoiceId()); if("COMPLETED".equals(capture.status())) { //進行數據庫操作,修改訂單狀態為已支付成功,盡快發貨(配合回調和CapturesGet查詢確定成功) log.info("支付成功,狀態為=COMPLETED"); } if("PENDING".equals(capture.status())) { log.info("status_details: {}", capture.captureStatusDetails().reason()); String reason = "PENDING"; if(capture.captureStatusDetails() != null && capture.captureStatusDetails().reason() != null) { reason = capture.captureStatusDetails().reason(); } //進行數據庫操作,修改訂單狀態為已支付成功,但觸發了人工審核,請審核通過后再發貨(配合回調和CapturesGet查詢確定成功) log.info("支付成功,狀態為=PENDING : {}", reason); } } } Payer buyer = response.result().payer(); log.info("Buyer Email Address: {}", buyer.email()); log.info("Buyer Name: {} {}", buyer.name().givenName(), buyer.name().surname()); String json = new JSONObject(new Json().serialize(response.result())).toString(4); log.info("captureOrder response body: {}", json); return response; } public static void main(String[] args) { try { new CaptureOrder().captureOrder("訂單id,CreateOrder 生成"); } catch (Exception e) { e.printStackTrace(); } } }
4. RefundOrder(申請退款)
package com.ratta.paypal.info; import java.io.IOException; import org.json.JSONObject; import com.paypal.http.HttpResponse; import com.paypal.http.serializer.Json; import com.paypal.orders.OrdersGetRequest; import com.paypal.payments.CapturesRefundRequest; import com.paypal.payments.Money; import com.paypal.payments.Refund; import com.paypal.payments.RefundRequest; import lombok.extern.slf4j.Slf4j; @Slf4j public class RefundOrder extends PayPalClient { private String clientId = "AdTJ2en6Av42r1xoziJj6bJK-X4tRGDHACZId0OPXfGyXs8OyoYEmlm8bHjzrgd3UislDQR0iBP7x-wM"; private String clientSecret = "EC9DSrfAfVfo-c3K-1dILXA5iijnHtaunKwv2JzECSz9jcdy3t78rDFeAEgaixnnIYYlgQcipbZQWoCa"; private String mode = "sandbox"; /** * 創建退款請求體 */ public RefundRequest buildRequestBody() { RefundRequest refundRequest = new RefundRequest(); Money money = new Money(); money.currencyCode("USD"); money.value("40.00"); refundRequest.amount(money); refundRequest.invoiceId("T202005230002"); refundRequest.noteToPayer("7天無理由退款"); return refundRequest; } /** * 申請退款 */ public HttpResponse<Refund> refundOrder(String orderId) throws IOException { OrdersGetRequest ordersGetRequest = new OrdersGetRequest(orderId); PayPalClient payPalClient = new PayPalClient(); HttpResponse<com.paypal.orders.Order> ordersGetResponse = null; try { ordersGetResponse = payPalClient.client(mode, clientId, clientSecret).execute(ordersGetRequest); } catch (Exception e) { try { log.error("第1次調用paypal訂單查詢失敗"); ordersGetResponse = payPalClient.client(mode, clientId, clientSecret).execute(ordersGetRequest); } catch (Exception e2) { try { log.error("第2次調用paypal訂單查詢失敗"); ordersGetResponse = payPalClient.client(mode, clientId, clientSecret).execute(ordersGetRequest); } catch (Exception e3) { log.error("第3次調用paypal訂單查詢失敗,失敗原因:{}", e3.getMessage()); } } } String captureId = ordersGetResponse.result().purchaseUnits().get(0).payments().captures().get(0).id(); CapturesRefundRequest request = new CapturesRefundRequest(captureId); request.prefer("return=representation"); request.requestBody( buildRequestBody()); HttpResponse<Refund> response = null; try { response = payPalClient.client(mode, clientId, clientSecret).execute(request); } catch (IOException e) { try { log.error("第1次調用paypal退款申請失敗"); response = payPalClient.client(mode, clientId, clientSecret).execute(request); } catch (Exception e1) { try { log.error("第2次調用paypal退款申請失敗"); response = payPalClient.client(mode, clientId, clientSecret).execute(request); } catch (Exception e2) { log.error("第3次調用paypal退款申請失敗,失敗原因 {}", e2.getMessage()); } } } log.info("Status Code = {}, Status = {}, RefundID = {}", response.statusCode(), response.result().status(), response.result().id()); if("COMPLETED".equals(response.result().status())) { //進行數據庫操作,修改狀態為已退款(配合回調和退款查詢確定退款成功) log.info("退款成功"); } for (com.paypal.payments.LinkDescription link : response.result().links()) { log.info("Links-{}: {} \tCall Type: {}", link.rel(), link.href(), link.method()); } String json = new JSONObject(new Json().serialize(response.result())).toString(4); log.info("refundOrder response body: {}", json); return response; } public static void main(String[] args) { try { new RefundOrder().refundOrder("訂單id,CreateOrder 生成"); } catch (Exception e) { e.printStackTrace(); } } }
5. OrdersGet(查詢訂單詳情)
package com.ratta.paypal.info; import java.io.IOException; import java.util.List; import org.json.JSONObject; import com.paypal.http.HttpResponse; import com.paypal.http.serializer.Json; import com.paypal.orders.Capture; import com.paypal.orders.Order; import com.paypal.orders.OrdersGetRequest; import com.paypal.orders.Refund; public class OrdersGet extends PayPalClient { private String clientId = "AdTJ2en6Av42r1xoziJj6bJK-X4tRGDHACZId0OPXfGyXs8OyoYEmlm8bHjzrgd3UislDQR0iBP7x-wM"; private String clientSecret = "EC9DSrfAfVfo-c3K-1dILXA5iijnHtaunKwv2JzECSz9jcdy3t78rDFeAEgaixnnIYYlgQcipbZQWoCa"; private String mode = "sandbox"; public void testOrdersGetRequest() throws IOException { OrdersGetRequest request = new OrdersGetRequest("訂單id,CreateOrder 生成"); HttpResponse<Order> response = null; try { response = client(mode, clientId, clientSecret).execute(request); } catch (Exception e) { try { System.out.println("調用paypal訂單查詢失敗,鏈接異常1"); response = client(mode, clientId, clientSecret).execute(request); } catch (Exception e2) { try { System.out.println("調用paypal訂單查詢失敗,鏈接異常2"); response = client(mode, clientId, clientSecret).execute(request); } catch (Exception e3) { System.out.println("調用paypal訂單查詢失敗,鏈接異常3"); System.out.println(e3.getMessage()); } } } System.out.println("Status Code: " + response.statusCode()); System.out.println("Status: " + response.result().status()); System.out.println("Order id: " + response.result().id()); if(response.result().purchaseUnits().get(0).payments() != null) { List<Capture> captures = response.result().purchaseUnits().get(0).payments().captures(); if(captures != null) { for (Capture capture : captures) { System.out.println("\t訂單編號= " + capture.invoiceId() + "\tCapture Id= " + capture.id() + "\tCapture status= " + capture.status() + "\tCapture amount= " + capture.amount().currencyCode() + ":" + capture.amount().value()); } } List<Refund> refunds = response.result().purchaseUnits().get(0).payments().refunds(); if(refunds != null) { for (Refund refund : refunds) { System.out.println("\t售后編號= " + refund.invoiceId() + "\tRefund Id= " + refund.id() + "\tRefund status= " + refund.status() + "\tRefund amount= " + refund.amount().currencyCode() + ":" + refund.amount().value()); } } } System.out.println("Links: "); for (com.paypal.orders.LinkDescription link : response.result().links()) { System.out.println("\t" + link.rel() + ": " + link.href() + "\tCall Type: " + link.method()); } System.out.println("Full response body:"); String json = new JSONObject(new Json().serialize(response.result())).toString(4); System.out.println(json); } public static void main(String[] args) { try { new OrdersGet().testOrdersGetRequest(); } catch (IOException e) { e.printStackTrace(); } } }
6. CapturesGet(查詢扣款詳情)
package com.ratta.paypal.info; import java.io.IOException; import org.json.JSONObject; import com.paypal.http.HttpResponse; import com.paypal.http.serializer.Json; import com.paypal.payments.Capture; import com.paypal.payments.CapturesGetRequest; import com.paypal.payments.LinkDescription; public class CapturesGet extends PayPalClient { private String clientId = "AdTJ2en6Av42r1xoziJj6bJK-X4tRGDHACZId0OPXfGyXs8OyoYEmlm8bHjzrgd3UislDQR0iBP7x-wM"; private String clientSecret = "EC9DSrfAfVfo-c3K-1dILXA5iijnHtaunKwv2JzECSz9jcdy3t78rDFeAEgaixnnIYYlgQcipbZQWoCa"; private String mode = "sandbox"; public void testCapturesGetRequest() throws IOException { CapturesGetRequest request = new CapturesGetRequest("扣款id, CaptureOrder生成"); HttpResponse<Capture> response = client(mode, clientId, clientSecret).execute(request); System.out.println("Status Code: " + response.statusCode()); System.out.println("Status: " + response.result().status()); System.out.println("Capture ids: " + response.result().id()); System.out.println("Links: "); for (LinkDescription link : response.result().links()) { System.out.println("\t" + link.rel() + ": " + link.href() + "\tCall Type: " + link.method()); } System.out.println("Full response body:"); System.out.println(new JSONObject(new Json().serialize(response.result())).toString(4)); } public static void main(String[] args) { try { new CapturesGet().testCapturesGetRequest(); } catch (IOException e) { e.printStackTrace(); } } }
7. RefundsGet(查詢退款詳情)
package com.ratta.paypal.info; import com.paypal.http.HttpResponse; import com.paypal.http.serializer.Json; import com.paypal.payments.LinkDescription; import com.paypal.payments.Refund; import com.paypal.payments.RefundsGetRequest; import org.json.JSONObject; import java.io.IOException; public class RefundsGet extends PayPalClient { private String clientId = "AdTJ2en6Av42r1xoziJj6bJK-X4tRGDHACZId0OPXfGyXs8OyoYEmlm8bHjzrgd3UislDQR0iBP7x-wM"; private String clientSecret = "EC9DSrfAfVfo-c3K-1dILXA5iijnHtaunKwv2JzECSz9jcdy3t78rDFeAEgaixnnIYYlgQcipbZQWoCa"; private String mode = "sandbox"; public void testRefundsGetRequest() throws IOException { RefundsGetRequest request = new RefundsGetRequest("退款id RefundOrder生成"); HttpResponse<Refund> response = client(mode, clientId, clientSecret).execute(request); System.out.println("Status Code: " + response.statusCode()); System.out.println("Status: " + response.result().status()); System.out.println("Refund Id: " + response.result().id()); System.out.println("Links: "); for (LinkDescription link : response.result().links()) { System.out.println("\t" + link.rel() + ": " + link.href() + "\tCall Type: " + link.method()); } System.out.println("Full response body:"); System.out.println(new JSONObject(new Json().serialize(response.result())).toString(4)); } public static void main(String[] args) { try { new RefundsGet().testRefundsGetRequest(); } catch (IOException e) { e.printStackTrace(); } } }
到了這里基本上已經接入完畢, 現在還剩余異步回調,PayPal的異步回調我使用的 是IPN ,這個回調是需要登錄進你的PayPal賬號,
8.PayPalController(PayPal控制層代碼)
需要進入沙盒app內,SANDBOX WEBHOOKS

package com.ratta.controller; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import com.ratta.service.PayPalCheckoutService; import com.ratta.util.RequestToMapUtil; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiOperation; @RestController @Api(description = "PayPalCheckout接口") public class PayPalCheckoutController { @Autowired private PayPalCheckoutService payPalCheckoutService; @ApiOperation(value = "ipn異步回調") @PostMapping(value = "/paypal/ipn/back") public String callback(HttpServletRequest request, HttpServletResponse response) { return payPalCheckoutService.callback(RequestToMapUtil.getParameterMap(request)); } }
9.RequestToMapUtil
package com.ratta.util; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.http.HttpServletRequest; /** * 將Request轉換成Map * @author yll * */ public class RequestToMapUtil { @SuppressWarnings({ "unchecked", "rawtypes" }) public static Map getParameterMap(HttpServletRequest request) { // 參數Map Map properties = request.getParameterMap(); // 返回值Map Map returnMap = new HashMap(); Iterator entries = properties.entrySet().iterator(); Map.Entry entry; String name = ""; String value = ""; while (entries.hasNext()) { entry = (Map.Entry) entries.next(); name = (String) entry.getKey(); Object valueObj = entry.getValue(); if (null == valueObj) { value = ""; } else if (valueObj instanceof String[]) { String[] values = (String[]) valueObj; for (int i = 0; i < values.length; i++) { value = values[i] + ","; } value = value.substring(0, value.length() - 1); } else { value = valueObj.toString(); } returnMap.put(name, value); } return returnMap; } public static Map<String, Object> getPrepayMapInfo(String Str) { String notityXml = Str.replaceAll("</?xml>", ""); Pattern pattern = Pattern.compile("<.*?/.*?>"); Matcher matcher = pattern.matcher(notityXml); Pattern pattern2 = Pattern.compile("!.*]"); Map<String, Object> mapInfo = new HashMap<>(); while (matcher.find()) { String key = matcher.group().replaceAll(".*/", ""); key = key.substring(0, key.length() - 1); Matcher matcher2 = pattern2.matcher(matcher.group()); String value = matcher.group().replaceAll("</?.*?>", ""); if (matcher2.find() && !value.equals("DATA")) { value = matcher2.group().replaceAll("!.*\\[", ""); value = value.substring(0, value.length() - 2); } mapInfo.put(key, value); } return mapInfo; } }
10.PayPalCheckoutService
package com.ratta.service; import java.util.Map; import com.ratta.dto.CreateOrderDTO; import com.ratta.dto.ExecuteOrderDTO; import com.ratta.dto.RefundOrderDTO; import com.ratta.vo.BaseVO; import com.ratta.vo.RefundOrderVO; public interface PayPalCheckoutService { /** * 回調 * @param map */ String callback(@SuppressWarnings("rawtypes") Map map); }
11.PayPalCheckoutServiceImpl
回調里面的邏輯經過二次編輯基本已經完善,只差判斷金額、幣種和簽名
package com.ratta.service.impl; import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import javax.annotation.Resource; import org.apache.commons.lang3.StringUtils; import org.json.JSONException; import org.json.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.cloud.stream.annotation.EnableBinding; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.support.MessageBuilder; import org.springframework.stereotype.Service; import com.paypal.http.HttpResponse; import com.paypal.http.exceptions.SerializeException; import com.paypal.http.serializer.Json; import com.paypal.orders.AddressPortable; import com.paypal.orders.AmountBreakdown; import com.paypal.orders.AmountWithBreakdown; import com.paypal.orders.ApplicationContext; import com.paypal.orders.Capture; import com.paypal.orders.Item; import com.paypal.orders.LinkDescription; import com.paypal.payments.Refund; import com.paypal.payments.RefundRequest; import com.paypal.payments.RefundsGetRequest; import com.paypal.orders.Money; import com.paypal.orders.Name; import com.paypal.orders.Order; import com.paypal.orders.OrderRequest; import com.paypal.orders.OrdersCaptureRequest; import com.paypal.orders.OrdersCreateRequest; import com.paypal.orders.OrdersGetRequest; import com.paypal.orders.Payer; import com.paypal.orders.PurchaseUnit; import com.paypal.orders.PurchaseUnitRequest; import com.paypal.orders.ShippingDetail; import com.paypal.payments.CapturesGetRequest; import com.paypal.payments.CapturesRefundRequest; import com.ratta.constants.PayPalCheckoutConstant; import com.ratta.pay.info.PayPalClient; import com.ratta.service.PayPalCheckoutService; import lombok.extern.slf4j.Slf4j; @Slf4j @Service @RefreshScope public class PayPalCheckoutServiceImpl implements PayPalCheckoutService { @Value("${paypal.receiver.email}") private String receiverEmail; @Override public String callback(@SuppressWarnings("rawtypes") Map map) { log.info(map.toString()); String outTradeNo = (String)map.get("invoice"); String paymentStatus = (String)map.get("payment_status"); String amount = (String)map.get("mc_gross"); String currency = (String)map.get("mc_currency"); String paymentId = (String)map.get("txn_id"); String parentPaymentId = (String)map.get("parent_txn_id"); log.info("商家訂單號 = {}", outTradeNo); log.info("訂單狀態 = {}", paymentStatus); log.info("金額 = {}", amount); log.info("幣種 = {}", currency); log.info("流水號 = {}", paymentId); log.info("父流水號 = {}", parentPaymentId); if (!receiverEmail.equals((String) map.get("receiver_email"))) { log.info("FAIL = 商戶id錯誤, outTradeNo = {}", outTradeNo); return "failure"; } if("Completed".equals(paymentStatus)) { //進行數據庫操作 // // log.info("支付成功,狀態為=COMPLETED"); return "success"; } if("Refunded".equals(paymentStatus)) { //進行數據庫操作 // // log.info("退款成功"); return "success"; } if("Pending".equals(paymentStatus) && StringUtils.isEmpty(parentPaymentId)) { String pendingReason = String.valueOf(map.get("pending_reason")); //進行數據庫操作 // // log.info("訂單支付成功,狀態為=PENDING,產生此狀態的原因是 {}", pendingReason ); return "success"; } if(StringUtils.isEmpty(parentPaymentId)) { if(PayPalCheckoutConstant.PAYMENT_STATUS_REVERSED.equals(paymentStatus) || PayPalCheckoutConstant.PAYMENT_STATUS_CANCELED_REVERSAL.equals(paymentStatus) || PayPalCheckoutConstant.PAYMENT_STATUS_DENIED.equals(paymentStatus)) { String reasonCode = String.valueOf(map.get("reason_code")); //進行數據庫操作(狀態修改) // // log.info("訂單異常,請盡快查看處理,狀態為={},產生此狀態的原因是 {} ", paymentStatus, reasonCode); return PayPalCheckoutConstant.SUCCESS; } if(PayPalCheckoutConstant.PAYMENT_STATUS_EXPIRED.equals(paymentStatus) || PayPalCheckoutConstant.PAYMENT_STATUS_CREATED.equals(paymentStatus) || PayPalCheckoutConstant.PAYMENT_STATUS_FAILED.equals(paymentStatus) || PayPalCheckoutConstant.PAYMENT_STATUS_PROCESSED.equals(paymentStatus) || PayPalCheckoutConstant.PAYMENT_STATUS_VOIDED.equals(paymentStatus)) { //進行數據庫操作(狀態修改) // // log.info("其他訂單狀態,訂單異常,請盡快查看處理, 狀態={}", paymentStatus); return PayPalCheckoutConstant.SUCCESS; } } return "failure"; } }
12、PayPalCheckoutConstant
package com.ratta.constants; public class PayPalCheckoutConstant { public static final String CAPTURE = "CAPTURE"; /** * 該標簽將覆蓋PayPal網站上PayPal帳戶中的公司名稱 */ public static final String BRANDNAME = "Supernote"; /** * LOGIN。當客戶單擊PayPal Checkout時,客戶將被重定向到頁面以登錄PayPal并批準付款。 * BILLING。當客戶單擊PayPal Checkout時,客戶將被重定向到一個頁面,以輸入信用卡或借記卡以及完成購買所需的其他相關賬單信息 * NO_PREFERENCE。當客戶單擊“ PayPal Checkout”時,將根據其先前的交互方式將其重定向到頁面以登錄PayPal并批準付款,或重定向至頁面以輸入信用卡或借記卡以及完成購買所需的其他相關賬單信息使用PayPal。 * 默認值:NO_PREFERENCE */ public static final String LANDINGPAGE = "NO_PREFERENCE"; /** * CONTINUE。將客戶重定向到PayPal付款頁面后,將出現“ 繼續”按鈕。當結帳流程啟動時最終金額未知時,請使用此選項,并且您想將客戶重定向到商家頁面而不處理付款。 * PAY_NOW。將客戶重定向到PayPal付款頁面后,出現“ 立即付款”按鈕。當啟動結帳時知道最終金額并且您要在客戶單擊“ 立即付款”時立即處理付款時,請使用此選項。 */ public static final String USERACTION = "CONTINUE"; /** * GET_FROM_FILE。使用貝寶網站上客戶提供的送貨地址。 * NO_SHIPPING。從PayPal網站編輯送貨地址。推薦用于數字商品 * SET_PROVIDED_ADDRESS。使用商家提供的地址??蛻魺o法在PayPal網站上更改此地址 */ public static final String SHIPPINGPREFERENCE = "SET_PROVIDED_ADDRESS"; /** * 交易異常 */ public static final String FAILURE = "failure"; /** * 交易成功 */ public static final String SUCCESS = "success"; /** * ipn回調。支付成功 */ public static final String PAYMENT_STATUS_COMPLETED = "Completed"; /** * ipn回調。退款成功 */ public static final String PAYMENT_STATUS_REFUNDED = "Refunded"; /** * ipn回調。待定 */ public static final String PAYMENT_STATUS_PENDING = "Pending"; /** * ipn回調,付款因退款或其他類型的沖銷而被沖銷。資金已從您的帳戶余額中刪除,并退還給買方 */ public static final String PAYMENT_STATUS_REVERSED = "Reversed"; /** * ipn回調, 撤銷已被取消。例如,您贏得了與客戶的糾紛,并且撤回的交易資金已退還給您 */ public static final String PAYMENT_STATUS_CANCELED_REVERSAL = "Canceled_Reversal"; /** * ipn回調,付款被拒絕 */ public static final String PAYMENT_STATUS_DENIED = "Denied"; /** * ipn回調, 此授權已過期,無法捕獲 */ public static final String PAYMENT_STATUS_EXPIRED = "Expired"; /** * ipn回調, 德國的ELV付款是通過Express Checkout進行的 */ public static final String PAYMENT_STATUS_CREATED = "Created"; /** * ipn回調, 付款失敗。僅當付款是通過您客戶的銀行帳戶進行的。 */ public static final String PAYMENT_STATUS_FAILED = "Failed"; /** * ipn回調,付款已被接受 */ public static final String PAYMENT_STATUS_PROCESSED = "Processed"; /** * ipn回調,此授權已失效 */ public static final String PAYMENT_STATUS_VOIDED = "Voided"; //訂單狀態 /** * 1、支付完成;捕獲的付款的資金已記入收款人的PayPal帳戶 * 2、退款完成;該交易的資金已記入客戶的帳戶 */ public static final String STATE_COMPLETED = "COMPLETED"; /** * 部分退款;少于所捕獲付款金額的金額已部分退還給付款人。 */ public static final String STATE_PARTIALLY_REFUNDED = "PARTIALLY_REFUNDED"; /** * 1、支付待定;捕獲的付款資金尚未記入收款人的PayPal帳戶。有關更多信息請參見status.details。 * 2、退款待定;有關更多信息,請參見status_details.reason。 */ /** * 支付待定: * capture_status_details * reason 枚舉 * 捕獲的付款狀態為PENDING或DENIED的原因??赡艿闹禐椋? * BUYER_COMPLAINT。付款人與貝寶(PayPal)對此捕獲的付款提出了爭議。 * CHARGEBACK。響應于付款人與用于支付此已捕獲付款的金融工具的發行人對此已捕獲的付款提出異議,已收回的資金被撤回。 * ECHECK。由尚未結清的電子支票支付的付款人。 * INTERNATIONAL_WITHDRAWAL。訪問您的在線帳戶。在您的“帳戶概覽”中,接受并拒絕此筆付款。 * OTHER。無法提供其他特定原因。有關此筆付款的更多信息,請在線訪問您的帳戶或聯系PayPal。 * PENDING_REVIEW。捕獲的付款正在等待人工審核。 *(手動收?。㏑ECEIVING_PREFERENCE_MANDATES_MANUAL_ACTION。收款人尚未為其帳戶設置適當的接收首選項。有關如何接受或拒絕此付款的更多信息,請在線訪問您的帳戶。通常在某些情況下提供此原因,例如,當所捕獲付款的貨幣與收款人的主要持有貨幣不同時。 * REFUNDED。收回的資金已退還。 * TRANSACTION_APPROVED_AWAITING_FUNDING。付款人必須將這筆付款的資金匯出。通常,此代碼適用于手動EFT。 * UNILATERAL。收款人沒有PayPal帳戶。 * VERIFICATION_REQUIRED。收款人的PayPal帳戶未通過驗證。 */ /** * 退款待定 * 退款具有“PENDING”或“FAILED”狀態的原因。 可能的值為: * ECHECK。客戶的帳戶通過尚未結清的eCheck進行注資。 */ public static final String STATE_PENDING = "PENDING"; /** * 退款;大于或等于此捕獲的付款金額的金額已退還給付款人 */ public static final String STATE_REFUNDED = "REFUNDED"; /** * 支付拒絕 */ public static final String STATE_DENIED = "DENIED"; /** * 退款失敗 */ public static final String STATE_FAILED = "FAILED"; /** * 爭議狀態 */ public static final String BUYER_COMPLAINT = "BUYER_COMPLAINT"; /** * 沙箱環境請求網關地址 */ public static final String SANDBOX = "https://api.sandbox.paypal.com"; /** * 生產環境請求網關地址 */ public static final String LIVE = "https://api.paypal.com"; /** * 添加物流信息請求路徑 */ public static final String ADD_TRACK_URL = "/v1/shipping/trackers-batch"; /** * 修改物流信息請求路徑 */ public static final String UPDATE_TRACK_URL = "/v1/shipping/trackers/"; }
浙公網安備 33010602011771號