|
@@ -0,0 +1,206 @@
|
|
|
|
|
+package com.cyksj.service.paypal;
|
|
|
|
|
+
|
|
|
|
|
+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.model.entity.OrderDon;
|
|
|
|
|
+import com.cyksj.model.entity.PaypalPayConfig;
|
|
|
|
|
+import com.cyksj.model.request.OrderRefundReq;
|
|
|
|
|
+import com.cyksj.redis.RedisService;
|
|
|
|
|
+import com.cyksj.service.order.OrderDonService;
|
|
|
|
|
+import com.paypal.api.payments.*;
|
|
|
|
|
+import com.paypal.api.payments.Currency;
|
|
|
|
|
+import com.paypal.base.rest.APIContext;
|
|
|
|
|
+import com.paypal.base.rest.OAuthTokenCredential;
|
|
|
|
|
+import com.paypal.base.rest.PayPalRESTException;
|
|
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
|
|
+
|
|
|
|
|
+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 chan
|
|
|
|
|
+ * @date 2021/3/26 11:03 上午
|
|
|
|
|
+ */
|
|
|
|
|
+@Service
|
|
|
|
|
+@Slf4j
|
|
|
|
|
+@RequiredArgsConstructor
|
|
|
|
|
+public class PayPalService{
|
|
|
|
|
+
|
|
|
|
|
+ private static final GlobalThreadPoolTaskExecutor THREAD_POOL = GlobalThreadPoolTaskExecutor.getInstance();
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ private final OrderDonService orderDonService;
|
|
|
|
|
+
|
|
|
|
|
+ private final PaypalPayConfigService paypalPayConfigService;
|
|
|
|
|
+
|
|
|
|
|
+ private final RedisService redisService;
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ private static final String SANDBOX_URL = "https://api-m.sandbox.paypal.com";
|
|
|
|
|
+ private static final String LIVE_URL = "https://api-m.paypal.com";
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ public String successPayment(String paymentId, String payerId,PaypalPayConfig paypalPayConfig) throws Exception {
|
|
|
|
|
+ Payment payment = null;
|
|
|
|
|
+ try {
|
|
|
|
|
+ payment = this.executePayment(paymentId,payerId,paypalPayConfig.getClientId(),paypalPayConfig.getClientSecret(),paypalPayConfig.getMode());
|
|
|
|
|
+ } catch (PayPalRESTException e) {
|
|
|
|
|
+ log.error("支付回调失败",e);
|
|
|
|
|
+ return paypalPayConfig.getCancelUrl();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ PayerInfo payerInfo = payment.getPayer().getPayerInfo();
|
|
|
|
|
+ log.info("paypal 支付回调 用户信息:{}", payerInfo);
|
|
|
|
|
+ //支付成功
|
|
|
|
|
+ if("approved".equals(payment.getState())){
|
|
|
|
|
+ //paypal 交易id
|
|
|
|
|
+ Sale sale = payment.getTransactions().get(0).getRelatedResources().get(0).getSale();
|
|
|
|
|
+ Currency transactionFee = sale.getTransactionFee();
|
|
|
|
|
+ String transationId = sale.getId();
|
|
|
|
|
+ String orderId = payment.getTransactions().get(0).getCustom();
|
|
|
|
|
+ //更新订单支付状态
|
|
|
|
|
+ OrderDon orderDon = orderDonService.getById(orderId);
|
|
|
|
|
+ orderDon.setTransactionId(transationId);
|
|
|
|
|
+ orderDon.setPayTime(new Date());
|
|
|
|
|
+ orderDon.setAbroadTransactionFee(new BigDecimal(transactionFee.getValue()).multiply(BigDecimal.valueOf(100)));
|
|
|
|
|
+ orderDonService.orderNotify(orderDon);
|
|
|
|
|
+ log.info("支付成功订单:"+orderId+",支付交易号:"+transationId);
|
|
|
|
|
+ log.info("支付成功订单:"+orderId+",返回参数:"+Jsons.toJson(payment));
|
|
|
|
|
+
|
|
|
|
|
+ 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\": \"" + transationId + "\", \"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 response = s.hasNext() ? s.next() : "";
|
|
|
|
|
+ log.info("添加物流信息 response:{}",response);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ e.printStackTrace();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return redisService.getStr(RedisService.key.ABROAD_SUCCESS_URL.getName() + orderId);
|
|
|
|
|
+ }
|
|
|
|
|
+ log.info("支付失败返回参数:"+ Jsons.toJson(payment));
|
|
|
|
|
+ return paypalPayConfig.getCancelUrl();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ public Payment createPayment(Long orderId,String successUrl) throws PayPalRESTException {
|
|
|
|
|
+ redisService.set(RedisService.key.ABROAD_SUCCESS_URL.getName() + orderId,successUrl,RedisService.key.ABROAD_SUCCESS_URL.getTimeout());
|
|
|
|
|
+ OrderDon orderDon = orderDonService.getById(orderId);
|
|
|
|
|
+ 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);
|
|
|
|
|
+ // Set payer details
|
|
|
|
|
+ Payer payer = new Payer();
|
|
|
|
|
+ payer.setPaymentMethod("paypal");
|
|
|
|
|
+
|
|
|
|
|
+ // Set redirect URLs
|
|
|
|
|
+ RedirectUrls redirectUrls = new RedirectUrls();
|
|
|
|
|
+
|
|
|
|
|
+ redirectUrls.setCancelUrl(paypalPayConfig.getCancelUrl());
|
|
|
|
|
+ String returnUrl = "https://" + paypalPayConfig.getHost() + "/8081/api/applets/order/paypal/notify";
|
|
|
|
|
+ //成功回调地址(自己写的/pay/success)
|
|
|
|
|
+ redirectUrls.setReturnUrl(returnUrl);
|
|
|
|
|
+
|
|
|
|
|
+ Transaction transaction = new Transaction();
|
|
|
|
|
+ transaction.setDescription("");
|
|
|
|
|
+ transaction.setCustom(orderDon.getId().toString());
|
|
|
|
|
+
|
|
|
|
|
+ //订单价格
|
|
|
|
|
+ Amount amount = new Amount();
|
|
|
|
|
+ amount.setCurrency("USD");
|
|
|
|
|
+ // 支付的总价,paypal会校验 total = subTotal + tax + ...
|
|
|
|
|
+ BigDecimal totalMoney = money.divide(new BigDecimal(100), 2, RoundingMode.HALF_DOWN);
|
|
|
|
|
+ amount.setTotal(totalMoney.toString());
|
|
|
|
|
+ // 设置各种费用
|
|
|
|
|
+ Details details = new Details();
|
|
|
|
|
+ //商品总价
|
|
|
|
|
+ details.setSubtotal(totalMoney.toString());
|
|
|
|
|
+
|
|
|
|
|
+ amount.setDetails(details);
|
|
|
|
|
+ transaction.setAmount(amount);
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ // Add transaction to a list
|
|
|
|
|
+ List<Transaction> transactions = new ArrayList<Transaction>();
|
|
|
|
|
+ transactions.add(transaction);
|
|
|
|
|
+
|
|
|
|
|
+ // Add payment details
|
|
|
|
|
+ Payment payment = new Payment();
|
|
|
|
|
+
|
|
|
|
|
+ payment.setIntent("sale");
|
|
|
|
|
+ payment.setPayer(payer);
|
|
|
|
|
+ payment.setRedirectUrls(redirectUrls);
|
|
|
|
|
+ payment.setTransactions(transactions);
|
|
|
|
|
+ return payment.create(this.getAPIContext(paypalPayConfig.getClientId(),paypalPayConfig.getClientSecret(),paypalPayConfig.getMode()));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ 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;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 执行支付
|
|
|
|
|
+ */
|
|
|
|
|
+ public Payment executePayment(String paymentId, String PayerID,String clientId,String clientSecret,String mode) throws PayPalRESTException {
|
|
|
|
|
+ Payment payment = new Payment();
|
|
|
|
|
+ payment.setId(paymentId);
|
|
|
|
|
+ PaymentExecution paymentExecute = new PaymentExecution();
|
|
|
|
|
+ paymentExecute.setPayerId(PayerID);
|
|
|
|
|
+ return payment.execute(this.getAPIContext(clientId,clientSecret,mode), paymentExecute);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ public Boolean refund(OrderRefundReq refundReq, OrderDon orderDon) throws PayPalRESTException {
|
|
|
|
|
+ BigDecimal net = refundReq.getRefundMoney().divide(new BigDecimal(7),2,RoundingMode.HALF_DOWN).subtract(orderDon.getAbroadTransactionFee());;
|
|
|
|
|
+ log.info("paypal return order:{},money:{}",orderDon.getId(),net);
|
|
|
|
|
+ PaypalPayConfig paypalPayConfig = paypalPayConfigService.getById(1);
|
|
|
|
|
+ APIContext apiContext = getAPIContext(paypalPayConfig.getClientId(), paypalPayConfig.getClientSecret(),paypalPayConfig.getMode());
|
|
|
|
|
+ Sale sale = Sale.get(apiContext, orderDon.getTransactionId());
|
|
|
|
|
+ Refund refund = new Refund();
|
|
|
|
|
+ refund.setAmount(new Amount("USD", net.divide(new BigDecimal(100),2,RoundingMode.HALF_DOWN).toString()));
|
|
|
|
|
+ refund = sale.refund(apiContext, refund);
|
|
|
|
|
+ log.info("paypal return refund:{}",refund);
|
|
|
|
|
+ if ("completed".equals(refund.getState())){
|
|
|
|
|
+ orderDon.setStatus(OrderDon.Status.refund);
|
|
|
|
|
+ orderDonService.updateById(orderDon);
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+}
|