为PayPal添加定时任务

This commit is contained in:
2024-04-10 16:07:52 +08:00
parent 7d967ed41e
commit 1d443b140b
10 changed files with 129 additions and 206 deletions

View File

@@ -40,7 +40,12 @@ public enum OrderStatusEnum {
/** /**
* 退款异常 * 退款异常
*/ */
REFUND_ABNORMAL("退款异常"); REFUND_ABNORMAL("退款异常"),
/**
* paypal订单状态为 APPROVED
*/
ORDER_PROCESSING("订单处理中");
/** /**
* 类型 * 类型

View File

@@ -7,18 +7,24 @@ import lombok.Getter;
@AllArgsConstructor @AllArgsConstructor
public enum PayPalOrderStatusEnum { public enum PayPalOrderStatusEnum {
// The order was created with the specified context.
// 订单已创建并具有指定的上下文。
CREATED("CREATED"), 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"), 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"), APPROVED("APPROVED"),
// All purchase units in the order are voided.
// 订单中的所有购买单元都已作废。
VOIDED("VOIDED"), VOIDED("VOIDED"),
// The payment was authorized or the authorized payment was captured for the order.
// 订单的支付已被授权或已捕获授权的支付。
COMPLETED("COMPLETED"), 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"); PAYER_ACTION_REQUIRED("PAYER_ACTION_REQUIRED");
private final String status; private final String status;
} }

View File

@@ -24,10 +24,10 @@ public class AliPayTask {
/** /**
* 从第0秒开始每隔30秒执行1次查询创建超过5分钟并且未支付的订单 * 从第0秒开始每隔30秒执行1次查询创建超过5分钟并且未支付的订单
*/ */
@Scheduled(cron = "0/30 * * * * ?") // @Scheduled(cron = "0/30 * * * * ?")
public void orderConfirm(){ public void orderConfirm(){
log.info("orderConfirm 被执行......"); log.info("Alipay orderConfirm 被执行......");
List<OrderInfo> orderInfoList = orderInfoService.getNoPayOrderByDuration(5, PayTypeEnum.ALIPAY.getType()); List<OrderInfo> orderInfoList = orderInfoService.getNoPayOrderByDuration(5, PayTypeEnum.ALIPAY.getType());

View File

@@ -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<OrderInfo> orderInfoList = orderInfoService.getNoPayOrderByDuration(5, PayTypeEnum.PAYPAL.getType());
for (OrderInfo orderInfo : orderInfoList) {
String orderNo = orderInfo.getOrderNo();
log.warn("超时订单 ===> {}", orderNo);
//核实订单状态:调用支付宝查单接口
payPalCheckoutService.checkOrderStatus(orderNo);
}
}
}

View File

@@ -45,8 +45,8 @@ public class PayPalCheckoutController {
@ApiOperation(value = "查询指定订单") @ApiOperation(value = "查询指定订单")
@PostMapping(value = "/trade/query/{orderNo}") @PostMapping(value = "/trade/query/{orderNo}")
public Response<String> queryOrder(@PathVariable String orderNo) throws SerializeException { public Response<Order> queryOrder(@PathVariable String orderNo) throws SerializeException {
String s = payPalCheckoutService.queryOrder(orderNo); Order s = payPalCheckoutService.queryOrder(orderNo);
return Response.success(s); return Response.success(s);
} }

View File

@@ -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;
}

View File

@@ -19,7 +19,7 @@ public interface PayPalCheckoutService {
Boolean doPost(HttpServletRequest req, HttpServletResponse resp) Boolean doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException; throws ServletException, IOException;
String queryOrder(String orderNo) throws SerializeException; Order queryOrder(String orderNo) throws SerializeException;
Order captureOrder(String orderId) throws IOException; Order captureOrder(String orderId) throws IOException;
@@ -28,5 +28,7 @@ public interface PayPalCheckoutService {
String getOAuth(); String getOAuth();
void processOrder(String orderId); void processOrder(String orderId);
void checkOrderStatus(String orderNo) throws SerializeException;
} }

View File

@@ -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<String, Object> 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<HashMap> 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<String,LinkedHashMap<String,String>> webhookEventMap = (LinkedHashMap<String,LinkedHashMap<String,String>>) 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<String, String> getHeadersInfo(HttpServletRequest request) {
Map<String, String> map = new HashMap<String, String>();
@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;
}
}

View File

