feat: stripe支付
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user