<dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.10.1</version>
        </dependency>

        <dependency>
            <groupId>com.stripe</groupId>
            <artifactId>stripe-java</artifactId>
            <version>24.0.0</version>
        </dependency>

 

stripe_spring

#stripe配置
stripe:
  publicKey: pk_test_51OFTJTIkpJUW1eOYejtK7ZM3YNnSNc1ELk8D4X2wkXYJoKDtU0Ny7uICL2M4zZerGnNOipTSwyVPmxoKhZE0nmS700zFLJUIAi
  apiKey: sk_test_51OFTJTIkpJUW1eOYDq5k2I6jT74Xn1jAymML11dfFbyeTmoVIdCpgSV8QBRnrdMYDBJ4j7m3O00R3a52VpdmsypF00VVSLNYmC
  cancelUrl: http://stripe.fojia21jia.com/#/pages/index/payFail
  successUrl: http://stripe.foji21ajia.com/#/pages/index/paySuccess
  webhookSecret: whsec_fXgFfbSnxd5x7MGwYrpTIU1euO1Od1yc
package com.ruoyi.web.controller.member;



import com.ruoyi.religious.model.CreateOrderEntity;
import com.ruoyi.religious.model.CreateRefundEntity;

import com.ruoyi.religious.service.StripeOrderService;
import com.stripe.Stripe;
import com.stripe.model.*;
import com.stripe.model.checkout.Session;
import com.stripe.param.*;
import com.stripe.param.checkout.SessionCreateParams;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.math.BigDecimal;
import java.util.*;

@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping("/stripe")
public class OrderController {
    @Autowired
    private StripeOrderService orderService;

    /**
     * 创建支付会话
     * 1、创建产品
     * 2、设置价格
     * 3、创建支付信息 得到url
     * @return
     */
    @PostMapping("/order/pay")
    public String pay(@Validated @RequestBody CreateOrderEntity createOrderEntity) {
        return orderService.pay(createOrderEntity);
    }

//    /**
//     *  发放退款
//     * @return
//     */
//    @PostMapping("/order/refund")
//    public void refund(@Validated @RequestBody CreateRefundEntity createRefund) {
//        orderService.refund(createRefund);
//    }
}
package com.ruoyi.religious.service;


import com.ruoyi.religious.model.CreateOrderEntity;
import com.ruoyi.religious.model.CreateRefundEntity;

public interface StripeOrderService {
    String pay(CreateOrderEntity createOrderEntity);

    void refund(CreateRefundEntity createRefund);
}
package com.ruoyi.religious.service.impl;


import com.ruoyi.common.enums.OrderStatusEnum;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.religious.domain.ReligiousCultureOrder;
import com.ruoyi.religious.model.CreateOrderEntity;
import com.ruoyi.religious.model.CreateRefundEntity;
import com.ruoyi.religious.result.ApiResult;
import com.ruoyi.religious.service.IReligiousCultureOrderService;
import com.ruoyi.religious.service.StripeOrderService;
import com.ruoyi.system.service.ISysConfigService;
import com.stripe.Stripe;
import com.stripe.model.Price;
import com.stripe.model.Product;
import com.stripe.model.Refund;
import com.stripe.model.checkout.Session;
import com.stripe.param.RefundCreateParams;
import com.stripe.param.checkout.SessionCreateParams;
import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;


import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Service
public class StripeOrderServiceImpl implements StripeOrderService {

    @Value("${stripe.apiKey}")
    private String apiKey;

    @Value("${stripe.cancelUrl}")
    private String cancelUrl;

    @Value("${stripe.successUrl}")
    private String successUrl;

    @Autowired
    private ISysConfigService sysConfigService;

    @Autowired
    private IReligiousCultureOrderService religiousCultureOrderService;