@@ -3,7 +3,6 @@ package com.ai.da.service.impl;
import cn.hutool.core.convert.Convert; import cn.hutool.core.convert.Convert;
import com.ai.da.common.config.PayPalClient; import com.ai.da.common.config.PayPalClient;
import com.ai.da.common.config.exception.BusinessException; 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.enums.*;
import com.ai.da.common.utils.RedisUtil; import com.ai.da.common.utils.RedisUtil;
import com.ai.da.common.utils.paypalRequest.AuthenticationRequest; 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.RefundRequest;
import com.paypal.payments.RefundsGetRequest; import com.paypal.payments.RefundsGetRequest;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.json.JSONObject; import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -329,7 +327,7 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService {
* @return * @return
* @throws SerializeException * @throws SerializeException
*/ */
public String queryOrder(String orderNo) throws SerializeException { public Order queryOrder(String orderNo) throws SerializeException {
OrdersGetRequest request = new OrdersGetRequest(orderNo); OrdersGetRequest request = new OrdersGetRequest(orderNo);
HttpResponse<Order> response = null; HttpResponse<Order> response = null;
@@ -337,33 +335,31 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService {
response = payPalClient.client(mode, clientId, clientSecret).execute(request); response = payPalClient.client(mode, clientId, clientSecret).execute(request);
} catch (Exception e) { } catch (Exception e) {
log.error("paypal订单查询失败失败原因 ===> {}", e.getMessage()); log.error("paypal订单查询失败失败原因 ===> {}", e.getMessage());
return null;
} }
System.out.println("Status Code: " + response.statusCode()); log.debug("Status Code: " + response.statusCode() + "\tStatus: " + response.result().status() + "\tOrder id: " + response.result().id());
System.out.println("Status: " + response.result().status());
System.out.println("Order id: " + response.result().id());
if (response.result().purchaseUnits().get(0).payments() != null) { if (response.result().purchaseUnits().get(0).payments() != null) {
List<Capture> captures = response.result().purchaseUnits().get(0).payments().captures(); List<Capture> captures = response.result().purchaseUnits().get(0).payments().captures();
if (captures != null) { if (captures != null) {
for (Capture capture : captures) { 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<Refund> refunds = response.result().purchaseUnits().get(0).payments().refunds(); List<Refund> refunds = response.result().purchaseUnits().get(0).payments().refunds();
if (refunds != null) { if (refunds != null) {
for (Refund refund : refunds) { 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()) { 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); String json = new JSONObject(new Json().serialize(response.result())).toString(4);
System.out.println(json); log.info("Full response body: {}", json);
return null; return response.result();
} }
/** /**
@@ -598,6 +594,49 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService {
creditsService.buyCredits(orderInfo.getAccountId(), orderInfo.getTotalFee() / Integer.parseInt(CreditsEventsEnum.PRICE.getValue())); 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()));
}
}
} }

View File

@@ -1,9 +1,9 @@
# developer-sandbox # developer-sandbox
#paypal.client-id=ATbaebYi7-GXWRWJqwRLYMzKEbwjh4BFRqD4Y13i4lZq0rplWIM_IpPrtPKpdkAt_KrPXd6IJTwsDqa5 paypal.client-id=ATbaebYi7-GXWRWJqwRLYMzKEbwjh4BFRqD4Y13i4lZq0rplWIM_IpPrtPKpdkAt_KrPXd6IJTwsDqa5
#paypal.client-secret=EHWWJqGmmbfjLXqCUpGrvxRYBPPtWvA3hR5ZaAyHlGSVJiHoQPS8skbNaJ9h39VObnchUbgiY2pPu__s paypal.client-secret=EHWWJqGmmbfjLXqCUpGrvxRYBPPtWvA3hR5ZaAyHlGSVJiHoQPS8skbNaJ9h39VObnchUbgiY2pPu__s
#paypal.receiver.email=sb-ukxfk29608925@business.example.com paypal.receiver.email=sb-ukxfk29608925@business.example.com
#paypal.mode=sandbox paypal.mode=sandbox
#paypal.webhook_id=31797347YC028794L paypal.webhook_id=31797347YC028794L
# aida-sandbox # aida-sandbox
#paypal.client-id=AbDDH8jnTrKqjnWLFgEu6LogYzVz2ZLuirE4W54t1M4lrofrP5OzXfhbxqktLLFB-rAO9KeYQVYFJ_tO #paypal.client-id=AbDDH8jnTrKqjnWLFgEu6LogYzVz2ZLuirE4W54t1M4lrofrP5OzXfhbxqktLLFB-rAO9KeYQVYFJ_tO
@@ -13,8 +13,8 @@
#paypal.webhook_id=1WH327112B602422N #paypal.webhook_id=1WH327112B602422N
# aida-live # aida-live
paypal.client-id=ASWSIZ3MXJU5w5VOeOHeigWcSw6iinl30ZCipruziKpHclxP0ryf8-7VKG1Ba2VwZwa2DMvGEzTfCTgz #paypal.client-id=ASWSIZ3MXJU5w5VOeOHeigWcSw6iinl30ZCipruziKpHclxP0ryf8-7VKG1Ba2VwZwa2DMvGEzTfCTgz
paypal.client-secret=EHQg_K5PSqmp4FJlzEcOEH_kFkmq4aBzaI7jridw53L6cOQRULBAnfv2KakRfrsqaU1PDSkO4Co9Vyxc #paypal.client-secret=EHQg_K5PSqmp4FJlzEcOEH_kFkmq4aBzaI7jridw53L6cOQRULBAnfv2KakRfrsqaU1PDSkO4Co9Vyxc
paypal.receiver.email=kimwong@code-create.com.hk #paypal.receiver.email=kimwong@code-create.com.hk
paypal.mode=live #paypal.mode=live
paypal.webhook_id=41L14847MC833625B #paypal.webhook_id=41L14847MC833625B