Forráskód Böngészése

Merge branch 'paypal&stripe'

# Conflicts:
#	netflix-service/src/main/java/com/cyksj/redis/RedisService.java
zwhui 3 éve
szülő
commit
af5febad81

+ 11 - 0
netflix-dao/src/main/java/com/cyksj/mapper/PaypalPayConfigMapper.java

@@ -0,0 +1,11 @@
+package com.cyksj.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.PaypalPayConfig;
+
+/**
+ * @author zwhui
+ * @date 2022/10/11 16:18
+ */
+public interface PaypalPayConfigMapper extends BaseMapper<PaypalPayConfig> {
+}

+ 11 - 0
netflix-dao/src/main/java/com/cyksj/mapper/StripePayConfigMapper.java

@@ -0,0 +1,11 @@
+package com.cyksj.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.StripePayConfig;
+
+/**
+ * @author zwhui
+ * @date 2022/10/11 16:18
+ */
+public interface StripePayConfigMapper extends BaseMapper<StripePayConfig> {
+}

+ 13 - 1
netflix-dao/src/main/java/com/cyksj/model/entity/OrderDon.java

@@ -49,6 +49,16 @@ public class OrderDon extends BaseEntity implements Serializable {
     /** 金额 */
     private BigDecimal money ;
 
+    /**
+     * 海外支付金额
+     */
+    private BigDecimal abroadMoney;
+
+    /**
+     * 手续费
+     */
+    private BigDecimal abroadTransactionFee;
+
     /**
      * 退款金额
      */
@@ -236,7 +246,9 @@ public class OrderDon extends BaseEntity implements Serializable {
         LOTTERY("lottery","抽奖"),
         EQUIPMENT("equipment", "实物设备分销"),
         PAY_EQUIPMENT("pay_equipment", "自购设备"),
-        BALANCE("balance", "余额支付")
+        BALANCE("balance", "余额支付"),
+        PAYPAL("paypal","paypal支付"),
+        STRIPE("stripe","stripe支付")
         ;
         String type;
         String desc;

+ 30 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/PaypalPayConfig.java

@@ -0,0 +1,30 @@
+package com.cyksj.model.entity;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @author zwhui
+ * @date 2022/10/11 16:14
+ */
+@Data
+public class PaypalPayConfig extends BaseEntity implements Serializable {
+    /**
+     * 域名
+     */
+    private String host;
+    /**
+     * 公钥
+     */
+    private String clientId;
+    /**
+     * 私钥
+     */
+    private String clientSecret;
+
+    private String successUrl;
+    private String cancelUrl;
+
+    private String mode;
+}

+ 31 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/StripePayConfig.java

@@ -0,0 +1,31 @@
+package com.cyksj.model.entity;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @author zwhui
+ * @date 2022/10/11 16:14
+ */
+@Data
+public class StripePayConfig  extends BaseEntity implements Serializable {
+    /**
+     * 域名
+     */
+    private String host;
+    /**
+     * 公钥
+     */
+    private String publicKey;
+    /**
+     * 私钥
+     */
+    private String apiKey;
+    /**
+     * webhook秘钥
+     */
+    private String webhookSecret;
+    private String successUrl;
+    private String cancelUrl;
+}

+ 1 - 0
netflix-service/src/main/java/com/cyksj/redis/RedisService.java

@@ -624,6 +624,7 @@ public class RedisService {
         CORP_UPLOAD_ATTACHMENTS("corp:%s:upload:attachments:","临时附件",60*60*24*3 - 60*60*1L),
         CORP_DYNAMIC_RULE_TAG_KEY("corp_dynamic_rule_tag_key:", "企业微信动态标签key", 60 * 60L),
         ORDER_DON_POST_DATA_USER_KEY("order_don_post_data_user_key:", "用户订单回传缓存key", -1L),
+        ABROAD_SUCCESS_URL("abroad:pay:success_url:","海外支付成功页",60 * 60 * 24L),
         ;
 
         private String name;

+ 14 - 8
netflix-service/src/main/java/com/cyksj/service/mange/impl/CmsOrderDonServiceImpl.java

@@ -41,7 +41,9 @@ import com.cyksj.service.mange.stock.GoodsDonStockService;
 import com.cyksj.service.market.MarketFrontService;
 import com.cyksj.service.order.OrderCommonService;
 import com.cyksj.service.order.UserRealOrderBenefitsService;
+import com.cyksj.service.paypal.PayPalService;
 import com.cyksj.service.relation.GroupRelationClearService;
+import com.cyksj.service.stripe.StripeService;
 import com.cyksj.service.template.TemplateCommonService;
 import com.cyksj.service.user.UserBenefitsService;
 import com.cyksj.service.wechat.WeChatService;
@@ -125,6 +127,10 @@ public class CmsOrderDonServiceImpl extends ServiceImpl<OrderDonMapper,OrderDon>
 
     private final WorkOrderMapper workOrderMapper;
 
+    private final PayPalService payPalService;
+
+    private final StripeService stripeService;
+
     private static final GlobalThreadPoolTaskExecutor TASK_POOL = GlobalThreadPoolTaskExecutor.getInstance();
 
     @Override
@@ -252,7 +258,7 @@ public class CmsOrderDonServiceImpl extends ServiceImpl<OrderDonMapper,OrderDon>
         }
         String refundType = refundReq.getRefundType();
         //校验订单是否符合退款
-        checkOrderRefund(orderDon, goodsDon, sku, 2, refundReq);
+        checkOrderRefund(orderDon, goodsDon, sku, refundReq);
         OrderDon.Type type = orderDon.getType();
         String outNo = StrUtil.EMPTY;
         if (type == OrderDon.Type.WeChat && !refundType.equals(OrderDon.Type.BALANCE.getType())) {
@@ -261,6 +267,12 @@ public class CmsOrderDonServiceImpl extends ServiceImpl<OrderDonMapper,OrderDon>
         if (type == OrderDon.Type.ALI_PAY && !refundType.equals(OrderDon.Type.BALANCE.getType())) {
             outNo = zfbRefund(refundReq, orderDon);
         }
+        if (type == OrderDon.Type.PAYPAL && !refundType.equals(OrderDon.Type.BALANCE.getType())) {
+            payPalService.refund(refundReq, orderDon);
+        }
+        if (type == OrderDon.Type.STRIPE && !refundType.equals(OrderDon.Type.BALANCE.getType())) {
+            stripeService.refund(refundReq, orderDon);
+        }
         orderDon.setRefundDesc(refundReq.getRefundDesc());
         //记录退款信息
         commonRefund(orderDon, goodsDon, sku, outNo, refundType);
@@ -467,7 +479,7 @@ public class CmsOrderDonServiceImpl extends ServiceImpl<OrderDonMapper,OrderDon>
     /**
      * 校验订单是否符合退款操作
      */
-    private void checkOrderRefund(OrderDon orderDon, GoodsDon goodsDon, GoodsDonSku sku, Integer orderPayType, OrderRefundReq refundReq) {
+    private void checkOrderRefund(OrderDon orderDon, GoodsDon goodsDon, GoodsDonSku sku, OrderRefundReq refundReq) {
         if (orderDon == null) {
             throw BusinessRuntimeException.getInstance("订单不存在");
         }
@@ -475,12 +487,6 @@ public class CmsOrderDonServiceImpl extends ServiceImpl<OrderDonMapper,OrderDon>
         if (orderDon.getType() == OrderDon.Type.BALANCE && !OrderDon.Type.BALANCE.getType().equals(refundType)) {
             throw BusinessRuntimeException.getInstance("余额支付的订单不支持金额退款");
         }
-        if (orderDon.getType() != OrderDon.Type.BALANCE && orderDon.getType() != OrderDon.Type.WeChat && orderPayType == 1) {
-            throw BusinessRuntimeException.getInstance("该订单不是微信支付的订单");
-        }
-        if (orderDon.getType() != OrderDon.Type.BALANCE && orderDon.getType() != OrderDon.Type.ALI_PAY && orderPayType == 2) {
-            throw BusinessRuntimeException.getInstance("该订单不是支付宝支付的订单");
-        }
         if (OrderDon.Status.refund == orderDon.getStatus()) {
             throw BusinessRuntimeException.getInstance("该订单已退款");
         }

+ 2 - 0
netflix-service/src/main/java/com/cyksj/service/order/OrderDonService.java

@@ -94,4 +94,6 @@ public interface OrderDonService extends IService<OrderDon> {
      * 0元订单确认
      */
     void zeroOrderConfirm(Long orderId);
+
+    void orderNotify(OrderDon orderDon);
 }

+ 64 - 0
netflix-service/src/main/java/com/cyksj/service/order/impl/OrderDonServiceImpl.java

@@ -1913,4 +1913,68 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 			updateCarParkAndSendMsg(goodsDon, sku, orderDon);
 		}
 	}
+
+
+	@Override
+	public void orderNotify(OrderDon orderDon){
+		Long goodsId = orderDon.getGoodsId();
+		if (orderDon.getIsNoLogin()) {
+			orderDon.setExCode(exchangeCodeService.getExCode(goodsId, orderDon.getSkuId(), 1));
+		}
+		GoodsDonSku sku = goodsDonSkuMapper.selectById(orderDon.getSkuId());
+		GoodsDon goodsDon = goodsDonMapper.selectById(sku.getGoodsId());
+		orderDon.setStatus(goodsDon.getType() == 1 && goodsDon.getSecondType() == 2 ? OrderDon.Status.complete : OrderDon.Status.hasPayment);
+
+		if (orderDon.getIsNoLogin()) {
+			orderDon.setStatus(OrderDon.Status.hasPayment);
+		}
+		boolean isSuccess = update(Wrappers.lambdaUpdate(OrderDon.class)
+				.set(OrderDon::getStatus, orderDon.getStatus())
+				.in(OrderDon::getStatus, List.of(OrderDon.Status.noPayment, OrderDon.Status.close))
+				.eq(OrderDon::getId, orderDon.getId()));
+		if (!isSuccess) {
+			log.info("支付宝回调商家订单号:{}订单状态已修改", orderDon.getOrderNo());
+			return;
+		}
+		if (!orderDon.getIsNoLogin()) {
+			//更新座位信息
+			updateGroupsTicket(orderDon, goodsDon, sku);
+			if (orderDon.getMoney().compareTo(BigDecimal.ZERO) > 0 && orderDon.getIsDistribute()) {
+				TASK_POOL.execute(() -> distributeWaitingSendPointsRecord(orderDon));
+			}
+			if (goodsDon.getType() == 1) {
+				//无车队,新增车队
+				checkNoGroupsTripsAndSendMsg(sku, orderDon);
+			}
+			//完成任务
+			TASK_POOL.execute(() -> {
+				TaskType taskType = taskTypeMapper.selectOne(Wrappers.lambdaQuery(TaskType.class)
+						.eq(TaskType::getName, TaskTypeEnum.yh_order.getDesc()).last("limit 1"));
+				taskService.completeTask(taskType, orderDon.getUserId());
+			});
+			//客服续费订单
+			handleServiceRenewOrder(orderDon);
+		}
+		//购买Midjourney课程 赠送Midjourney车票
+		if (goodsId != null && goodsId.equals(27l)) {
+			orderDon.setStatus(OrderDon.Status.complete);
+			TASK_POOL.execute(() -> sendMidjourneyTicketIfCourse(orderDon.getUserId()));
+		}
+		this.updateById(orderDon);
+		//配置了特定奖池,增加抽奖机会
+		TASK_POOL.execute(() -> {
+			try {
+				marketFrontService.addSpecificChanceNum(orderDon.getUserId(), orderDon.getId(), orderDon.getGoodsId(), orderDon.getSkuId());
+				if (goodsDon.getType() == 2) {
+					equipmentDistributeService.addDistributeBenefitsChance(orderDon.getUserId(), orderDon.getId());
+				}
+				//若该订单使用了规定推广者的兑换码 增加上下级关系
+				if (StrUtil.isNotBlank(orderDon.getCouponExCode())) {
+					addDistributeCouponCode(orderDon.getUserId(), orderDon.getCouponExCode());
+				}
+			} catch (Exception e) {
+				log.error("zfb增加抽奖机会错误,orderId:{},error:{}", orderDon.getId(), e);
+			}
+		});
+	}
 }

+ 206 - 0
netflix-service/src/main/java/com/cyksj/service/paypal/PayPalService.java

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

+ 11 - 0
netflix-service/src/main/java/com/cyksj/service/paypal/PaypalPayConfigService.java

@@ -0,0 +1,11 @@
+package com.cyksj.service.paypal;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.cyksj.model.entity.PaypalPayConfig;
+
+/**
+ * @author zwhui
+ * @date 2022/10/11 16:19
+ */
+public interface PaypalPayConfigService extends IService<PaypalPayConfig> {
+}

+ 17 - 0
netflix-service/src/main/java/com/cyksj/service/paypal/impl/PaypalPayConfigServiceImpl.java

@@ -0,0 +1,17 @@
+package com.cyksj.service.paypal.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.cyksj.mapper.PaypalPayConfigMapper;
+import com.cyksj.model.entity.PaypalPayConfig;
+import com.cyksj.service.paypal.PaypalPayConfigService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author zwhui
+ * @date 2022/10/11 16:19
+ */
+@Service
+@Slf4j
+public class PaypalPayConfigServiceImpl extends ServiceImpl<PaypalPayConfigMapper, PaypalPayConfig> implements PaypalPayConfigService {
+}

+ 11 - 0
netflix-service/src/main/java/com/cyksj/service/stripe/StripePayConfigService.java

@@ -0,0 +1,11 @@
+package com.cyksj.service.stripe;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.cyksj.model.entity.StripePayConfig;
+
+/**
+ * @author zwhui
+ * @date 2022/10/11 16:19
+ */
+public interface StripePayConfigService extends IService<StripePayConfig> {
+}

+ 138 - 0
netflix-service/src/main/java/com/cyksj/service/stripe/StripeService.java

@@ -0,0 +1,138 @@
+package com.cyksj.service.stripe;
+
+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.mapper.StripePayConfigMapper;
+import com.cyksj.model.entity.OrderDon;
+import com.cyksj.model.entity.StripePayConfig;
+import com.cyksj.model.request.OrderRefundReq;
+import com.cyksj.service.order.OrderDonService;
+import com.stripe.Stripe;
+import com.stripe.exception.StripeException;
+import com.stripe.model.*;
+import com.stripe.model.checkout.Session;
+import com.stripe.param.RefundCreateParams;
+import com.stripe.param.checkout.SessionCreateParams;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.Date;
+import java.util.HashMap;
+
+/**
+ * @author zwhui
+ * @date 2021/6/21 11:03 上午
+ */
+@Service
+@Slf4j
+@RequiredArgsConstructor
+public class StripeService {
+
+    private static final GlobalThreadPoolTaskExecutor THREAD_POOL = GlobalThreadPoolTaskExecutor.getInstance();
+
+
+
+    private final OrderDonService orderDonService;
+
+    private final StripePayConfigMapper stripePayConfigMapper;
+
+
+
+   public String checkOutStripe(Long orderId,String successUrl) throws Exception{
+       OrderDon orderDon = orderDonService.getById(orderId);
+       BigDecimal money = orderDon.getMoney();
+       orderDon.setType(OrderDon.Type.STRIPE);
+       orderDonService.updateById(orderDon);
+       HashMap<String, String> responseData = new HashMap<String, String>();
+       if (OrderDon.Status.hasPayment.equals(orderDon.getStatus())) {
+           throw new BusinessRuntimeException(GatewayApiCode.METADATA_SYSTEM_ERROR.getCode(),"The donation has been paid");
+       }
+       StripePayConfig stripePayConfig = stripePayConfigMapper.selectById(1);
+       try {
+           Stripe.apiKey = stripePayConfig.getApiKey();
+           SessionCreateParams params =
+                   SessionCreateParams.builder()
+                           .setMode(SessionCreateParams.Mode.PAYMENT)
+                           .setSuccessUrl(successUrl)
+                           .setCancelUrl(stripePayConfig.getCancelUrl())
+                           .setClientReferenceId(orderDon.getId().toString())
+                           .addLineItem(
+                                   SessionCreateParams.LineItem.builder()
+                                           .setQuantity(1L)
+                                           .setPriceData(
+                                                   SessionCreateParams.LineItem.PriceData.builder()
+                                                           .setCurrency("USD")
+                                                           .setUnitAmount(money.longValue())
+                                                           .setProductData(
+                                                                   SessionCreateParams.LineItem.PriceData.ProductData.builder()
+                                                                           .setName("donation")
+                                                                           .setDescription("donation")
+                                                                           .build()).build()).build()).build();
+           Session session = Session.create(params);
+           responseData.put("id", session.getId());
+       } catch (StripeException e) {
+           e.printStackTrace();
+       }
+       return Jsons.toJson(responseData);
+   }
+
+
+   public void fulfillOrder(Session session) throws Exception{
+       if (null == session){
+           log.error("支付回调失败");
+           return;
+       }
+       String orderId = session.getClientReferenceId();
+       if (!"paid".equals(session.getPaymentStatus())){
+           return;
+       }
+       OrderDon orderDon = orderDonService.getById(orderId);
+       if (orderDon == null){
+           return;
+       }
+       if (orderDon.getStatus().equals(OrderDon.Status.hasPayment)) {
+           log.info("已回调,重复修改状态 id:{}", orderDon.getId());
+           return;
+       }
+       String paymentIntentId = session.getPaymentIntent();
+       PaymentIntent paymentIntent = PaymentIntent.retrieve(paymentIntentId);
+       String chargeId = paymentIntent.getLatestCharge();
+       BalanceTransaction balanceTransaction = BalanceTransaction.retrieve(Charge.retrieve(chargeId).getBalanceTransaction());
+       //更新订单支付状态
+
+       orderDon.setTransactionId(chargeId);
+       orderDon.setAbroadTransactionFee(BigDecimal.valueOf(balanceTransaction.getFee()));
+       orderDon.setPayTime(new Date());
+       orderDonService.orderNotify(orderDon);
+       log.info("支付成功订单:"+orderId+",支付交易号:"+chargeId);
+       log.info("支付成功订单:"+orderId+",返回参数:"+Jsons.toJson(session));
+
+   }
+
+
+    public Boolean refund(OrderRefundReq refundReq, OrderDon orderDon) throws StripeException {
+        BigDecimal net = refundReq.getRefundMoney().divide(new BigDecimal(7),2, RoundingMode.HALF_DOWN).subtract(orderDon.getAbroadTransactionFee());
+        log.info("stripe return order:{},money:{}",orderDon.getId(),net);
+        StripePayConfig stripePayConfig = stripePayConfigMapper.selectById(1);
+        Stripe.apiKey = stripePayConfig.getApiKey();
+        RefundCreateParams params =
+                RefundCreateParams.builder()
+                        .setCharge(orderDon.getTransactionId())
+                        .setAmount(net.longValue())
+                        .build();
+        Refund refund = Refund.create(params);
+        log.info("stripe return refund:{}",refund);
+        if ("succeeded".equals(refund.getStatus())){
+            orderDon.setStatus(OrderDon.Status.refund);
+            orderDonService.updateById(orderDon);
+            return true;
+        }
+        return false;
+    }
+
+}

+ 17 - 0
netflix-service/src/main/java/com/cyksj/service/stripe/impl/StripePayConfigServiceImpl.java

@@ -0,0 +1,17 @@
+package com.cyksj.service.stripe.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.cyksj.mapper.StripePayConfigMapper;
+import com.cyksj.model.entity.StripePayConfig;
+import com.cyksj.service.stripe.StripePayConfigService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author zwhui
+ * @date 2022/10/11 16:19
+ */
+@Service
+@Slf4j
+public class StripePayConfigServiceImpl extends ServiceImpl<StripePayConfigMapper, StripePayConfig> implements StripePayConfigService {
+}

+ 141 - 0
netflix-web/src/main/java/com/cyksj/web/controller/payment/OrderController.java

@@ -29,13 +29,25 @@ import com.cyksj.model.request.OrderPayRequest;
 import com.cyksj.model.response.AliPayTradeStatus;
 import com.cyksj.model.views.GoodsDonSkuView;
 import com.cyksj.model.views.OrderCommentView;
+import com.cyksj.redis.RedisService;
 import com.cyksj.service.order.OrderDonService;
+import com.cyksj.service.paypal.PayPalService;
+import com.cyksj.service.paypal.PaypalPayConfigService;
+import com.cyksj.service.stripe.StripePayConfigService;
+import com.cyksj.service.stripe.StripeService;
 import com.cyksj.service.user.UserService;
 import com.cyksj.web.util.StpUserUtil;
 import com.ejlchina.searcher.BeanSearcher;
 import com.ejlchina.searcher.SearchResult;
 import com.ejlchina.searcher.param.Operator;
 import com.ejlchina.searcher.util.MapUtils;
+import com.google.gson.JsonSyntaxException;
+import com.paypal.api.payments.Links;
+import com.paypal.api.payments.Payment;
+import com.stripe.exception.SignatureVerificationException;
+import com.stripe.model.*;
+import com.stripe.model.checkout.Session;
+import com.stripe.net.Webhook;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -45,7 +57,9 @@ import org.springframework.web.bind.annotation.*;
 import javax.annotation.Resource;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import java.io.InputStream;
 import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -70,6 +84,9 @@ public class OrderController {
     @Resource
     private HttpServletRequest request;
 
+    @Resource
+    private HttpServletResponse response;
+
     @Autowired
     private OrderDonMapper orderDonMapper;
 
@@ -88,6 +105,18 @@ public class OrderController {
     @Autowired
     private UserService userService;
 
+    @Autowired
+    private PaypalPayConfigService paypalPayConfigService;
+
+    @Autowired
+    private PayPalService payPalService;
+
+    @Autowired
+    private StripePayConfigService stripePayConfigService;
+
+    @Autowired
+    private StripeService stripeService;
+
     /**
      * 支付
      * @author chan
@@ -459,4 +488,116 @@ public class OrderController {
         return retMap;
     }
 
+
+    /**
+     * paypal支付
+     */
+    @PostMapping("/paypal/pay/{orderId}")
+    public Result<String> paypalPay(@PathVariable Long orderId,String successUrl) throws Exception {
+        final String methodName = "paypal支付";
+        log.info("{}[start] orderId:{}",methodName,orderId);
+
+        Payment payment = payPalService.createPayment(orderId,successUrl);
+        String payUrl = "/";
+        log.info("paypal links:{}",payment.getLinks());
+        for(Links links : payment.getLinks()){
+            if("approval_url".equals(links.getRel())){
+                // 客户付款登陆地址
+                String payPalUrl = links.getHref();
+                log.info("PayPal调起支付成功[end]:{}", orderId);
+                payUrl = payPalUrl;
+            }
+        }
+
+        log.info("paypal支付成功[end]");
+
+        return GatewayResponse.SUCCESS.newBuilder().toResult(payUrl);
+    }
+
+
+    /**
+     * stripe支付
+     */
+    @PostMapping("/stripe/pay/{orderId}")
+    public Result<String> stripePay(@PathVariable Long orderId,String successUrl) throws Exception {
+        final String methodName = "stripe支付";
+        log.info("{}[start] orderId:{}",methodName,orderId);
+        String payUrl = stripeService.checkOutStripe(orderId,successUrl);
+
+        log.info("Stripe支付成功 result:{}[end]",payUrl);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(payUrl);
+    }
+
+
+    /**
+     * paypal支付回调
+     */
+    @RequestMapping(value = "/paypal/notify")
+    public void successPay(@RequestParam(value = "paymentId") String paymentId, @RequestParam(value = "PayerID") String PayerID) throws Exception {
+        PaypalPayConfig paypalPayConfig = paypalPayConfigService.getById(1);
+        response.sendRedirect(payPalService.successPayment(paymentId,PayerID,paypalPayConfig));
+    }
+
+
+    /**
+     * stripe支付回调
+     */
+    @PostMapping("/stripe/notify")
+    @ResponseBody
+    public String webhooks() throws Exception {
+        InputStream inputStream = request.getInputStream();
+        byte[] bytes = IoKit.toBytes(inputStream);
+        String payload = new String(bytes, StandardCharsets.UTF_8);
+
+        String sigHeader = request.getHeader("Stripe-Signature");
+        StripePayConfig stripePayConfig = stripePayConfigService.getById(1);
+        Event event = null;
+        try {
+            event = Webhook.constructEvent(
+                    payload, sigHeader, stripePayConfig.getWebhookSecret()
+            );
+        } catch (JsonSyntaxException | SignatureVerificationException e) {
+            response.setStatus(400);
+            return "";
+        }
+
+        // Deserialize the nested object inside the event
+        EventDataObjectDeserializer dataObjectDeserializer = event.getDataObjectDeserializer();
+        StripeObject stripeObject = null;
+        if (dataObjectDeserializer.getObject().isPresent()) {
+            stripeObject = dataObjectDeserializer.getObject().orElse(null);
+        } else {
+            // Deserialization failed, probably due to an API version mismatch.
+            // Refer to the Javadoc documentation on `EventDataObjectDeserializer` for
+            // instructions on how to handle this case, or return an error here.
+        }
+
+        // Handle the event
+        switch (event.getType()) {
+            case "payment_intent.succeeded":
+                PaymentIntent paymentIntent = (PaymentIntent) stripeObject;
+                response.setStatus(200);
+                break;
+            case "charge.succeeded":
+                //使用token支付成功回调
+                Charge charge = (Charge) stripeObject;
+                //TODO 此时根据charge ID 查询出关联的订单并处理支付成功业务代码
+                response.setStatus(200);
+                break;
+            case "checkout.session.completed":
+                //使用checkout支付成功回调
+                Session session = (Session) stripeObject;
+                stripeService.fulfillOrder(session);
+                response.setStatus(200);
+                break;
+            default:
+                response.setStatus(400);
+                return "";
+        }
+        response.setStatus(200);
+        return "";
+    }
+
+
+
 }

+ 11 - 0
pom.xml

@@ -85,6 +85,17 @@
             <artifactId>lombok</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>com.paypal.sdk</groupId>
+            <artifactId>rest-api-sdk</artifactId>
+            <version>1.4.2</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.stripe</groupId>
+            <artifactId>stripe-java</artifactId>
+            <version>22.1.0</version>
+        </dependency>
 
         <dependency>
             <groupId>org.springframework.boot</groupId>