    @Override
    public String pay(CreateOrderEntity createOrderEntity) {
        Stripe.apiKey = apiKey;
        try {
            //创建产品
            Map<String, Object> params = new HashMap<>();
            params.put("name", createOrderEntity.getProductName());
            Product product = Product.create(params);
            ReligiousCultureOrder religiousCultureOrder = religiousCultureOrderService.selectReligiousCultureOrderBySerialNumber(createOrderEntity.getOrderId());
            if (religiousCultureOrder == null || !religiousCultureOrder.getStatus().equals(OrderStatusEnum.ADD_GONGDE_WAIT.getCode())) {
                throw new RuntimeException("订单状态错误!");
            }

            String s = sysConfigService.selectConfigByKey("sys.app.virtues.ratio");
            BigDecimal divide = religiousCultureOrder.getPrice().multiply(new BigDecimal(s)).divide(new BigDecimal(1), 2, RoundingMode.UP);
//            BigDecimal price1 = religiousCultureOrder.getPrice();
            //创建价格
            Map<String, Object> priceParams = new HashMap<>();

            BigDecimal actualAmount = divide.multiply(BigDecimal.valueOf(100));  //stripe的默认单位是分,即传入的amount实际上小数点会被左移两位
            //给price绑定元数据并更新price用于检索
            Map<String, Object> metadata = new HashMap<>();
            metadata.put("orderId", createOrderEntity.getOrderId());
            priceParams.put("metadata", metadata);  //通过订单号关联用于检索price信息(可选)
            priceParams.put("unit_amount", actualAmount.intValue());
            priceParams.put("currency", createOrderEntity.getCurrency());
            priceParams.put("product", product.getId());
            Price price = Price.create(priceParams);

            //创建支付信息得到url
            SessionCreateParams params3 = SessionCreateParams.builder()
                    .setMode(SessionCreateParams.Mode.PAYMENT)
                    .setSuccessUrl(successUrl)  //可自定义成功页面
                    .setCancelUrl(cancelUrl)
                    .addLineItem(
                            SessionCreateParams.LineItem.builder()
                                    .setQuantity(createOrderEntity.getQuantity())
                                    .setPrice(price.getId())
                                    .build()).putMetadata("orderId",createOrderEntity.getOrderId())  //通过订单号关联用于检索支付信息(可选)
                    .build();
            Session session = Session.create(params3);
            System.out.println("sessionId:" +session.getId());
            String sessionId = session.getId();  //退款方式1:拿到sessionId入库,退款的时候根据这个id找到PaymentIntent的id然后发起退款
            return session.getUrl();
        }catch (Exception e){
            log.error("创建支付会话出现异常:",e);
        }
        return "";
    }

    @Override
    public void refund(CreateRefundEntity createRefund) {
        try{
            Stripe.apiKey = apiKey;

            if (StringUtils.isNotEmpty(createRefund.getSessionId())){  //根据会话编号退款
                Session session =
                        Session.retrieve(
                                createRefund.getSessionId()
                        );
                RefundCreateParams params;
                if (createRefund.getAmount() != null && createRefund.getAmount().compareTo(BigDecimal.ZERO) != 0){  //指定退款金额
                    BigDecimal actualAmount = createRefund.getAmount().multiply(BigDecimal.valueOf(100));  //api默认单位分
                    params = RefundCreateParams.builder()
                            .setPaymentIntent(session.getPaymentIntent())
                            .setAmount(actualAmount.longValue())
                            .build();
                }else {  //全额退款
                    params = RefundCreateParams.builder()
                            .setPaymentIntent(session.getPaymentIntent())
                            .build();
                }
                Refund.create(params);
                log.info("根据会话编号退款成功");
                return;
            }
            if (StringUtils.isNotEmpty(createRefund.getChargeId())){  //根据退款编号退款
                Map<String, Object> params = new HashMap<>();
                params.put("charge", createRefund.getChargeId());
                if (createRefund.getAmount() != null && createRefund.getAmount().compareTo(BigDecimal.ZERO) != 0){  //指定退款金额,否则全额退款
                    BigDecimal actualAmount = createRefund.getAmount().multiply(BigDecimal.valueOf(100));  //api默认单位分
                    params.put("amount", actualAmount.longValue());
                }
                Refund.create(params);
                log.info("根据退款编号退款成功");
                return;
            }
        }catch (Exception e){
            //e.getMessage.contain("charge_already_refunded") 已退款
            //e.getMessage.contain("resource_missing") 退款编号错误
            //e.getMessage.contain("amount on charge ($n)") 金额应小于n
            log.error("退款异常:",e);
        }
    }
}
package com.ruoyi.web.controller.member;


import com.ruoyi.religious.service.CallbackService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping("/stripe")
public class CallbackController {
    @Autowired
    private CallbackService callbackService;

    @GetMapping("/callback/success")
    public void success(String orderId)  {
        System.out.println("调用success成功:" + orderId);
    }

