|
|
@@ -0,0 +1,270 @@
|
|
|
+package com.cyksj.service.paypal;
|
|
|
+
|
|
|
+import cn.hutool.json.JSONObject;
|
|
|
+import com.cyksj.common.exception.BusinessRuntimeException;
|
|
|
+import com.cyksj.common.task.GlobalThreadPoolTaskExecutor;
|
|
|
+import com.cyksj.common.util.Jsons;
|
|
|
+import com.cyksj.enums.GatewayApiCode;
|
|
|
+import com.cyksj.enums.GatewayResponse;
|
|
|
+import com.cyksj.mapper.GoodsDonSkuMapper;
|
|
|
+import com.cyksj.model.entity.GoodsDonSku;
|
|
|
+import com.cyksj.model.entity.OrderDon;
|
|
|
+import com.cyksj.model.entity.PaypalPayConfig;
|
|
|
+import com.cyksj.redis.RedisService;
|
|
|
+import com.cyksj.service.order.OrderDonService;
|
|
|
+import com.paypal.api.payments.Amount;
|
|
|
+import com.paypal.api.payments.Sale;
|
|
|
+import com.paypal.base.rest.APIContext;
|
|
|
+import com.paypal.base.rest.OAuthTokenCredential;
|
|
|
+import com.paypal.base.rest.PayPalRESTException;
|
|
|
+import com.paypal.core.PayPalHttpClient;
|
|
|
+import com.paypal.http.HttpResponse;
|
|
|
+import com.paypal.http.serializer.Json;
|
|
|
+import com.paypal.orders.*;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import java.io.IOException;
|
|
|
+import java.io.InputStream;
|
|
|
+import java.io.OutputStreamWriter;
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.math.RoundingMode;
|
|
|
+import java.net.HttpURLConnection;
|
|
|
+import java.net.URL;
|
|
|
+import java.util.*;
|
|
|
+
|
|
|
+/**
|
|
|
+ * @author zwhui
|
|
|
+ * @date 2025/2/14 11:07
|
|
|
+ */
|
|
|
+@Service
|
|
|
+@Slf4j
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class PaypalV2Service {
|
|
|
+
|
|
|
+ private static final GlobalThreadPoolTaskExecutor THREAD_POOL = GlobalThreadPoolTaskExecutor.getInstance();
|
|
|
+
|
|
|
+ private final PaypalPayConfigService paypalPayConfigService;
|
|
|
+
|
|
|
+ private final OrderDonService orderDonService;
|
|
|
+
|
|
|
+ private final RedisService redisService;
|
|
|
+
|
|
|
+ private final GoodsDonSkuMapper skuMapper;
|
|
|
+ private static final String SANDBOX_URL = "https://api-m.sandbox.paypal.com";
|
|
|
+
|
|
|
+ private static final String LIVE_URL = "https://api-m.paypal.com";
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * LOGIN。当客户单击PayPal Checkout时,客户将被重定向到页面以登录PayPal并批准付款。
|
|
|
+ * BILLING。当客户单击PayPal Checkout时,客户将被重定向到一个页面,以输入信用卡或借记卡以及完成购买所需的其他相关账单信息
|
|
|
+ * NO_PREFERENCE。当客户单击“ PayPal Checkout”时,将根据其先前的交互方式将其重定向到页面以登录PayPal并批准付款,或重定向至页面以输入信用卡或借记卡以及完成购买所需的其他相关账单信息使用PayPal。
|
|
|
+ * 默认值:NO_PREFERENCE
|
|
|
+ */
|
|
|
+ public static final String LANDINGPAGE = "BILLING";
|
|
|
+ /**
|
|
|
+ * CONTINUE。将客户重定向到PayPal付款页面后,将出现“ 继续”按钮。当结帐流程启动时最终金额未知时,请使用此选项,并且您想将客户重定向到商家页面而不处理付款。
|
|
|
+ * PAY_NOW。将客户重定向到PayPal付款页面后,出现“ 立即付款”按钮。当启动结帐时知道最终金额并且您要在客户单击“ 立即付款”时立即处理付款时,请使用此选项。
|
|
|
+ */
|
|
|
+ public static final String USERACTION = "PAY_NOW";
|
|
|
+
|
|
|
+
|
|
|
+ public String createPayment(Long orderId, String successUrl) {
|
|
|
+ OrderDon orderDon = orderDonService.getById(orderId);
|
|
|
+ if (orderDon.getGoodsId() == 26) {
|
|
|
+ GoodsDonSku sku = skuMapper.selectById(orderDon.getSkuId());
|
|
|
+ if (!sku.getIsMirror()) {
|
|
|
+ throw BusinessRuntimeException.getInstance("订单异常..");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //paypal 不能购买奈飞月付账号 24-1-23
|
|
|
+ if (orderDon.getSkuId() != null && orderDon.getSkuId() == 4) {
|
|
|
+ throw BusinessRuntimeException.getInstance("暂不支持paypal购买该规格车票..");
|
|
|
+ }
|
|
|
+ redisService.set(RedisService.key.ABROAD_SUCCESS_URL.getName() + orderId,successUrl,RedisService.key.ABROAD_SUCCESS_URL.getTimeout());
|
|
|
+ BigDecimal money = orderDon.getMoney().divide(new BigDecimal(7),2,RoundingMode.HALF_DOWN);
|
|
|
+ orderDon.setAbroadMoney(money);
|
|
|
+ orderDon.setType(OrderDon.Type.PAYPAL);
|
|
|
+ orderDonService.updateById(orderDon);
|
|
|
+ if (OrderDon.Status.hasPayment.equals(orderDon.getStatus())) {
|
|
|
+ throw new BusinessRuntimeException(GatewayApiCode.METADATA_SYSTEM_ERROR.getCode(),"该订单已支付");
|
|
|
+ }
|
|
|
+ PaypalPayConfig paypalPayConfig = paypalPayConfigService.getById(1);
|
|
|
+ PayPalClient payPalClient = new PayPalClient();
|
|
|
+ // 设置环境沙盒或生产
|
|
|
+ PayPalHttpClient client = payPalClient.client(paypalPayConfig.getMode(), paypalPayConfig.getClientId(), paypalPayConfig.getClientSecret());
|
|
|
+
|
|
|
+ //回调参数(支付成功success路径所携带的参数)
|
|
|
+ Map<String, String> sParaTemp = new HashMap<String, String>();
|
|
|
+
|
|
|
+ sParaTemp.put("orderId", orderId.toString());
|
|
|
+
|
|
|
+ String returnUrl = "https://" + paypalPayConfig.getHost() + "/8081/api/applets/order/paypalV2/notify";
|
|
|
+
|
|
|
+ // 配置请求参数
|
|
|
+ OrderRequest orderRequest = new OrderRequest();
|
|
|
+ orderRequest.checkoutPaymentIntent("CAPTURE");
|
|
|
+ List<PurchaseUnitRequest> purchaseUnits = new ArrayList<>();
|
|
|
+ BigDecimal totalMoney = money.divide(new BigDecimal(100), 2, RoundingMode.HALF_DOWN);
|
|
|
+ purchaseUnits.add(new PurchaseUnitRequest().referenceId(orderId.toString()).amountWithBreakdown(new AmountWithBreakdown().currencyCode("USD").value(totalMoney.toString())));
|
|
|
+ orderRequest.purchaseUnits(purchaseUnits);
|
|
|
+ orderRequest.applicationContext(new ApplicationContext()
|
|
|
+ .landingPage(LANDINGPAGE)
|
|
|
+ .userAction(USERACTION)
|
|
|
+ .returnUrl(returnUrl)
|
|
|
+ .cancelUrl(paypalPayConfig.getCancelUrl()));
|
|
|
+ OrdersCreateRequest request = new OrdersCreateRequest().requestBody(orderRequest);
|
|
|
+
|
|
|
+ HttpResponse<Order> response;
|
|
|
+ try {
|
|
|
+ response = client.execute(request);
|
|
|
+ Order order = response.result();
|
|
|
+ String payHref = null;
|
|
|
+ String status = order.status();
|
|
|
+ if (status.equals("CREATED")) {
|
|
|
+ List<LinkDescription> links = order.links();
|
|
|
+ for (LinkDescription linkDescription : links) {
|
|
|
+ if (linkDescription.rel().equals("approve")) {
|
|
|
+ payHref = linkDescription.href();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return payHref;
|
|
|
+ } catch (IOException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String paramsConvertUrl(Map<String, String> params) {
|
|
|
+ StringBuilder urlParams = new StringBuilder("?");
|
|
|
+ Set<Map.Entry<String, String>> entries = params.entrySet();
|
|
|
+ for (Map.Entry<String, String> entry : params.entrySet()) {
|
|
|
+ urlParams.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
|
|
|
+ }
|
|
|
+ String urlParamsStr = urlParams.toString();
|
|
|
+ return urlParamsStr.substring(0, urlParamsStr.length()-1);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static void main(String[] args) {
|
|
|
+ String payUrl = "";
|
|
|
+ PayPalClient payPalClient = new PayPalClient();
|
|
|
+ // 设置环境沙盒或生产
|
|
|
+ PayPalHttpClient client = payPalClient.client("sandbox", "Ad4Jbuus51awFCDQKRDkLHpLBfmCqFvCPYLTZEmQ8nEVQWV1jBuxNE7KfWqc8HOKgMCatrTLJFrTOqkb", "EKxtALS9isrGu7PcTLWjI3lGcPDMMkN8qwXIpW99oylFDfAf38nUB6hd0mbeVHv291BDfYtsB36IQRNN");
|
|
|
+
|
|
|
+ //回调参数(支付成功success路径所携带的参数)
|
|
|
+ Map<String, String> sParaTemp = new HashMap<String, String>();
|
|
|
+
|
|
|
+ sParaTemp.put("orderId", "111");
|
|
|
+ String returnUrl = "https://inside.yicanggongyi.com/8081/api/applets/order/paypal/notify";
|
|
|
+ String url = returnUrl + paramsConvertUrl(sParaTemp);
|
|
|
+
|
|
|
+ System.out.println("回调链接:"+url);
|
|
|
+
|
|
|
+ // 配置请求参数
|
|
|
+ OrderRequest orderRequest = new OrderRequest();
|
|
|
+ orderRequest.checkoutPaymentIntent("CAPTURE");
|
|
|
+ List<PurchaseUnitRequest> purchaseUnits = new ArrayList<>();
|
|
|
+ purchaseUnits.add(new PurchaseUnitRequest().amountWithBreakdown(new AmountWithBreakdown().currencyCode("USD").value("100")));
|
|
|
+ orderRequest.purchaseUnits(purchaseUnits);
|
|
|
+ orderRequest.applicationContext(new ApplicationContext().landingPage(LANDINGPAGE)
|
|
|
+ .userAction(USERACTION).returnUrl(url).cancelUrl("https://inside.yicanggongyi.com/yinhe/web/mine?modules=ticket"));
|
|
|
+ OrdersCreateRequest request = new OrdersCreateRequest().requestBody(orderRequest);
|
|
|
+
|
|
|
+ HttpResponse<Order> response;
|
|
|
+ try {
|
|
|
+ response = client.execute(request);
|
|
|
+ Order order = response.result();
|
|
|
+ String payHref = null;
|
|
|
+ String status = order.status();
|
|
|
+ if (status.equals("CREATED")) {
|
|
|
+ List<LinkDescription> links = order.links();
|
|
|
+ for (LinkDescription linkDescription : links) {
|
|
|
+ if (linkDescription.rel().equals("approve")) {
|
|
|
+ payHref = linkDescription.href();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ payUrl = payHref;
|
|
|
+ } catch (IOException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ System.out.println(payUrl);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public String successPayment(String token, String payerId,PaypalPayConfig paypalPayConfig) throws Exception {
|
|
|
+ log.info("paypalV2回调 token:{},payerId:{}", token, payerId);
|
|
|
+ //捕获订单 进行支付
|
|
|
+ HttpResponse<Order> response = null;
|
|
|
+ OrdersCaptureRequest ordersCaptureRequest = new OrdersCaptureRequest(token);
|
|
|
+ ordersCaptureRequest.requestBody(new OrderRequest());
|
|
|
+
|
|
|
+ PayPalClient payPalClient = new PayPalClient();
|
|
|
+ try {
|
|
|
+ response = payPalClient.client(paypalPayConfig.getMode(), paypalPayConfig.getClientId(), paypalPayConfig.getClientSecret()).execute(ordersCaptureRequest);
|
|
|
+
|
|
|
+ for (PurchaseUnit purchaseUnit : response.result().purchaseUnits()) {
|
|
|
+ for (Capture capture : purchaseUnit.payments().captures()) {
|
|
|
+ if ("COMPLETED".equals(capture.status())) {
|
|
|
+ //支付成功
|
|
|
+ // 订单号
|
|
|
+ String saleId = capture.id();
|
|
|
+ String fee = capture.sellerReceivableBreakdown().paypalFee().value();
|
|
|
+ String orderId = purchaseUnit.referenceId();
|
|
|
+ String json = Jsons.toJson(response.result());
|
|
|
+ log.info("captureOrder response body: {}", json);
|
|
|
+ //更新订单支付状态
|
|
|
+ OrderDon orderDon = orderDonService.getById(orderId);
|
|
|
+ orderDon.setTransactionId(saleId);
|
|
|
+ orderDon.setPayTime(new Date());
|
|
|
+ orderDon.setAbroadTransactionFee(new BigDecimal(fee).multiply(BigDecimal.valueOf(100)));
|
|
|
+ orderDonService.orderNotify(orderDon);
|
|
|
+ log.info("支付成功订单:"+orderId+",支付交易号:"+saleId);
|
|
|
+
|
|
|
+ THREAD_POOL.execute(()->{
|
|
|
+ try {
|
|
|
+ APIContext apiContext = this.getAPIContext(paypalPayConfig.getClientId(), paypalPayConfig.getClientSecret(), paypalPayConfig.getMode());
|
|
|
+ URL url = new URL("live".equals(paypalPayConfig.getMode()) ? LIVE_URL : SANDBOX_URL + "/v1/shipping/trackers-batch");
|
|
|
+ HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
|
|
|
+ httpConn.setRequestMethod("POST");
|
|
|
+ httpConn.setRequestProperty("Content-Type", "application/json");
|
|
|
+ httpConn.setRequestProperty("Authorization", apiContext.getAccessToken());
|
|
|
+ httpConn.setDoOutput(true);
|
|
|
+ OutputStreamWriter writer = new OutputStreamWriter(httpConn.getOutputStream());
|
|
|
+ writer.write("{\"trackers\": [{ \"transaction_id\": \"" + saleId + "\", \"status\": \"SHIPPED\"}]}");
|
|
|
+ writer.flush();
|
|
|
+ writer.close();
|
|
|
+ httpConn.getOutputStream().close();
|
|
|
+
|
|
|
+ InputStream responseStream = httpConn.getResponseCode() / 100 == 2 ? httpConn.getInputStream() : httpConn.getErrorStream();
|
|
|
+ Scanner s = new Scanner(responseStream).useDelimiter("\\A");
|
|
|
+ String res = s.hasNext() ? s.next() : "";
|
|
|
+ log.info("添加物流信息 response:{}",res);
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+
|
|
|
+ });
|
|
|
+
|
|
|
+ return redisService.getStr(RedisService.key.ABROAD_SUCCESS_URL.getName() + orderId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (IOException e) {
|
|
|
+ throw new RuntimeException(e);
|
|
|
+ }
|
|
|
+ log.info("支付失败返回参数:"+ Jsons.toJson(response.result()));
|
|
|
+ return paypalPayConfig.getCancelUrl();
|
|
|
+ }
|
|
|
+ private APIContext getAPIContext(String clientId,String clientSecret,String mode) throws PayPalRESTException {
|
|
|
+ Map<String, String> sdkConfig = new HashMap<>();
|
|
|
+ sdkConfig.put("mode", mode);
|
|
|
+ OAuthTokenCredential authTokenCredential = new OAuthTokenCredential(clientId,clientSecret,sdkConfig);
|
|
|
+ APIContext apiContext = new APIContext(authTokenCredential.getAccessToken());
|
|
|
+ apiContext.setConfigurationMap(sdkConfig);
|
|
|
+ return apiContext;
|
|
|
+ }
|
|
|
+}
|