2
0

feat: stripe支付

This commit is contained in:
caiyuchao
2025-04-25 11:27:27 +08:00
parent 9a4eaddc50
commit b99d2600f7
8 changed files with 318 additions and 12 deletions

View File

@@ -106,17 +106,11 @@
<dependency>
<groupId>com.paypal.sdk</groupId>
<artifactId>paypal-server-sdk</artifactId>
<version>1.0.0</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.stripe</groupId>
<artifactId>stripe-java</artifactId>
</dependency>
<dependency>

View File

@@ -0,0 +1,24 @@
package org.wfc.payment.domain;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @description: stripe 配置
* @author: cyc
* @since: 2025-04-24
*/
@Data
@Component
@ConfigurationProperties(prefix = "stripe")
public class StripeBean {
private String secretKey;
private String publicKey;
private String endpointSecret;
private String domain;
}

View File

@@ -0,0 +1,37 @@
package org.wfc.payment.pay.stripe.controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.wfc.common.core.domain.R;
import org.wfc.payment.pay.stripe.service.IStripeService;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @description: stripe controller
* @author: cyc
* @since: 2025-04-24
*/
@RestController
@RequestMapping("/stripe")
public class StripeController {
@Resource
private IStripeService stripeService;
@PostMapping("/pay/{orderId}")
public R<String> stripePay(@PathVariable Long orderId) {
return R.ok(stripeService.stripePay(orderId));
}
@PostMapping("/callback")
public String webhook(HttpServletRequest request, HttpServletResponse response) {
String result = stripeService.webhook(request, response);
return result;
}
}

View File

@@ -0,0 +1,15 @@
package org.wfc.payment.pay.stripe.service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author: cyc
* @since: 2025-04-24
*/
public interface IStripeService {
String stripePay(Long orderId);
String webhook(HttpServletRequest request, HttpServletResponse response);
}

View File