    @GetMapping("/callback/cancel")
    public void cancel(String orderId)  {
        System.out.println("调用cancel成功:" + orderId);
    }

    /**
     * webhook回调
     * @return 返回处理状态(验签不通过拒绝)
     */
    @PostMapping(value = "/callback/webhook")
    public Object webhook(HttpServletRequest request, HttpServletResponse response){
        return callbackService.webhook(request,response);
    }
}
package com.ruoyi.religious.service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public interface CallbackService {
    Object webhook(HttpServletRequest request, HttpServletResponse response);
}
package com.ruoyi.religious.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.google.gson.JsonSyntaxException;

import com.ruoyi.common.enums.OrderStatusEnum;
import com.ruoyi.common.enums.OrderTypeEnum;
import com.ruoyi.religious.domain.ReligiousCultureOrder;
import com.ruoyi.religious.service.CallbackService;
import com.ruoyi.religious.service.ClientMemberService;
import com.ruoyi.religious.service.IReligiousCultureOrderService;
import com.stripe.exception.SignatureVerificationException;
import com.stripe.model.*;
import com.stripe.net.ApiResource;
import com.stripe.net.Webhook;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.stream.Collectors;

@Slf4j
@Service
public class CallbackServiceImpl implements CallbackService {
    @Value("${stripe.webhookSecret}")
    private String webhookSecret;  //webhookSecret配置在Webhook秘钥签名中

    @Autowired
    private IReligiousCultureOrderService orderService;

    @Autowired
    private ClientMemberService clientMemberService;

    @Override
    public Object webhook(HttpServletRequest request, HttpServletResponse response) {
        System.out.println(request);
        InputStream inputStream = null;
        ByteArrayOutputStream output = null;
        try {
            //获取请求体的参数
            inputStream = request.getInputStream();
            output = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024 * 4];
            int n;
            while (-1 != (n = inputStream.read(buffer))) {
                output.write(buffer, 0, n);
            }
            byte[] bytes = output.toByteArray();
            String eventPayload = new String(bytes, "UTF-8");
            System.out.println("获取请求体的参数:" + eventPayload);
            //获取请求头签名
            String sigHeader = request.getHeader("Stripe-Signature");
            System.out.println("获取请求头的签名:" + sigHeader);
            Event event;

            try {
                event = ApiResource.GSON.fromJson(eventPayload, Event.class);
            } catch (JsonSyntaxException e) {
                // Invalid payload
                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().get();
            } 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.
            }

//            try {
//                System.out.println("webhookSecret"+webhookSecret);
//                event = Webhook.constructEvent(eventPayload, sigHeader, webhookSecret,5000);  //调用webhook进行验签
//                System.out.println(event);
//            } catch (JsonSyntaxException e) {
//                log.error("参数格式有误,解析失败:", e);
//                System.out.println(-1);
//                response.setStatus(400);
//                return "";
//            } catch (SignatureVerificationException e) {
//                log.error("参数被篡改,验签失败:", e);
//                response.setStatus(400);
//                System.out.println(0);
//                return "";
//            }


