From ee9675983245f941833c8e1cab8560348f2c3897 Mon Sep 17 00:00:00 2001 From: xupei Date: Fri, 1 Mar 2024 17:31:26 +0800 Subject: [PATCH] =?UTF-8?q?1=E3=80=81=E6=8E=A5=E5=85=A5paypal=202=E3=80=81?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=94=AF=E4=BB=98=E5=AE=9D=E6=94=AF=E4=BB=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 20 +- .../com/ai/da/common/config/PayPalClient.java | 51 ++ .../constant/PayPalCheckoutConstant.java | 177 +++++++ .../ai/da/common/enums/CurrencyCodesEnum.java | 40 ++ .../ai/da/common/enums/OrderStatusEnum.java | 2 +- .../common/enums/PayPalOrderStatusEnum.java | 24 + .../com/ai/da/common/enums/PayTypeEnum.java | 8 +- .../security/filter/AuthenticationFilter.java | 2 +- .../com/ai/da/common/task/AliPayTask.java | 2 +- .../com/ai/da/common/utils/RedisUtil.java | 15 + .../paypalRequest/AuthenticationRequest.java | 30 ++ .../paypalRequest/WebhookVerifyRequest.java | 26 + .../ai/da/controller/AliPayController.java | 99 +--- .../ai/da/controller/OrderInfoController.java | 11 +- .../controller/PayPalCheckoutController.java | 80 +++ .../da/mapper/primary/entity/OrderInfo.java | 4 +- .../da/mapper/primary/entity/RefundInfo.java | 2 +- .../com/ai/da/model/dto/WebhookVerifyDTO.java | 35 ++ .../java/com/ai/da/service/AliPayService.java | 2 + .../com/ai/da/service/CallBackService.java | 12 + .../com/ai/da/service/OrderInfoService.java | 4 + .../ai/da/service/PayPalCheckoutService.java | 29 ++ .../com/ai/da/service/PaymentInfoService.java | 4 + .../com/ai/da/service/RefundInfoService.java | 2 + .../ai/da/service/impl/AliPayServiceImpl.java | 134 +++-- .../da/service/impl/CallBackServiceImpl.java | 159 ++++++ .../da/service/impl/OrderInfoServiceImpl.java | 28 +- .../impl/PayPalCheckoutServiceImpl.java | 476 ++++++++++++++++++ .../service/impl/PaymentInfoServiceImpl.java | 28 +- .../service/impl/RefundInfoServiceImpl.java | 29 +- src/main/resources/application-dev.properties | 2 +- src/main/resources/paypal-sandbox.properties | 5 + 32 files changed, 1379 insertions(+), 163 deletions(-) create mode 100644 src/main/java/com/ai/da/common/config/PayPalClient.java create mode 100644 src/main/java/com/ai/da/common/constant/PayPalCheckoutConstant.java create mode 100644 src/main/java/com/ai/da/common/enums/CurrencyCodesEnum.java create mode 100644 src/main/java/com/ai/da/common/enums/PayPalOrderStatusEnum.java create mode 100644 src/main/java/com/ai/da/common/utils/paypalRequest/AuthenticationRequest.java create mode 100644 src/main/java/com/ai/da/common/utils/paypalRequest/WebhookVerifyRequest.java create mode 100644 src/main/java/com/ai/da/controller/PayPalCheckoutController.java create mode 100644 src/main/java/com/ai/da/model/dto/WebhookVerifyDTO.java create mode 100644 src/main/java/com/ai/da/service/CallBackService.java create mode 100644 src/main/java/com/ai/da/service/PayPalCheckoutService.java create mode 100644 src/main/java/com/ai/da/service/impl/CallBackServiceImpl.java create mode 100644 src/main/java/com/ai/da/service/impl/PayPalCheckoutServiceImpl.java create mode 100644 src/main/resources/paypal-sandbox.properties diff --git a/pom.xml b/pom.xml index 87a9fc66..2c6dacea 100644 --- a/pom.xml +++ b/pom.xml @@ -164,7 +164,6 @@ 9.2.1.jre8 - org.springframework.boot @@ -184,6 +183,25 @@ 4.22.57.ALL + + + com.paypal.sdk + checkout-sdk + 1.0.5 + + + + com.paypal.sdk + rest-api-sdk + LATEST + + + + org.json + json + 20230618 + + diff --git a/src/main/java/com/ai/da/common/config/PayPalClient.java b/src/main/java/com/ai/da/common/config/PayPalClient.java new file mode 100644 index 00000000..bcdef0a3 --- /dev/null +++ b/src/main/java/com/ai/da/common/config/PayPalClient.java @@ -0,0 +1,51 @@ +package com.ai.da.common.config; + +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 org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; + +import java.util.Iterator; + +@Configuration +@Slf4j +@PropertySource("classpath:paypal-sandbox.properties") +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(); + }*/ +} + diff --git a/src/main/java/com/ai/da/common/constant/PayPalCheckoutConstant.java b/src/main/java/com/ai/da/common/constant/PayPalCheckoutConstant.java new file mode 100644 index 00000000..2a7726f2 --- /dev/null +++ b/src/main/java/com/ai/da/common/constant/PayPalCheckoutConstant.java @@ -0,0 +1,177 @@ +package com.ai.da.common.constant; + +public class PayPalCheckoutConstant { + + public static String MODE = "sandbox"; + + public static final String CAPTURE = "CAPTURE"; + /** + * 该标签将覆盖PayPal网站上PayPal帐户中的公司名称 + */ + public static final String BRANDNAME = "AIDA"; + /** + * 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 = "PAY_NOW"; + /** + * GET_FROM_FILE。使用贝宝网站上客户提供的送货地址。 + * NO_SHIPPING。从PayPal网站编辑送货地址。推荐用于数字商品 + * SET_PROVIDED_ADDRESS。使用商家提供的地址。客户无法在PayPal网站上更改此地址 + */ +// public static final String SHIPPINGPREFERENCE = "SET_PROVIDED_ADDRESS"; + public static final String SHIPPINGPREFERENCE = "NO_SHIPPING"; + /** + * 交易异常 + */ + 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。捕获的付款正在等待人工审核。 + *(手动收取)RECEIVING_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/"; + + public final static String CMD_NOTIFY_VALIDATE = "_notify-validate"; + + public final static String WEBHOOK_ID = "31797347YC028794L"; + + public final static String PAYPAL_TOKEN_KEY = "PayPalAccessToken"; + + +} + + diff --git a/src/main/java/com/ai/da/common/enums/CurrencyCodesEnum.java b/src/main/java/com/ai/da/common/enums/CurrencyCodesEnum.java new file mode 100644 index 00000000..1f0225c8 --- /dev/null +++ b/src/main/java/com/ai/da/common/enums/CurrencyCodesEnum.java @@ -0,0 +1,40 @@ +package com.ai.da.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +public enum CurrencyCodesEnum { + + AUSTRALIAN_DOLLAR("AUD"), + BRAZILIAN_REAL("BRL"), + CANADIAN_DOLLAR("CAD"), + CHINESE_RENMENBI("CNY"), + CZECH_KORUNA("CZK"), + DANISH_KRONE("DKK"), + EURO("EUR"), + HONG_KONG_DOLLAR("HKD"), + HUNGARIAN_FORINT("HUF"), + ISRAELI_NEW_SHEKEL("ILS"), + JAPANESE_YEN("JPY"), + MALAYSIAN_RINGGIT("MYR"), + MEXICAN_PESO("MXN"), + NEW_TAIWAN_DOLLAR("TWD"), + NEW_ZEALAND_DOLLAR("NZD"), + NORWEGIAN_KRONE("NOK"), + PHILIPPINE_PESO("PHP"), + POLISH_ZLOTY("PLN"), + POUND_STERLING("GBP"), + RUSSIAN_RUBLE("RUB"), + SINGAPORE_DOLLAR("SGD"), + SWEDISH_KRONA("SEK"), + SWISS_FRANC("CHF"), + THAI_BAHT("THB"), + UNITED_STATES_DOLLAR("USD"); + + private String code; + + CurrencyCodesEnum(String code) { + this.code = code; + } +} 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 ede1e162..559ae38b 100644 --- a/src/main/java/com/ai/da/common/enums/OrderStatusEnum.java +++ b/src/main/java/com/ai/da/common/enums/OrderStatusEnum.java @@ -20,7 +20,7 @@ public enum OrderStatusEnum { /** * 已关闭 */ - CLOSED("超时已关闭"), + TIMEOUT_CLOSED("超时已关闭"), /** * 已取消 diff --git a/src/main/java/com/ai/da/common/enums/PayPalOrderStatusEnum.java b/src/main/java/com/ai/da/common/enums/PayPalOrderStatusEnum.java new file mode 100644 index 00000000..f35fd8f8 --- /dev/null +++ b/src/main/java/com/ai/da/common/enums/PayPalOrderStatusEnum.java @@ -0,0 +1,24 @@ +package com.ai.da.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum PayPalOrderStatusEnum { + + CREATED("CREATED"), + + SAVED("SAVED"), + + APPROVED("APPROVED"), + + VOIDED("VOIDED"), + + COMPLETED("COMPLETED"), + + PAYER_ACTION_REQUIRED("PAYER_ACTION_REQUIRED"); + + + private final String status; +} diff --git a/src/main/java/com/ai/da/common/enums/PayTypeEnum.java b/src/main/java/com/ai/da/common/enums/PayTypeEnum.java index cb2e89f8..70067578 100644 --- a/src/main/java/com/ai/da/common/enums/PayTypeEnum.java +++ b/src/main/java/com/ai/da/common/enums/PayTypeEnum.java @@ -11,11 +11,15 @@ public enum PayTypeEnum { */ WXPAY("微信"), - /** * 支付宝 */ - ALIPAY("支付宝"); + ALIPAY("支付宝"), + + /** + * PayPal + */ + PAYPAL("PayPal"); /** * 类型 diff --git a/src/main/java/com/ai/da/common/security/filter/AuthenticationFilter.java b/src/main/java/com/ai/da/common/security/filter/AuthenticationFilter.java index b669f4d0..8ae7a2a1 100644 --- a/src/main/java/com/ai/da/common/security/filter/AuthenticationFilter.java +++ b/src/main/java/com/ai/da/common/security/filter/AuthenticationFilter.java @@ -48,7 +48,7 @@ public class AuthenticationFilter extends OncePerRequestFilter { "/api/third/party/addNoLoginRequiredNew","/api/third/party/deleteNoLoginRequiredNew", "/api/third/party/existNoLoginRequired","/api/third/party/getRedirectUrl", // "/api/python/chatStream", - "/api/python/flush","/api/account/healthy","/api/ali-pay/trade/notify" + "/api/python/flush","/api/account/healthy","/api/ali-pay/trade/notify","/api/paypal/ipn/back" ); @Override 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 f95a3a10..64b0e81c 100644 --- a/src/main/java/com/ai/da/common/task/AliPayTask.java +++ b/src/main/java/com/ai/da/common/task/AliPayTask.java @@ -29,7 +29,7 @@ public class AliPayTask { log.info("orderConfirm 被执行......"); - List orderInfoList = orderInfoService.getNoPayOrderByDuration(1, PayTypeEnum.ALIPAY.getType()); + List orderInfoList = orderInfoService.getNoPayOrderByDuration(5, PayTypeEnum.ALIPAY.getType()); for (OrderInfo orderInfo : orderInfoList) { String orderNo = orderInfo.getOrderNo(); diff --git a/src/main/java/com/ai/da/common/utils/RedisUtil.java b/src/main/java/com/ai/da/common/utils/RedisUtil.java index 5121e883..92bc06aa 100644 --- a/src/main/java/com/ai/da/common/utils/RedisUtil.java +++ b/src/main/java/com/ai/da/common/utils/RedisUtil.java @@ -9,6 +9,7 @@ import org.springframework.util.CollectionUtils; import javax.annotation.Resource; import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; @Slf4j @Component @@ -17,6 +18,10 @@ public class RedisUtil { @Resource private RedisTemplate redisTemplate; + public Boolean hasKey(String key){ + return redisTemplate.hasKey(key); + } + //- - - - - - - - - - - - - - - - - - - - - ZSet类型 - - - - - - - - - - - - - - - - - - - - /** @@ -123,4 +128,14 @@ public class RedisUtil { public Long removeFromMap(String key, String hashKeys) { return redisTemplate.opsForHash().delete(key, hashKeys); } + + //- - - - - - - - - - - - - - - - - - - - - String类型 - - - - - - - - - - - - - - - - - - - - + public void addToString(String key, String value, Long expiresIn){ + redisTemplate.opsForValue().set(key,value,expiresIn, TimeUnit.SECONDS); + } + + public String getFromString(String key){ + return redisTemplate.opsForValue().get(key); + } + } diff --git a/src/main/java/com/ai/da/common/utils/paypalRequest/AuthenticationRequest.java b/src/main/java/com/ai/da/common/utils/paypalRequest/AuthenticationRequest.java new file mode 100644 index 00000000..eedda337 --- /dev/null +++ b/src/main/java/com/ai/da/common/utils/paypalRequest/AuthenticationRequest.java @@ -0,0 +1,30 @@ +package com.ai.da.common.utils.paypalRequest; + +import com.alibaba.fastjson.JSONObject; +import com.paypal.http.HttpRequest; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.HashMap; + +@Component +public class AuthenticationRequest extends HttpRequest { + + public AuthenticationRequest() { + super("/v1/oauth2/token", "POST", HashMap.class); + this.header("Content-Type", "application/x-www-form-urlencoded"); + body(); + } + + public AuthenticationRequest authorization(String clientId, String clientSecret) { + this.header(clientId, clientSecret); + return this; + } + + public AuthenticationRequest body() { + HashMap body = new HashMap<>(); + body.put("grant_type", "client_credentials"); + this.requestBody(body); + return this; + } +} diff --git a/src/main/java/com/ai/da/common/utils/paypalRequest/WebhookVerifyRequest.java b/src/main/java/com/ai/da/common/utils/paypalRequest/WebhookVerifyRequest.java new file mode 100644 index 00000000..957482d3 --- /dev/null +++ b/src/main/java/com/ai/da/common/utils/paypalRequest/WebhookVerifyRequest.java @@ -0,0 +1,26 @@ +package com.ai.da.common.utils.paypalRequest; + +import com.ai.da.model.dto.WebhookVerifyDTO; +import com.alibaba.fastjson.JSONObject; +import com.paypal.http.HttpRequest; +import com.paypal.orders.OrdersCreateRequest; + +import java.io.Serializable; +import java.util.HashMap; + +public class WebhookVerifyRequest extends HttpRequest { + public WebhookVerifyRequest() { + super("/v1/notifications/verify-webhook-signature", "POST", HashMap.class); + this.header("Content-Type", "application/json"); + } + + public WebhookVerifyRequest authorization(String authorization) { + this.header("Authorization", "Bearer " + String.valueOf(authorization)); + return this; + } + + public WebhookVerifyRequest requestBody(HashMap webhookVerify) { + super.requestBody(webhookVerify); + return this; + } +} diff --git a/src/main/java/com/ai/da/controller/AliPayController.java b/src/main/java/com/ai/da/controller/AliPayController.java index 5559a186..3878628a 100644 --- a/src/main/java/com/ai/da/controller/AliPayController.java +++ b/src/main/java/com/ai/da/controller/AliPayController.java @@ -1,20 +1,13 @@ package com.ai.da.controller; import com.ai.da.common.response.Response; -import com.ai.da.mapper.primary.entity.OrderInfo; import com.ai.da.service.AliPayService; -import com.ai.da.service.OrderInfoService; -import com.alipay.api.AlipayApiException; -import com.alipay.api.AlipayConstants; -import com.alipay.api.internal.util.AlipaySignature; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; -import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; -import java.math.BigDecimal; import java.util.Map; @CrossOrigin @@ -27,18 +20,9 @@ public class AliPayController { @Resource private AliPayService aliPayService; - @Resource - private Environment config; - - @Resource - private OrderInfoService orderInfoService; - @ApiOperation("统一收单下单并支付页面接口的调用") @PostMapping("/trade/page/pay/{productId}") public Response tradePagePay(@PathVariable Long productId,@RequestParam String returnUrl){ - - System.out.println(productId + " " + returnUrl); - log.info("统一收单下单并支付页面接口的调用"); //支付宝开放平台接受 request 请求对象后 // 会为开发者生成一个html 形式的 form表单,包含自动提交的脚本 @@ -51,80 +35,7 @@ public class AliPayController { @ApiOperation("支付通知") @PostMapping("/trade/notify") public String tradeNotify(@RequestParam Map params){ - - log.info("支付通知正在执行"); - log.info("通知参数 ===> {}", params); - - String result = "failure"; - - try { - //异步通知验签 - boolean signVerified = AlipaySignature.rsaCheckV1( - params, - config.getProperty("alipay.alipay-public-key"), - AlipayConstants.CHARSET_UTF8, - AlipayConstants.SIGN_TYPE_RSA2); //调用SDK验证签名 - - if(!signVerified){ - //验签失败则记录异常日志,并在response中返回failure. - log.error("支付成功异步通知验签失败!"); - return result; - } - - // 验签成功后 - log.info("支付成功异步通知验签成功!"); - - //按照支付结果异步通知中的描述,对支付结果中的业务内容进行二次校验, - //1 商户需要验证该通知数据中的 out_trade_no 是否为商户系统中创建的订单号 - String outTradeNo = params.get("out_trade_no"); - OrderInfo order = orderInfoService.getOrderByOrderNo(outTradeNo); - if(order == null){ - log.error("订单不存在"); - return result; - } - - //2 判断 total_amount 是否确实为该订单的实际金额(即商户订单创建时的金额) - String totalAmount = params.get("total_amount"); - int totalAmountInt = new BigDecimal(totalAmount).multiply(new BigDecimal("100")).intValue(); - int totalFeeInt = order.getTotalFee().intValue(); - if(totalAmountInt != totalFeeInt){ - log.error("金额校验失败"); - return result; - } - - //3 校验通知中的 seller_id(或者 seller_email) 是否为 out_trade_no 这笔单据的对应的操作方 - String sellerId = params.get("seller_id"); - String sellerIdProperty = config.getProperty("alipay.seller-id"); - if(!sellerId.equals(sellerIdProperty)){ - log.error("商家pid校验失败"); - return result; - } - - //4 验证 app_id 是否为该商户本身 - String appId = params.get("app_id"); - String appIdProperty = config.getProperty("alipay.app-id"); - if(!appId.equals(appIdProperty)){ - log.error("appid校验失败"); - return result; - } - - //在支付宝的业务通知中,只有交易通知状态为 TRADE_SUCCESS时, - // 支付宝才会认定为买家付款成功。 - String tradeStatus = params.get("trade_status"); - if(!"TRADE_SUCCESS".equals(tradeStatus)){ - log.error("支付未成功"); - return result; - } - - //处理业务 修改订单状态 记录支付日志 - aliPayService.processOrder(params); - - //校验成功后在response中返回success并继续商户自身业务处理,校验失败返回failure - result = "success"; - } catch (AlipayApiException e) { - e.printStackTrace(); - } - return result; + return aliPayService.tradeNotify(params); } /** @@ -135,7 +46,6 @@ public class AliPayController { @ApiOperation("用户取消订单") @PostMapping("/trade/close/{orderNo}") public Response cancel(@PathVariable String orderNo){ - log.info("取消订单"); aliPayService.cancelOrder(orderNo); return Response.success("订单已取消"); @@ -149,9 +59,7 @@ public class AliPayController { @ApiOperation("查询订单:测试订单状态用") @GetMapping("/trade/query/{orderNo}") public Response queryOrder(@PathVariable String orderNo) { - log.info("查询订单"); - String result = aliPayService.queryOrder(orderNo); return Response.success(result); @@ -166,7 +74,6 @@ public class AliPayController { @ApiOperation("申请退款") @PostMapping("/trade/refund/{orderNo}/{reason}") public Response refunds(@PathVariable String orderNo, @PathVariable String reason){ - log.info("申请退款"); aliPayService.refund(orderNo, reason); return Response.success(); @@ -180,10 +87,8 @@ public class AliPayController { */ @ApiOperation("查询退款:测试用") @GetMapping("/trade/fastpay/refund/{orderNo}") - public Response queryRefund(@PathVariable String orderNo) throws Exception { - + public Response queryRefund(@PathVariable String orderNo) { log.info("查询退款"); - String result = aliPayService.queryRefund(orderNo); return Response.success(result); } diff --git a/src/main/java/com/ai/da/controller/OrderInfoController.java b/src/main/java/com/ai/da/controller/OrderInfoController.java index 63e3463c..d5f8dfa2 100644 --- a/src/main/java/com/ai/da/controller/OrderInfoController.java +++ b/src/main/java/com/ai/da/controller/OrderInfoController.java @@ -1,5 +1,6 @@ package com.ai.da.controller; +import com.ai.da.common.context.UserContext; import com.ai.da.common.enums.OrderStatusEnum; import com.ai.da.common.response.Response; import com.ai.da.mapper.primary.entity.OrderInfo; @@ -23,9 +24,9 @@ public class OrderInfoController { @ApiOperation("订单列表") @GetMapping("/list") public Response> list(){ - - List list = orderInfoService.listOrderByCreateTimeDesc(); - return Response.success(list); + List orderByAccountId = orderInfoService.getOrderByAccountId(UserContext.getUserHolder().getId()); +// List list = orderInfoService.listOrderByCreateTimeDesc(); + return Response.success(orderByAccountId); } /** @@ -36,15 +37,11 @@ public class OrderInfoController { @ApiOperation("查询本地订单状态") @GetMapping("/query-order-status/{orderNo}") public Response queryOrderStatus(@PathVariable String orderNo){ - String orderStatus = orderInfoService.getOrderStatus(orderNo); if(OrderStatusEnum.SUCCESS.getType().equals(orderStatus)){ return Response.success("支付成功"); //支付成功 } - return Response.success(101,"支付中......"); } - - } diff --git a/src/main/java/com/ai/da/controller/PayPalCheckoutController.java b/src/main/java/com/ai/da/controller/PayPalCheckoutController.java new file mode 100644 index 00000000..0e141e67 --- /dev/null +++ b/src/main/java/com/ai/da/controller/PayPalCheckoutController.java @@ -0,0 +1,80 @@ +package com.ai.da.controller; + +import com.ai.da.common.response.Response; +import com.ai.da.service.CallBackService; +import com.ai.da.service.PayPalCheckoutService; +import com.paypal.http.HttpResponse; +import com.paypal.http.exceptions.SerializeException; +import com.paypal.orders.Order; +import com.paypal.payments.Refund; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.HashMap; + +@RestController +@Api(tags = "PayPalCheckout接口") +@RequestMapping("/api/paypal") +public class PayPalCheckoutController { + + @Resource + private PayPalCheckoutService payPalCheckoutService; + + @Resource + private CallBackService callBackService; + + @ApiOperation(value = "创建订单") + @PostMapping(value = "/trade/{productId}") + public Response> createOrder(@PathVariable Long productId,@RequestParam String returnUrl) throws SerializeException { + HashMap approvalUrl = payPalCheckoutService.createOrder(productId,returnUrl); + return Response.success(approvalUrl); + } + + @ApiOperation(value = "ipn异步回调") + @PostMapping(value = "/ipn/back") + public Response callback(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + Boolean result = callBackService.doGet(request, response); + if (result){ + return Response.success(); + }else { + return Response.fail(500,"failure"); + } +// return payPalCheckoutService.callback(RequestToMapUtil.getParameterMap(request)); + } + + @ApiOperation(value = "查询指定订单") + @PostMapping(value = "/trade/query/{orderNo}") + public Response queryOrder(@PathVariable String orderNo) throws SerializeException { + String s = payPalCheckoutService.queryOrder(orderNo); + return Response.success(s); + } + + @ApiOperation("申请退款") + @PostMapping("/trade/refund/{orderNo}/{reason}") + public Response> refund(@PathVariable String orderNo, @PathVariable String reason) throws IOException { + Boolean response = payPalCheckoutService.refundOrder(orderNo,reason); + if (response){ + return Response.success(); + }else { + return Response.fail("Request for refund failed."); + } + + } + + @ApiOperation("执行扣款") + @PostMapping("/trade/capture/{orderNo}") + public Response captureOrder(@PathVariable String orderNo) throws IOException { + Order response = payPalCheckoutService.captureOrder(orderNo); + return Response.success(response); + } + + + +} + diff --git a/src/main/java/com/ai/da/mapper/primary/entity/OrderInfo.java b/src/main/java/com/ai/da/mapper/primary/entity/OrderInfo.java index a4668176..c3b0be10 100644 --- a/src/main/java/com/ai/da/mapper/primary/entity/OrderInfo.java +++ b/src/main/java/com/ai/da/mapper/primary/entity/OrderInfo.java @@ -11,11 +11,11 @@ public class OrderInfo extends BaseEntity{ private String orderNo;//商户订单编号 - private Long userId;//用户id + private Long accountId;//用户id private Long productId;//支付产品id - private Integer totalFee;//订单金额(分) + private Integer totalFee;//订单金额(元) private String codeUrl;//订单二维码连接 diff --git a/src/main/java/com/ai/da/mapper/primary/entity/RefundInfo.java b/src/main/java/com/ai/da/mapper/primary/entity/RefundInfo.java index ee07efc8..19719a98 100644 --- a/src/main/java/com/ai/da/mapper/primary/entity/RefundInfo.java +++ b/src/main/java/com/ai/da/mapper/primary/entity/RefundInfo.java @@ -11,7 +11,7 @@ public class RefundInfo extends BaseEntity{ private String refundNo;//退款单编号 - private String refundId;//支付系统退款单号 + private String refundId;//支付系统退款单号(微信) private Integer totalFee;//原订单金额(分) diff --git a/src/main/java/com/ai/da/model/dto/WebhookVerifyDTO.java b/src/main/java/com/ai/da/model/dto/WebhookVerifyDTO.java new file mode 100644 index 00000000..4bcbc352 --- /dev/null +++ b/src/main/java/com/ai/da/model/dto/WebhookVerifyDTO.java @@ -0,0 +1,35 @@ +package com.ai.da.model.dto; + +import com.alibaba.fastjson.JSONObject; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class WebhookVerifyDTO implements Serializable { + + private String transmission_id; + private String transmission_time; + private String cert_url; + private String auth_algo; + private String transmission_sig; + private String webhook_id; + private Object webhook_event; + +} + + + + + + + + + diff --git a/src/main/java/com/ai/da/service/AliPayService.java b/src/main/java/com/ai/da/service/AliPayService.java index 796126a2..fbfc86df 100644 --- a/src/main/java/com/ai/da/service/AliPayService.java +++ b/src/main/java/com/ai/da/service/AliPayService.java @@ -5,6 +5,8 @@ import java.util.Map; public interface AliPayService { String tradeCreate(Long productId,String returnUrl); + String tradeNotify(Map params); + void processOrder(Map params); void cancelOrder(String orderNo); diff --git a/src/main/java/com/ai/da/service/CallBackService.java b/src/main/java/com/ai/da/service/CallBackService.java new file mode 100644 index 00000000..39efb1eb --- /dev/null +++ b/src/main/java/com/ai/da/service/CallBackService.java @@ -0,0 +1,12 @@ +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/OrderInfoService.java b/src/main/java/com/ai/da/service/OrderInfoService.java index eaac6df3..4f4dff38 100644 --- a/src/main/java/com/ai/da/service/OrderInfoService.java +++ b/src/main/java/com/ai/da/service/OrderInfoService.java @@ -22,4 +22,8 @@ public interface OrderInfoService extends IService { List getNoPayOrderByDuration(int minutes, String paymentType); OrderInfo getOrderByOrderNo(String orderNo); + + List getOrderByAccountId(Long accountId); + + void updateOrderNoById(Long id, String orderNo); } diff --git a/src/main/java/com/ai/da/service/PayPalCheckoutService.java b/src/main/java/com/ai/da/service/PayPalCheckoutService.java new file mode 100644 index 00000000..8a79b489 --- /dev/null +++ b/src/main/java/com/ai/da/service/PayPalCheckoutService.java @@ -0,0 +1,29 @@ +package com.ai.da.service; + +import com.paypal.http.exceptions.SerializeException; +import com.paypal.orders.Order; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public interface PayPalCheckoutService { + + HashMap createOrder(Long productId,String returnUrl) throws SerializeException; + /** + * 回调 + * @param map + */ + String callback(@SuppressWarnings("rawtypes") Map map); + + String queryOrder(String orderNo) throws SerializeException; + + Order captureOrder(String orderId) throws IOException; + + Boolean refundOrder(String orderId, String reason) throws IOException; + + String getOAuth(); + + void processOrder(String orderId); +} + diff --git a/src/main/java/com/ai/da/service/PaymentInfoService.java b/src/main/java/com/ai/da/service/PaymentInfoService.java index aa1470e4..4a350dff 100644 --- a/src/main/java/com/ai/da/service/PaymentInfoService.java +++ b/src/main/java/com/ai/da/service/PaymentInfoService.java @@ -1,5 +1,7 @@ package com.ai.da.service; +import com.paypal.orders.Order; + import java.util.Map; public interface PaymentInfoService { @@ -7,4 +9,6 @@ public interface PaymentInfoService { void createPaymentInfo(String plainText); void createPaymentInfoForAliPay(Map params); + + void createPaymentInfoForPayPal(Order order); } diff --git a/src/main/java/com/ai/da/service/RefundInfoService.java b/src/main/java/com/ai/da/service/RefundInfoService.java index 3690c794..5cfeca4c 100644 --- a/src/main/java/com/ai/da/service/RefundInfoService.java +++ b/src/main/java/com/ai/da/service/RefundInfoService.java @@ -17,4 +17,6 @@ public interface RefundInfoService extends IService { RefundInfo createRefundByOrderNoForAliPay(String orderNo, String reason); void updateRefundForAliPay(String refundNo, String content, String refundStatus); + + void updateRefundForPayPal(Long id, String refundId, String content, String refundStatus); } diff --git a/src/main/java/com/ai/da/service/impl/AliPayServiceImpl.java b/src/main/java/com/ai/da/service/impl/AliPayServiceImpl.java index 38d67eac..1b2dc62d 100644 --- a/src/main/java/com/ai/da/service/impl/AliPayServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/AliPayServiceImpl.java @@ -1,5 +1,6 @@ package com.ai.da.service.impl; +import com.ai.da.common.config.exception.BusinessException; import com.ai.da.common.enums.AliPayTradeStateEnum; import com.ai.da.common.enums.OrderStatusEnum; import com.ai.da.common.enums.PayTypeEnum; @@ -12,6 +13,8 @@ import com.ai.da.service.RefundInfoService; import com.alibaba.fastjson.JSONObject; import com.alipay.api.AlipayApiException; import com.alipay.api.AlipayClient; +import com.alipay.api.AlipayConstants; +import com.alipay.api.internal.util.AlipaySignature; import com.alipay.api.request.*; import com.alipay.api.response.*; import com.google.gson.Gson; @@ -69,7 +72,7 @@ public class AliPayServiceImpl implements AliPayService { //组装当前业务方法的请求参数 JSONObject bizContent = new JSONObject(); bizContent.put("out_trade_no", orderInfo.getOrderNo()); - BigDecimal total = new BigDecimal(orderInfo.getTotalFee().toString()).divide(new BigDecimal("100")); + BigDecimal total = new BigDecimal(orderInfo.getTotalFee().toString()); bizContent.put("total_amount", total); bizContent.put("subject", orderInfo.getTitle()); bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY"); @@ -84,14 +87,93 @@ public class AliPayServiceImpl implements AliPayService { return response.getBody(); } else { log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg()); - throw new RuntimeException("创建支付交易失败"); + throw new BusinessException("Order creation failed"); } } catch (AlipayApiException e) { e.printStackTrace(); - throw new RuntimeException("创建支付交易失败"); + throw new BusinessException("Order creation failed"); } } + @Override + public String tradeNotify(Map params) { + log.info("支付通知正在执行"); + log.info("通知参数 ===> {}", params); + + String result = "failure"; + + try { + //异步通知验签 + boolean signVerified = AlipaySignature.rsaCheckV1( + params, + config.getProperty("alipay.alipay-public-key"), + AlipayConstants.CHARSET_UTF8, + AlipayConstants.SIGN_TYPE_RSA2); //调用SDK验证签名 + + if(!signVerified){ + //验签失败则记录异常日志,并在response中返回failure. + log.error("支付成功异步通知验签失败!"); + return result; + } + + // 验签成功后 + log.info("支付成功异步通知验签成功!"); + + //按照支付结果异步通知中的描述,对支付结果中的业务内容进行二次校验, + //1 商户需要验证该通知数据中的 out_trade_no 是否为商户系统中创建的订单号 + String outTradeNo = params.get("out_trade_no"); + OrderInfo order = orderInfoService.getOrderByOrderNo(outTradeNo); + if(order == null){ + log.error("订单不存在"); + return result; + } + + //2 判断 total_amount 是否确实为该订单的实际金额(即商户订单创建时的金额) + String totalAmount = params.get("total_amount"); + int totalAmountInt = new BigDecimal(totalAmount).intValue(); + int totalFeeInt = order.getTotalFee().intValue(); + if(totalAmountInt != totalFeeInt){ + log.error("金额校验失败"); + return result; + } + + //3 校验通知中的 seller_id(或者 seller_email) 是否为 out_trade_no 这笔单据的对应的操作方 + String sellerId = params.get("seller_id"); + String sellerIdProperty = config.getProperty("alipay.seller-id"); + if(!sellerId.equals(sellerIdProperty)){ + log.error("商家pid校验失败"); + return result; + } + + //4 验证 app_id 是否为该商户本身 + String appId = params.get("app_id"); + String appIdProperty = config.getProperty("alipay.app-id"); + if(!appId.equals(appIdProperty)){ + log.error("appid校验失败"); + return result; + } + + //在支付宝的业务通知中,只有交易通知状态为 TRADE_SUCCESS时, + // 支付宝才会认定为买家付款成功。 + String tradeStatus = params.get("trade_status"); + if(!"TRADE_SUCCESS".equals(tradeStatus)){ + log.error("支付未成功"); + return result; + } + + //处理业务 修改订单状态 记录支付日志 + processOrder(params); + + //校验成功后在response中返回success并继续商户自身业务处理,校验失败返回failure + result = "success"; + } catch (AlipayApiException e) { + e.printStackTrace(); + } + + return result; + } + + /** * 处理订单 * @param params @@ -112,26 +194,22 @@ public class AliPayServiceImpl implements AliPayService { // 成功获取则立即返回true,获取失败则立即返回false。不必一直等待锁的释放 if(lock.tryLock()) { try { - //处理重复通知 //接口调用的幂等性:无论接口被调用多少次,以下业务执行一次 String orderStatus = orderInfoService.getOrderStatus(orderNo); - if (!OrderStatusEnum.NOT_PAY.getType().equals(orderStatus)) { + // 当订单状态处于未支付或超时已关闭时,更新订单状态 + if (!OrderStatusEnum.NOT_PAY.getType().equals(orderStatus) || !OrderStatusEnum.TIMEOUT_CLOSED.getType().equals(orderStatus)) { return; } - //更新订单状态 orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.SUCCESS); - //记录支付日志 paymentInfoService.createPaymentInfoForAliPay(params); - } finally { //要主动释放锁 lock.unlock(); } } - } /** @@ -176,7 +254,7 @@ public class AliPayServiceImpl implements AliPayService { } catch (AlipayApiException e) { e.printStackTrace(); - throw new RuntimeException("查单接口的调用失败"); + throw new BusinessException("查单接口的调用失败"); } } @@ -198,7 +276,7 @@ public class AliPayServiceImpl implements AliPayService { if(result == null){ log.warn("核实订单未创建 ===> {}", orderNo); //更新本地订单状态 - orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.CLOSED); + orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.TIMEOUT_CLOSED); } //解析查单响应结果 @@ -209,20 +287,16 @@ public class AliPayServiceImpl implements AliPayService { String tradeStatus = (String)alipayTradeQueryResponse.get("trade_status"); if(AliPayTradeStateEnum.NOTPAY.getType().equals(tradeStatus)){ log.warn("核实订单未支付 ===> {}", orderNo); - //如果订单未支付,则调用关单接口关闭订单 this.closeOrder(orderNo); - // 并更新商户端订单状态 - orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.CLOSED); + orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.TIMEOUT_CLOSED); } if(AliPayTradeStateEnum.SUCCESS.getType().equals(tradeStatus)){ log.warn("核实订单已支付 ===> {}", orderNo); - //如果订单已支付,则更新商户端订单状态 orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.SUCCESS); - //并记录支付日志 paymentInfoService.createPaymentInfoForAliPay(alipayTradeQueryResponse); } @@ -252,8 +326,8 @@ public class AliPayServiceImpl implements AliPayService { } } catch (AlipayApiException e) { - e.printStackTrace(); - throw new RuntimeException("关单接口的调用失败"); + log.error("关单失败,原因 ===> {}",e.getMessage()); + throw new BusinessException("关单接口的调用失败"); } } @@ -268,17 +342,14 @@ public class AliPayServiceImpl implements AliPayService { try { log.info("调用退款API"); - //创建退款单 RefundInfo refundInfo = refundsInfoService.createRefundByOrderNoForAliPay(orderNo, reason); - //调用统一收单交易退款接口 AlipayTradeRefundRequest request = new AlipayTradeRefundRequest (); - //组装当前业务方法的请求参数 JSONObject bizContent = new JSONObject(); bizContent.put("out_trade_no", orderNo);//订单编号 - BigDecimal refund = new BigDecimal(refundInfo.getRefund().toString()).divide(new BigDecimal("100")); + BigDecimal refund = new BigDecimal(refundInfo.getRefund().toString()); //BigDecimal refund = new BigDecimal("2").divide(new BigDecimal("100")); bizContent.put("refund_amount", refund);//退款金额:不能大于支付金额 bizContent.put("refund_reason", reason);//退款原因(可选) @@ -290,10 +361,8 @@ public class AliPayServiceImpl implements AliPayService { if(response.isSuccess()){ log.info("调用成功,返回结果 ===> " + response.getBody()); - //更新订单状态 orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.REFUND_SUCCESS); - //更新退款单 refundsInfoService.updateRefundForAliPay( refundInfo.getRefundNo(), @@ -302,21 +371,17 @@ public class AliPayServiceImpl implements AliPayService { } else { log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg()); - //更新订单状态 orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.REFUND_ABNORMAL); - //更新退款单 refundsInfoService.updateRefundForAliPay( refundInfo.getRefundNo(), response.getBody(), AliPayTradeStateEnum.REFUND_ERROR.getType()); //退款失败 } - - } catch (AlipayApiException e) { e.printStackTrace(); - throw new RuntimeException("创建退款申请失败"); + throw new BusinessException("创建退款申请失败"); } } @@ -349,7 +414,7 @@ public class AliPayServiceImpl implements AliPayService { } catch (AlipayApiException e) { e.printStackTrace(); - throw new RuntimeException("查单接口的调用失败"); + throw new BusinessException("查单接口的调用失败"); } } @@ -363,7 +428,6 @@ public class AliPayServiceImpl implements AliPayService { public String queryBill(String billDate, String type) { try { - AlipayDataDataserviceBillDownloadurlQueryRequest request = new AlipayDataDataserviceBillDownloadurlQueryRequest(); JSONObject bizContent = new JSONObject(); bizContent.put("bill_type", type); @@ -383,13 +447,11 @@ public class AliPayServiceImpl implements AliPayService { return billDownloadUrl; } else { log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg()); - throw new RuntimeException("申请账单失败"); + throw new BusinessException("申请账单失败"); } - } catch (AlipayApiException e) { - e.printStackTrace(); - throw new RuntimeException("申请账单失败"); + log.error("申请账单失败,原因 ===> {}",e.getMessage()); + throw new BusinessException("申请账单失败"); } } - } diff --git a/src/main/java/com/ai/da/service/impl/CallBackServiceImpl.java b/src/main/java/com/ai/da/service/impl/CallBackServiceImpl.java new file mode 100644 index 00000000..7c5fd42f --- /dev/null +++ b/src/main/java/com/ai/da/service/impl/CallBackServiceImpl.java @@ -0,0 +1,159 @@ +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/OrderInfoServiceImpl.java b/src/main/java/com/ai/da/service/impl/OrderInfoServiceImpl.java index 8b7662f7..43447c30 100644 --- a/src/main/java/com/ai/da/service/impl/OrderInfoServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/OrderInfoServiceImpl.java @@ -1,12 +1,14 @@ package com.ai.da.service.impl; +import com.ai.da.common.context.UserContext; import com.ai.da.common.enums.OrderStatusEnum; import com.ai.da.common.utils.OrderNoUtils; import com.ai.da.mapper.primary.OrderInfoMapper; import com.ai.da.mapper.primary.ProductMapper; import com.ai.da.mapper.primary.entity.OrderInfo; import com.ai.da.mapper.primary.entity.Product; +import com.ai.da.model.vo.AuthPrincipalVo; import com.ai.da.service.OrderInfoService; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; @@ -25,9 +27,6 @@ public class OrderInfoServiceImpl extends ServiceImpl getOrderByAccountId(Long accountId){ + QueryWrapper qw = new QueryWrapper<>(); + qw.eq("account_id",accountId); + qw.orderByDesc("create_time"); + + return baseMapper.selectList(qw); + } + + public void updateOrderNoById(Long id, String orderNo){ + OrderInfo orderInfo = new OrderInfo(); + orderInfo.setId(id); + orderInfo.setOrderNo(orderNo); + + baseMapper.updateById(orderInfo); + } } diff --git a/src/main/java/com/ai/da/service/impl/PayPalCheckoutServiceImpl.java b/src/main/java/com/ai/da/service/impl/PayPalCheckoutServiceImpl.java new file mode 100644 index 00000000..f38a00a8 --- /dev/null +++ b/src/main/java/com/ai/da/service/impl/PayPalCheckoutServiceImpl.java @@ -0,0 +1,476 @@ +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; +import com.ai.da.mapper.primary.entity.OrderInfo; +import com.ai.da.mapper.primary.entity.RefundInfo; +import com.ai.da.service.OrderInfoService; +import com.ai.da.service.PayPalCheckoutService; +import com.ai.da.service.PaymentInfoService; +import com.ai.da.service.RefundInfoService; +import com.google.gson.Gson; +import com.paypal.http.HttpResponse; +import com.paypal.http.exceptions.SerializeException; +import com.paypal.http.serializer.Json; +import com.paypal.orders.*; +import com.paypal.payments.CapturesGetRequest; +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; + +import javax.annotation.Resource; +import java.io.IOException; +import java.util.*; + +import static com.ai.da.common.constant.PayPalCheckoutConstant.*; + +@Slf4j +@Service +//@RefreshScope +public class PayPalCheckoutServiceImpl implements PayPalCheckoutService { + + @Value("${paypal.receiver.email}") + private String receiverEmail; + + @Value("${paypal.client-id}") + private String clientId; + + @Value("${paypal.client-secret}") + private String clientSecret; + + @Resource + private PayPalClient payPalClient; + + @Resource + private OrderInfoService orderInfoService; + + @Resource + private PaymentInfoService paymentInfoService; + + @Resource + private RefundInfoService refundsInfoService; + + @Resource + private RedisUtil redisUtil; + + /** + * 创建订单的方法 + */ + @Override + public HashMap createOrder(Long productId,String returnUrl) throws SerializeException { + // 生成订单 + log.info("生成订单"); + OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId, PayTypeEnum.PAYPAL.getType()); + + OrdersCreateRequest request = new OrdersCreateRequest(); + request.header("prefer","return=representation"); + request.requestBody(buildRequestBody(String.valueOf(orderInfo.getTotalFee()),returnUrl)); + HttpResponse response = null; + try { + response = payPalClient.client(MODE, clientId, clientSecret).execute(request); + } catch (Exception e) { + log.error("调用paypal订单创建失败,失败原因 ===> {}", e.getMessage()); + throw new BusinessException("Order creation failed"); + } + + String approve = ""; + assert response != null; + 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); + } + + String orderId = response.result().id(); + orderInfoService.updateOrderNoById(orderInfo.getId(), orderId); + + + HashMap returnData = new HashMap<>(); + returnData.put("approve",approve); + returnData.put("orderNo",orderId); + + // 需要返回地址和订单id + return returnData; + } + + /** + * 生成订单主体信息 + */ + private OrderRequest buildRequestBody(String price, String returnUrl) { + OrderRequest orderRequest = new OrderRequest(); + orderRequest.checkoutPaymentIntent(CAPTURE); + + ApplicationContext applicationContext = new ApplicationContext() + .brandName(BRANDNAME) + .landingPage(LANDINGPAGE) + .cancelUrl("https://www.example.com").returnUrl(returnUrl) + .userAction(USERACTION) + .shippingPreference(SHIPPINGPREFERENCE); + orderRequest.applicationContext(applicationContext); + + List purchaseUnitRequests = new ArrayList(); + + PurchaseUnitRequest purchaseUnitRequest = new PurchaseUnitRequest() + .amountWithBreakdown(new AmountWithBreakdown() + .amountBreakdown(new AmountBreakdown()) + .currencyCode(CurrencyCodesEnum.HONG_KONG_DOLLAR.getCode()) + .value(price)); + purchaseUnitRequests.add(purchaseUnitRequest); + orderRequest.purchaseUnits(purchaseUnitRequests); + return orderRequest; + } + + @Override + public String callback(@SuppressWarnings("rawtypes") Map map) { + + log.info("paypal支付通知正在执行"); + log.info("通知参数 ===> {}", 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(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"; + } + + /** + * 查询订单信息 + * @param orderNo + * @return + * @throws SerializeException + */ + public String queryOrder(String orderNo) throws SerializeException { + OrdersGetRequest request = new OrdersGetRequest(orderNo); + + HttpResponse response = null; + try { + response = payPalClient.client(MODE, clientId, clientSecret).execute(request); + } catch (Exception e) { + log.error("paypal订单查询失败,失败原因 ===> {}",e.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 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 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); + return null; + } + + /** + * 用户授权支付成功,进行扣款操作 + */ + public Order captureOrder(String orderId) { + OrdersCaptureRequest request = new OrdersCaptureRequest(orderId); + request.requestBody(new OrderRequest()); + PayPalClient payPalClient = new PayPalClient(); + HttpResponse response ; + + try { + response = payPalClient.client(MODE, clientId, clientSecret).execute(request); + } catch (Exception e) { + log.error("调用paypal扣款失败,失败原因 ===> {}", e.getMessage() ); + throw new BusinessException("Order deduction failed."); + } + 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 jsonString = JSON.toJSONString(response.result()); + String json = null; + try { + json = new JSONObject(new Json().serialize(response.result())).toString(4); + } catch (SerializeException e) { + log.warn("response序列化出错,具体信息 ===> {}",e.getMessage()); + } + log.info("captureOrder response body: {}", json); +// return Convert.toStr(new Json().serialize(response.result())); + return response.result(); + } + + /** + * 查询扣款 + */ + public String queryCapture(String orderNo) throws IOException { + CapturesGetRequest request = new CapturesGetRequest("扣款id, CaptureOrder生成"); + + HttpResponse response = payPalClient.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 (com.paypal.payments.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)); + return null; + } + + + /** + * 申请退款 + */ + public Boolean refundOrder(String orderId,String reason) throws IOException { + + RefundInfo refundByOrderNo = refundsInfoService.createRefundByOrderNo(orderId, reason); + + OrdersGetRequest ordersGetRequest = new OrdersGetRequest(orderId); + PayPalClient payPalClient = new PayPalClient(); + HttpResponse ordersGetResponse = null; + ordersGetRequest.authorization("Bearer " + getOAuth()); + boolean result ; + try { + ordersGetResponse = payPalClient.client(MODE, clientId, clientSecret).execute(ordersGetRequest); + } catch (Exception e) { + log.error("调用paypal订单查询失败,失败原因 ===> {}", e.getMessage()); + throw new BusinessException("Order query failed"); + } + String captureId = ordersGetResponse.result().purchaseUnits().get(0).payments().captures().get(0).id(); + CapturesRefundRequest request = new CapturesRefundRequest(captureId); + request.authorization("Bearer " + getOAuth()); + request.prefer("return=representation"); + + OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(orderId); + request.requestBody(buildRefundRequestBody(String.valueOf(orderInfo.getTotalFee()),reason)); + HttpResponse response = null; + try { + response = payPalClient.client(MODE, clientId, clientSecret).execute(request); + } catch (IOException e) { + log.error("调用paypal退款申请失败,失败原因 {}", e.getMessage()); + throw new BusinessException("Request for refund failed"); + } + + log.info("Status Code = {}, Status = {}, RefundID = {}", response.statusCode(), response.result().status(), response.result().id()); + if("COMPLETED".equals(response.result().status())) { + //进行数据库操作,修改状态为已退款(配合回调和退款查询确定退款成功) + + //更新订单状态 + orderInfoService.updateStatusByOrderNo(orderId, OrderStatusEnum.REFUND_SUCCESS); + + refundsInfoService.updateRefundForPayPal( + refundByOrderNo.getId(), + response.result().id(), + new Gson().toJson(response.result(), com.paypal.payments.Refund.class), + AliPayTradeStateEnum.REFUND_SUCCESS.getType()); //退款成功 + log.info("退款成功"); + result = Boolean.TRUE; + }else { + //更新订单状态 + orderInfoService.updateStatusByOrderNo(orderId, OrderStatusEnum.REFUND_ABNORMAL); + + //更新退款单 + refundsInfoService.updateRefundForPayPal( + refundByOrderNo.getId(), + response.result().id(), + new Gson().toJson(response.result(), com.paypal.payments.Refund.class), + AliPayTradeStateEnum.REFUND_ERROR.getType()); //退款失败 + result = Boolean.FALSE; + } + 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 result; + } + + public RefundRequest buildRefundRequestBody(String price, String reason) { + RefundRequest refundRequest = new RefundRequest(); + com.paypal.payments.Money money = new com.paypal.payments.Money(); + money.currencyCode(CurrencyCodesEnum.HONG_KONG_DOLLAR.getCode()); + money.value(price); + refundRequest.amount(money); +// refundRequest.invoiceId("P2020052514440001"); + refundRequest.noteToPayer(reason); + try { + log.info("refund order body : {}", Convert.toStr(new Json().serialize(refundRequest))); + } catch (SerializeException e) { + throw new RuntimeException(e); + } + + return refundRequest; + } + + /** + * 查询退款 + */ + public String queryRefund(String orderNo) throws IOException { + RefundsGetRequest request = new RefundsGetRequest("退款id RefundOrder生成"); + HttpResponse response = payPalClient.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 (com.paypal.payments.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)); + return null; + } + + public String getOAuth(){ + // 1、判断缓存区是否有该token + Boolean hasKey = redisUtil.hasKey(PAYPAL_TOKEN_KEY); + if (hasKey){ + return redisUtil.getFromString(PAYPAL_TOKEN_KEY); +// return "A21AAKnpozur9r9omqQ2ge5aXHBBdEERi5F8FIgNYOjhhO2N7rjmkz2irh2lScpBO3s3Cqukw3eZkpYZ4YWE7rIacjv7MHmow"; + } + + // 2、无或者过期,重新获取token,返回 + AuthenticationRequest authenticationRequest = new AuthenticationRequest(); + authenticationRequest.authorization(clientId,clientSecret); + try { + HttpResponse authResult = payPalClient.client(MODE, clientId, clientSecret).execute(authenticationRequest); + String accessToken = authResult.result().get("access_token").toString(); + long expiresIn = Long.parseLong(authResult.result().get("expires_in").toString()); + // 3、存redis + redisUtil.addToString(PAYPAL_TOKEN_KEY,accessToken,expiresIn); + return accessToken; + } catch (IOException e) { + log.error("获取paypal token失败,失败原因 ===> {}", e.getMessage()); + throw new BusinessException(e); + } + } + + // 处理当前订单 + public void processOrder(String orderId){ + // 1、确定当前订单是否已经被扣款 + OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(orderId); + if (orderInfo.getOrderStatus().equals(OrderStatusEnum.SUCCESS.getType())){ + // 直接返回 + return ; + } + // 发起扣款请求 + Order capturedOrder = captureOrder(orderId); + // 业务处理 + if (PayPalOrderStatusEnum.COMPLETED.getStatus().equals(capturedOrder.status())){ + //更新订单状态 + orderInfoService.updateStatusByOrderNo(orderId, OrderStatusEnum.SUCCESS); + //记录支付日志 + paymentInfoService.createPaymentInfoForPayPal(capturedOrder); + } + } +} + + diff --git a/src/main/java/com/ai/da/service/impl/PaymentInfoServiceImpl.java b/src/main/java/com/ai/da/service/impl/PaymentInfoServiceImpl.java index 31cca80a..367dd376 100644 --- a/src/main/java/com/ai/da/service/impl/PaymentInfoServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/PaymentInfoServiceImpl.java @@ -6,6 +6,7 @@ import com.ai.da.mapper.primary.entity.PaymentInfo; import com.ai.da.service.PaymentInfoService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.google.gson.Gson; +import com.paypal.orders.Order; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -70,8 +71,8 @@ public class PaymentInfoServiceImpl extends ServiceImpl queryWrapper = new QueryWrapper<>(); +// queryWrapper.eq("order_no", orderNo); + + //设置要修改的字段 + RefundInfo refundInfo = new RefundInfo(); + refundInfo.setId(id); + refundInfo.setRefundNo(null); + refundInfo.setRefundId(refundId); + refundInfo.setRefundStatus(refundStatus);//退款状态 + refundInfo.setContentReturn(content);//将全部响应结果存入数据库的content字段 + + //更新退款单 +// baseMapper.update(refundInfo, queryWrapper); + baseMapper.updateById(refundInfo); + } } diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index 2d98c907..3bb0bef3 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -21,7 +21,7 @@ spring.security.jwtExpiration=8640000000 #spring security权限设置 认证了token还要认证权限 不然报错Full authentication is required to access this resource spring.security.ignorePaths=/,/favicon.ico,/doc.html,/webjars/**,/swagger-resources,/v2/api-docs,\ /api/account/**,/api/element/**,/api/python/**,/api/design/**,/api/history/**,/api/library/**,/api/third/party/**,/api/generate/**,/api/workspace/**,/api/classification/**,\ - /api/product/**,/api/ali-pay/**,/api/order-info/** + /api/product/**,/api/ali-pay/**,/api/order-info/**,/api/paypal/** spring.security.authApi=/auth/login diff --git a/src/main/resources/paypal-sandbox.properties b/src/main/resources/paypal-sandbox.properties new file mode 100644 index 00000000..e839eebf --- /dev/null +++ b/src/main/resources/paypal-sandbox.properties @@ -0,0 +1,5 @@ +paypal.client-id=ATbaebYi7-GXWRWJqwRLYMzKEbwjh4BFRqD4Y13i4lZq0rplWIM_IpPrtPKpdkAt_KrPXd6IJTwsDqa5 + +paypal.client-secret=EHWWJqGmmbfjLXqCUpGrvxRYBPPtWvA3hR5ZaAyHlGSVJiHoQPS8skbNaJ9h39VObnchUbgiY2pPu__s + +paypal.receiver.email=sb-ukxfk29608925@business.example.com \ No newline at end of file