@@ -0,0 +1,215 @@
package org.wfc.payment.pay.stripe.service.impl;
import com.stripe.Stripe;
import com.stripe.exception.SignatureVerificationException;
import com.stripe.exception.StripeException;
import com.stripe.model.Event;
import com.stripe.model.EventDataObjectDeserializer;
import com.stripe.model.PaymentIntent;
import com.stripe.model.StripeObject;
import com.stripe.model.checkout.Session;
import com.stripe.net.Webhook;
import com.stripe.param.checkout.SessionCreateParams;
import com.stripe.param.checkout.SessionRetrieveParams;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Service;
import org.wfc.common.core.domain.R;
import org.wfc.payment.domain.StripeBean;
import org.wfc.payment.pay.stripe.service.IStripeService;
import org.wfc.user.api.RemoteUUserService;
import org.wfc.user.api.domain.vo.UOrderVo;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Objects;
/**
* @author: cyc
* @since: 2025-04-24
*/
@Slf4j
@Service
public class StripeServiceImpl implements IStripeService {
@Resource
private RemoteUUserService remoteUUserService;
@Resource
private StripeBean stripeBean;
@Override
public String stripePay(Long orderId) {
Stripe.apiKey = stripeBean.getSecretKey();
String YOUR_DOMAIN = stripeBean.getDomain();
UOrderVo orderVo = getByOrderId(orderId);
Long totalFee = orderVo.getOrderAmount().multiply(BigDecimal.valueOf(100)).setScale(0, RoundingMode.HALF_UP).longValue();
String productName = orderVo.getType() == 0 ? "Package" : "Recharge";
SessionCreateParams params =
SessionCreateParams.builder()
.setMode(SessionCreateParams.Mode.PAYMENT)
.setSuccessUrl(YOUR_DOMAIN + "")
.setCancelUrl(YOUR_DOMAIN + "")
.setClientReferenceId(orderId.toString())
.addLineItem(
SessionCreateParams.LineItem.builder()
.setQuantity(1L)
.setPriceData(
SessionCreateParams.LineItem.PriceData.builder()
.setCurrency("usd")
.setUnitAmount(totalFee)
.setProductData(
SessionCreateParams.LineItem.PriceData.ProductData.builder()
.setName(productName)
.build())
.build())
.build())
.build();
Session session = null;
try {
session = Session.create(params);
} catch (StripeException e) {
log.error("Stripe create session error", e);
throw new RuntimeException(e);
}
return session.getUrl();
}
@Override
public String webhook(HttpServletRequest request, HttpServletResponse response) {
Stripe.apiKey = stripeBean.getSecretKey();
// Replace this endpoint secret with your endpoint's unique secret
// If you are testing with the CLI, find the secret by running 'stripe listen'
// If you are using an endpoint defined with the API or dashboard, look in your webhook settings
// at https://dashboard.stripe.com/webhooks
String endpointSecret = stripeBean.getEndpointSecret();
String payload = null;
try {
InputStream inputStream = request.getInputStream();
byte[] bytes = IOUtils.toByteArray(inputStream);
payload = new String(bytes, "UTF-8");
} catch (IOException e) {
log.error("Stripe webhook payload error", e);
response.setStatus(400);
return "";
}
// String payload = request.body();
Event event = null;
// try {
// event = ApiResource.GSON.fromJson(payload, Event.class);
// } catch (JsonSyntaxException e) {
// // Invalid payload
// System.out.println("⚠️ Webhook error while parsing basic request.");
// response.status(400);
// return "";
// }
String sigHeader = request.getHeader("Stripe-Signature");
if (endpointSecret != null && sigHeader != null) {
// Only verify the event if you have an endpoint secret defined.
// Otherwise use the basic event deserialized with GSON.
try {
event = Webhook.constructEvent(
payload, sigHeader, endpointSecret
);
} catch (SignatureVerificationException e) {
// Invalid signature
log.error("Webhook error while validating signature.", 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().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.
}
// Handle the event
switch (event.getType()) {
case "payment_intent.succeeded":
PaymentIntent paymentIntent = (PaymentIntent) stripeObject;
System.out.println("Payment for " + paymentIntent.getAmount() + " succeeded.");
// Then define and call a method to handle the successful payment intent.
// handlePaymentIntentSucceeded(paymentIntent);
break;
case "checkout.session.completed":
// TreeMap data = JSONObject.parseObject(dataObjectDeserializer.getRawJson(), TreeMap.class);
// String preOrderId = data.get("client_reference_id").toString();
Session sessionEvent= (Session) stripeObject;
fulfillCheckout(sessionEvent);
// Then define and call a method to handle the successful attachment of a PaymentMethod.
// handlePaymentMethodAttached(paymentMethod);
break;
default:
log.error("Unhandled event type: {}", event.getType());
break;
}
response.setStatus(200);
return "";
}
public void fulfillCheckout(Session sessionEvent) {
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
Stripe.apiKey = stripeBean.getSecretKey();
String sessionId = sessionEvent.getId();
log.info("Fulfilling Checkout Session {}", sessionId);
// Make this function safe to run multiple times,
// even concurrently, with the same session ID
// Make sure fulfillment hasn't already been
// performed for this Checkout Session
// Retrieve the Checkout Session from the API with line_items expanded
SessionRetrieveParams params =
SessionRetrieveParams.builder()
.addExpand("line_items")
.build();
Session checkoutSession = null;
try {
checkoutSession = Session.retrieve(sessionId, params, null);
} catch (StripeException e) {
log.error("Stripe webhook retrieve error", e);
throw new RuntimeException(e);
}
// Check the Checkout Session's payment_status property
// to determine if fulfillment should be performed
if (!Objects.equals(checkoutSession.getPaymentStatus(), "unpaid")) {
// Perform fulfillment of the line items
// Record/save fulfillment status for this
// Checkout Session
remoteUUserService.paySuccess(Long.valueOf(sessionEvent.getClientReferenceId()), "inner");
}
}
private UOrderVo getByOrderId(Long orderId) {
R<UOrderVo> orderRes = remoteUUserService.getOrderById(orderId);
UOrderVo orderVo = orderRes.getData();
return orderVo;
}
}

View File

@@ -77,3 +77,8 @@ paypal:
client-id: AfPgwFAmo9K7KCqiiGpNRCyQMSxI6V33eH-nEMnVndJNVEYOEOEn5wSPkHUybfzcjDLnBejt-RKnIfqX
client_secret: EOYQzSGuMaTMWodcppZUTz9v3H9j38yYiv8bmj4kLZl5NiSUJ0AJJuGlA1CU4oDtEX6jNdGNhsMCiAcN
sandBox: true
stripe:
secret-key: sk_test_51RHGN8FwutpVO5TqqmAkJNYMlWDPgwj4NVKPxcPKEXMGSPpEZ4yKwpGancV1vyPP74Pk3ETPUdAws0CfiH1jTN9v00kQ64suj5
domain: http://localhost:8085
endpoint-secret: whsec_rD5GFCx37wIS3Ag67ocPHWoD2WGIfWyC