From 1d443b140b1c65268dc67e707c555b8db012772d Mon Sep 17 00:00:00 2001 From: xupei Date: Wed, 10 Apr 2024 16:07:52 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=BAPayPal=E6=B7=BB=E5=8A=A0=E5=AE=9A?= =?UTF-8?q?=E6=97=B6=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ai/da/common/enums/OrderStatusEnum.java | 7 +- .../common/enums/PayPalOrderStatusEnum.java | 18 +- .../com/ai/da/common/task/AliPayTask.java | 4 +- .../com/ai/da/common/task/PaypalTask.java | 42 +++++ .../controller/PayPalCheckoutController.java | 4 +- .../com/ai/da/service/CallBackService.java | 12 -- .../ai/da/service/PayPalCheckoutService.java | 4 +- .../da/service/impl/CallBackServiceImpl.java | 159 ------------------ .../impl/PayPalCheckoutServiceImpl.java | 65 +++++-- src/main/resources/paypal-sandbox.properties | 20 +-- 10 files changed, 129 insertions(+), 206 deletions(-) create mode 100644 src/main/java/com/ai/da/common/task/PaypalTask.java delete mode 100644 src/main/java/com/ai/da/service/CallBackService.java delete mode 100644 src/main/java/com/ai/da/service/impl/CallBackServiceImpl.java diff --git a/src/main/java/com/ai/da/common/enums/OrderStatusEnum.java b/src/main/java/com/ai/da/common/enums/OrderStatusEnum.java index 559ae38b..aac9719d 100644 --- a/src/main/java/com/ai/da/common/enums/OrderStatusEnum.java +++ b/src/main/java/com/ai/da/common/enums/OrderStatusEnum.java @@ -40,7 +40,12 @@ public enum OrderStatusEnum { /** * 退款异常 */ - REFUND_ABNORMAL("退款异常"); + REFUND_ABNORMAL("退款异常"), + + /** + * paypal订单状态为 APPROVED + */ + ORDER_PROCESSING("订单处理中"); /** * 类型 diff --git a/src/main/java/com/ai/da/common/enums/PayPalOrderStatusEnum.java b/src/main/java/com/ai/da/common/enums/PayPalOrderStatusEnum.java index f35fd8f8..f9735c05 100644 --- a/src/main/java/com/ai/da/common/enums/PayPalOrderStatusEnum.java +++ b/src/main/java/com/ai/da/common/enums/PayPalOrderStatusEnum.java @@ -7,18 +7,24 @@ import lombok.Getter; @AllArgsConstructor public enum PayPalOrderStatusEnum { + // The order was created with the specified context. + // 订单已创建并具有指定的上下文。 CREATED("CREATED"), - + // The order was saved and persisted. The order status continues to be in progress until a capture is made with final_capture = true for all purchase units within the order. + // 订单已保存并持久化。订单状态仍处于进行中,直到对订单中的所有购买单元进行了 final_capture = true 的捕获为止 SAVED("SAVED"), - + // The customer approved the payment through the PayPal wallet or another form of guest or unbranded payment. For example, a card, bank account, or so on. + // 客户通过PayPal钱包或其他形式的游客或非品牌支付批准了付款。例如,信用卡、银行账户等。 APPROVED("APPROVED"), - + // All purchase units in the order are voided. + // 订单中的所有购买单元都已作废。 VOIDED("VOIDED"), - + // The payment was authorized or the authorized payment was captured for the order. + // 订单的支付已被授权或已捕获授权的支付。 COMPLETED("COMPLETED"), - + // The order requires an action from the payer (e.g. 3DS authentication). Redirect the payer to the "rel":"payer-action" HATEOAS link returned as part of the response prior to authorizing or capturing the order. + // 订单需要支付者执行某项操作(例如3DS身份验证)。在授权或捕获订单之前,请将支付者重定向到响应中返回的"rel":"payer-action" HATEOAS链接。 PAYER_ACTION_REQUIRED("PAYER_ACTION_REQUIRED"); - private final String status; } diff --git a/src/main/java/com/ai/da/common/task/AliPayTask.java b/src/main/java/com/ai/da/common/task/AliPayTask.java index 64b0e81c..fa9a9b3a 100644 --- a/src/main/java/com/ai/da/common/task/AliPayTask.java +++ b/src/main/java/com/ai/da/common/task/AliPayTask.java @@ -24,10 +24,10 @@ public class AliPayTask { /** * 从第0秒开始每隔30秒执行1次,查询创建超过5分钟,并且未支付的订单 */ - @Scheduled(cron = "0/30 * * * * ?") +// @Scheduled(cron = "0/30 * * * * ?") public void orderConfirm(){ - log.info("orderConfirm 被执行......"); + log.info("Alipay orderConfirm 被执行......"); List orderInfoList = orderInfoService.getNoPayOrderByDuration(5, PayTypeEnum.ALIPAY.getType()); diff --git a/src/main/java/com/ai/da/common/task/PaypalTask.java b/src/main/java/com/ai/da/common/task/PaypalTask.java new file mode 100644 index 00000000..bc0e24dd --- /dev/null +++ b/src/main/java/com/ai/da/common/task/PaypalTask.java @@ -0,0 +1,42 @@ +package com.ai.da.common.task; + +import com.ai.da.common.enums.PayTypeEnum; +import com.ai.da.mapper.primary.entity.OrderInfo; +import com.ai.da.service.OrderInfoService; +import com.ai.da.service.PayPalCheckoutService; +import com.paypal.http.exceptions.SerializeException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; + +@Slf4j +@Component +public class PaypalTask { + + @Resource + private OrderInfoService orderInfoService; + + @Resource + private PayPalCheckoutService payPalCheckoutService; + + @Scheduled(cron = "0/30 * * * * ?") + public void orderConfirm() throws SerializeException { + + log.info("PayPal orderConfirm 被执行......"); + + List orderInfoList = orderInfoService.getNoPayOrderByDuration(5, PayTypeEnum.PAYPAL.getType()); + + for (OrderInfo orderInfo : orderInfoList) { + String orderNo = orderInfo.getOrderNo(); + log.warn("超时订单 ===> {}", orderNo); + + //核实订单状态:调用支付宝查单接口 + payPalCheckoutService.checkOrderStatus(orderNo); + + } + } +} diff --git a/src/main/java/com/ai/da/controller/PayPalCheckoutController.java b/src/main/java/com/ai/da/controller/PayPalCheckoutController.java index d1ad9134..203da0e7 100644 --- a/src/main/java/com/ai/da/controller/PayPalCheckoutController.java +++ b/src/main/java/com/ai/da/controller/PayPalCheckoutController.java @@ -45,8 +45,8 @@ public class PayPalCheckoutController { @ApiOperation(value = "查询指定订单") @PostMapping(value = "/trade/query/{orderNo}") - public Response queryOrder(@PathVariable String orderNo) throws SerializeException { - String s = payPalCheckoutService.queryOrder(orderNo); + public Response queryOrder(@PathVariable String orderNo) throws SerializeException { + Order s = payPalCheckoutService.queryOrder(orderNo); return Response.success(s); } diff --git a/src/main/java/com/ai/da/service/CallBackService.java b/src/main/java/com/ai/da/service/CallBackService.java deleted file mode 100644 index 39efb1eb..00000000 --- a/src/main/java/com/ai/da/service/CallBackService.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.ai.da.service; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -public interface CallBackService { - - Boolean doGet(HttpServletRequest req, HttpServletResponse resp) - throws ServletException, IOException; -} diff --git a/src/main/java/com/ai/da/service/PayPalCheckoutService.java b/src/main/java/com/ai/da/service/PayPalCheckoutService.java index a3f0fcdb..450c521d 100644 --- a/src/main/java/com/ai/da/service/PayPalCheckoutService.java +++ b/src/main/java/com/ai/da/service/PayPalCheckoutService.java @@ -19,7 +19,7 @@ public interface PayPalCheckoutService { Boolean doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException; - String queryOrder(String orderNo) throws SerializeException; + Order queryOrder(String orderNo) throws SerializeException; Order captureOrder(String orderId) throws IOException; @@ -28,5 +28,7 @@ public interface PayPalCheckoutService { String getOAuth(); void processOrder(String orderId); + + void checkOrderStatus(String orderNo) throws SerializeException; } diff --git a/src/main/java/com/ai/da/service/impl/CallBackServiceImpl.java b/src/main/java/com/ai/da/service/impl/CallBackServiceImpl.java deleted file mode 100644 index 7c5fd42f..00000000 --- a/src/main/java/com/ai/da/service/impl/CallBackServiceImpl.java +++ /dev/null @@ -1,159 +0,0 @@ -package com.ai.da.service.impl; - -import com.ai.da.common.config.PayPalClient; -import com.ai.da.common.constant.PayPalCheckoutConstant; -import com.ai.da.common.utils.paypalRequest.WebhookVerifyRequest; -import com.ai.da.service.CallBackService; -import com.ai.da.service.PayPalCheckoutService; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.paypal.api.payments.Event; -import com.paypal.base.Constants; -import com.paypal.base.SDKUtil; -import com.paypal.base.rest.APIContext; -import com.paypal.base.rest.PayPalRESTException; -import com.paypal.http.HttpResponse; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; - -import javax.annotation.Resource; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.SignatureException; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; - -import static com.ai.da.common.constant.PayPalCheckoutConstant.MODE; - -// #Validate Webhook Sample -// -// This sample code demonstrates how to validate a webhook received on your -// web server. This sample assumes that you use the java servlet, which returns -// the HttpServletRequest object. However, you can modify this code to -// your specific case. -// - -@Slf4j -@Service -public class CallBackServiceImpl implements CallBackService { - - @Value("${paypal.client-id}") - private String clientId; - - @Value("${paypal.client-secret}") - private String clientSecret; - - @Resource - private PayPalClient payPalClient; - - @Resource - private PayPalCheckoutService payPalCheckoutService; - - @Override - public Boolean doGet(HttpServletRequest req, HttpServletResponse resp) - throws ServletException, IOException { - return doPost(req, resp); - } - - // ##Validate Webhook - protected Boolean doPost(HttpServletRequest req, HttpServletResponse resp) - throws ServletException, IOException { - try { - String body = getBody(req); - Map webhookEvent = new ObjectMapper().readValue(body, Map.class); - - HashMap webhookRequest = new HashMap<>(); - webhookRequest.put("auth_algo",SDKUtil.validateAndGet(getHeadersInfo(req), "PAYPAL-AUTH-ALGO")); - webhookRequest.put("cert_url",SDKUtil.validateAndGet(getHeadersInfo(req), "PAYPAL-CERT-URL")); - webhookRequest.put("transmission_id",SDKUtil.validateAndGet(getHeadersInfo(req), "PAYPAL-TRANSMISSION-ID")); - webhookRequest.put("transmission_sig",SDKUtil.validateAndGet(getHeadersInfo(req), "PAYPAL-TRANSMISSION-SIG")); - webhookRequest.put("transmission_time",SDKUtil.validateAndGet(getHeadersInfo(req), "PAYPAL-TRANSMISSION-TIME")); - webhookRequest.put("webhook_id",PayPalCheckoutConstant.WEBHOOK_ID); - webhookRequest.put("webhook_event",webhookEvent); - - WebhookVerifyRequest webhookVerifyRequest = new WebhookVerifyRequest(); - webhookVerifyRequest.authorization(payPalCheckoutService.getOAuth()); - webhookVerifyRequest.requestBody(webhookRequest); - // 验签 - HttpResponse verified = payPalClient.client(MODE, clientId, clientSecret).execute(webhookVerifyRequest); - boolean verifyResult = verified.result().get("verification_status").toString().equals("SUCCESS"); - if (verifyResult){ - // ### Api Context - APIContext apiContext = new APIContext(clientId, clientSecret, PayPalCheckoutConstant.MODE); - - // Set the webhookId that you received when you created this webhook. - apiContext.addConfiguration(Constants.PAYPAL_WEBHOOK_ID, PayPalCheckoutConstant.WEBHOOK_ID); - Boolean result = Event.validateReceivedEvent(apiContext, getHeadersInfo( - req), body); - log.info("Webhook Validated: " + result); - - if (result){ - // 处理订单数据 - LinkedHashMap> webhookEventMap = (LinkedHashMap>) webhookEvent; - String orderId = webhookEventMap.get("resource").get("id"); - payPalCheckoutService.processOrder(orderId); - return Boolean.TRUE; - } - - } - } catch (PayPalRESTException | InvalidKeyException | NoSuchAlgorithmException | SignatureException e) { - log.error(e.getMessage()); - } - return Boolean.FALSE; - } - - // Simple helper method to help you extract the headers from HttpServletRequest object. - private static Map getHeadersInfo(HttpServletRequest request) { - Map map = new HashMap(); - @SuppressWarnings("rawtypes") - Enumeration headerNames = request.getHeaderNames(); - while (headerNames.hasMoreElements()) { - String key = (String) headerNames.nextElement(); - String value = request.getHeader(key); - map.put(key, value); - } - return map; - } - - // Simple helper method to fetch request data as a string from HttpServletRequest object. - private static String getBody(HttpServletRequest request) throws IOException { - String body; - StringBuilder stringBuilder = new StringBuilder(); - BufferedReader bufferedReader = null; - try { - InputStream inputStream = request.getInputStream(); - if (inputStream != null) { - bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); - char[] charBuffer = new char[128]; - int bytesRead = -1; - while ((bytesRead = bufferedReader.read(charBuffer)) > 0) { - stringBuilder.append(charBuffer, 0, bytesRead); - } - } else { - stringBuilder.append(""); - } - } catch (IOException ex) { - throw ex; - } finally { - if (bufferedReader != null) { - try { - bufferedReader.close(); - } catch (IOException ex) { - throw ex; - } - } - } - body = stringBuilder.toString(); - log.info("回调参数 ===> {}", body); - return body; - } -} diff --git a/src/main/java/com/ai/da/service/impl/PayPalCheckoutServiceImpl.java b/src/main/java/com/ai/da/service/impl/PayPalCheckoutServiceImpl.java index 929cb3c5..39eb3206 100644 --- a/src/main/java/com/ai/da/service/impl/PayPalCheckoutServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/PayPalCheckoutServiceImpl.java @@ -3,7 +3,6 @@ package com.ai.da.service.impl; import cn.hutool.core.convert.Convert; import com.ai.da.common.config.PayPalClient; import com.ai.da.common.config.exception.BusinessException; -import com.ai.da.common.constant.PayPalCheckoutConstant; import com.ai.da.common.enums.*; import com.ai.da.common.utils.RedisUtil; import com.ai.da.common.utils.paypalRequest.AuthenticationRequest; @@ -27,7 +26,6 @@ import com.paypal.payments.CapturesRefundRequest; import com.paypal.payments.RefundRequest; import com.paypal.payments.RefundsGetRequest; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.json.JSONObject; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -329,7 +327,7 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService { * @return * @throws SerializeException */ - public String queryOrder(String orderNo) throws SerializeException { + public Order queryOrder(String orderNo) throws SerializeException { OrdersGetRequest request = new OrdersGetRequest(orderNo); HttpResponse response = null; @@ -337,33 +335,31 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService { response = payPalClient.client(mode, clientId, clientSecret).execute(request); } catch (Exception e) { log.error("paypal订单查询失败,失败原因 ===> {}", e.getMessage()); + return null; } - System.out.println("Status Code: " + response.statusCode()); - System.out.println("Status: " + response.result().status()); - System.out.println("Order id: " + response.result().id()); + log.debug("Status Code: " + response.statusCode() + "\tStatus: " + response.result().status() + "\tOrder id: " + response.result().id()); if (response.result().purchaseUnits().get(0).payments() != null) { List 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()); + log.debug("\t订单编号= " + capture.invoiceId() + "\tCapture Id= " + capture.id() + "\tCapture status= " + capture.status() + "\tCapture amount= " + capture.amount().currencyCode() + ":" + capture.amount().value()); } } List 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()); + log.debug("\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()); + log.debug("Links: \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); - return null; + log.info("Full response body: {}", json); + return response.result(); } /** @@ -598,6 +594,49 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService { creditsService.buyCredits(orderInfo.getAccountId(), orderInfo.getTotalFee() / Integer.parseInt(CreditsEventsEnum.PRICE.getValue())); } } + + @Override + public void checkOrderStatus(String orderNo) throws SerializeException { + + log.warn("根据订单号核实订单状态 ===> {}", orderNo); + + Order result = this.queryOrder(orderNo); + + // 订单未创建 | 订单异常 | 订单创建过但未支付 + if(result == null || PayPalOrderStatusEnum.CREATED.getStatus().equals(result.status())){ + log.warn("核实订单未创建 ===> {}", orderNo); + //更新本地订单状态 + orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.TIMEOUT_CLOSED); + return ; + } + + // 解析查单响应结果 + String tradeStatus = result.status(); + OrderInfo orderByOrderNo = orderInfoService.getOrderByOrderNo(orderNo); + + // 支付了,但还没完成 + if(PayPalOrderStatusEnum.APPROVED.getStatus().equals(tradeStatus)){ + log.warn("核实订单未支付 ===> {}", orderNo); + // 更新商户端订单状态 + orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.ORDER_PROCESSING); + } + + if(PayPalOrderStatusEnum.COMPLETED.getStatus().equals(tradeStatus)){ + log.warn("核实订单已支付 ===> {}", orderNo); + //如果订单已支付,则更新商户端订单状态 + orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.SUCCESS); + //并记录支付日志 + paymentInfoService.createPaymentInfoForPayPal(result); + // 添加积分变更记录 + creditsService.insertToCreditsDetail(orderByOrderNo.getAccountId(), + CreditsEventsEnum.BUY_CREDITS.getName() + "--Paypal", + CreditsEventsEnum.BUY_CREDITS.getValue(), + "positive"); + // 更新积分 + creditsService.buyCredits(orderByOrderNo.getAccountId(),orderByOrderNo.getTotalFee() / Integer.parseInt(CreditsEventsEnum.PRICE.getValue())); + } + + } } diff --git a/src/main/resources/paypal-sandbox.properties b/src/main/resources/paypal-sandbox.properties index aea3ac83..d39516a1 100644 --- a/src/main/resources/paypal-sandbox.properties +++ b/src/main/resources/paypal-sandbox.properties @@ -1,9 +1,9 @@ # developer-sandbox -#paypal.client-id=ATbaebYi7-GXWRWJqwRLYMzKEbwjh4BFRqD4Y13i4lZq0rplWIM_IpPrtPKpdkAt_KrPXd6IJTwsDqa5 -#paypal.client-secret=EHWWJqGmmbfjLXqCUpGrvxRYBPPtWvA3hR5ZaAyHlGSVJiHoQPS8skbNaJ9h39VObnchUbgiY2pPu__s -#paypal.receiver.email=sb-ukxfk29608925@business.example.com -#paypal.mode=sandbox -#paypal.webhook_id=31797347YC028794L +paypal.client-id=ATbaebYi7-GXWRWJqwRLYMzKEbwjh4BFRqD4Y13i4lZq0rplWIM_IpPrtPKpdkAt_KrPXd6IJTwsDqa5 +paypal.client-secret=EHWWJqGmmbfjLXqCUpGrvxRYBPPtWvA3hR5ZaAyHlGSVJiHoQPS8skbNaJ9h39VObnchUbgiY2pPu__s +paypal.receiver.email=sb-ukxfk29608925@business.example.com +paypal.mode=sandbox +paypal.webhook_id=31797347YC028794L # aida-sandbox #paypal.client-id=AbDDH8jnTrKqjnWLFgEu6LogYzVz2ZLuirE4W54t1M4lrofrP5OzXfhbxqktLLFB-rAO9KeYQVYFJ_tO @@ -13,8 +13,8 @@ #paypal.webhook_id=1WH327112B602422N # aida-live -paypal.client-id=ASWSIZ3MXJU5w5VOeOHeigWcSw6iinl30ZCipruziKpHclxP0ryf8-7VKG1Ba2VwZwa2DMvGEzTfCTgz -paypal.client-secret=EHQg_K5PSqmp4FJlzEcOEH_kFkmq4aBzaI7jridw53L6cOQRULBAnfv2KakRfrsqaU1PDSkO4Co9Vyxc -paypal.receiver.email=kimwong@code-create.com.hk -paypal.mode=live -paypal.webhook_id=41L14847MC833625B \ No newline at end of file +#paypal.client-id=ASWSIZ3MXJU5w5VOeOHeigWcSw6iinl30ZCipruziKpHclxP0ryf8-7VKG1Ba2VwZwa2DMvGEzTfCTgz +#paypal.client-secret=EHQg_K5PSqmp4FJlzEcOEH_kFkmq4aBzaI7jridw53L6cOQRULBAnfv2KakRfrsqaU1PDSkO4Co9Vyxc +#paypal.receiver.email=kimwong@code-create.com.hk +#paypal.mode=live +#paypal.webhook_id=41L14847MC833625B \ No newline at end of file