            // Handle the event
            switch (event.getType()) {
                case "checkout.session.completed"://支付完成
                    System.out.println("---------------success---------------");
                    String s = event.getDataObjectDeserializer().getObject().get().toJson();
                    JSONObject jsonObject = JSONObject.parseObject(s);
                    JSONObject jsonObject2 = (JSONObject)jsonObject.get("metadata");
                    String orderId = jsonObject2.getString("orderId");
                    System.out.println("支付完成,订单号为:"+orderId);
                    ReligiousCultureOrder religiousCultureOrder = orderService.selectReligiousCultureOrderBySerialNumber(orderId);
                    if (religiousCultureOrder.getStatus().equals(OrderStatusEnum.ADD_GONGDE_WAIT.getCode())) {
                        if (religiousCultureOrder.getOrderType().equals(OrderTypeEnum.ADD_GONGDE.getCode())){
                            clientMemberService.addSubscriber(religiousCultureOrder);
                        }else {
                            orderService.alipay(religiousCultureOrder);
                        }
                    }
                    break;
                case "checkout.session.expired": {
                    System.out.println("回话过期");
                    // Then define and call a function to handle the event checkout.session.expired
                    break;
                }
                case "payment_intent.canceled": {
                    System.out.println("订单取消");
                    // Then define and call a function to handle the event payment_intent.canceled
                    break;
                }
                case "charge.succeeded": {
                    System.out.println("charge.succeeded");
                    break;
                }
                case "payment_intent.created": {
                    System.out.println("创建支付了");
                    break;
                }
                case "payment_intent.payment_failed": {
                    System.out.println("支付失败");
                    // Then define and call a function to handle the event payment_intent.payment_failed
                    break;
                }
                case "payment_intent.succeeded": {
                    //支付成功
                    PaymentIntent paymentIntent = (PaymentIntent) stripeObject;
                    System.out.println("支付成功,订单chargeId:" + paymentIntent.getLatestCharge());
                    String chargeId = paymentIntent.getLatestCharge();  //退款方式2:回调保存该退款编号用于退款,
                    System.out.println(chargeId);
                    break;
                }
                // ... handle other event types
                default:
                    System.out.println("Unhandled event type: " + event.getType());
            }
            response.setStatus(200);  //处理无异常,返回
        } catch (Exception e) {
            System.out.println(2);
            log.error("webhook异步通知解析异常:", e);
            response.setStatus(400);
            return "";
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (output != null) {
                    output.close();
                }
            } catch (Exception e) {
                log.error("流关闭异常");
            }
        }
        return "";
    }
}
package com.ruoyi.religious.model;

import lombok.Data;

import javax.validation.constraints.NotEmpty;
import java.math.BigDecimal;

@Data
public class CreateOrderEntity {
    /** 本地订单号 */
//    @NotEmpty(message = "订单编号不允许为空")
    private String orderId;

    /** 商品名称 */
//    @NotEmpty(message = "商品名称不允许为空")
    private String productName;


    /** 货币类型 */
    private String currency;

    /** 购买数量 */
    private long quantity;
}
package com.ruoyi.religious.model;

import lombok.Data;

import java.math.BigDecimal;

@Data
public class CreateRefundEntity {
    /** 退款金额,支持多次退款,sessionId或者chargeId不会更改 */
    private BigDecimal amount;

    /** 会话编号 */
    private String sessionId;

    /** 退款编号 */
    private String chargeId;
}
package com.ruoyi.religious.model;

public class StripeConstant {
    /**
     * pending      刚刚准备好订单;
     * chargeable   Source对象变成chargeable客户已授权和验证的付款后。
     * failed       Source对象未能成为收费为你的客户拒绝授权支付。
     * canceled     Source对象过期,并且不能用于创建电荷。
     * succeeded    收费成功,付款完成。
     * created      发送客户投诉,从Stripe余额中扣除争议金额。
     */


    public static final String PAY_STRIPE_STATUS_PENDING = "pending";
    public static final String PAY_STRIPE_STATUS_CHARGEABLE = "chargeable";
    public static final String PAY_STRIPE_STATUS_FAILED = "failed";
    public static final String PAY_STRIPE_STATUS_CANCELED = "canceled";
    public static final String PAY_STRIPE_STATUS_SUCCEEDED = "succeeded";
    public static final String PAY_STRIPE_STATUS_CREATED = "created";
    //授权完成,但用户余额不足
    public static final String PAY_STRIPE_STATUS_INSUFFICIENT_FUNDS = "insufficient_funds";
    //授权完成,订单收费金额大于支付宝支持的费用
    public static final String PAY_STRIPE_STATUS_INVALID_AMOUNT = "invalid_amount";
    //授权完成,创建收费订单失败
    public static final String PAY_STRIPE_STATUS_CHARGE_ERROR = "charge_error";

    //支付回调状态,支付成功的事件类型
    public static final String PAY_STRIPE_STATUS_CALL_BACK_SUCCESSED = "payment_intent.succeeded";

    //支付回调状态,支付方式附加事件(当前的支付模式不会返回该支付状态)
    public static final String PAY_STRIPE_STATUS_CALL_BACK_ATTACHED = "payment_method.attached";

    //支付回调状态,取消订单
    public static final String PAY_STRIPE_STATUS_CALL_BACK_CANCELED = "payment_method.canceled";

    //支付回调状态,支付失败
    public static final String PAY_STRIPE_STATUS_CALL_BACK_FAILED = "payment_method.payment_failed";
}

 

stripe_spring_02

 

stripe_前端_03

 

stripe_ide_04

stripe_ide_05

  

stripe_spring_06