|
|
@@ -291,11 +291,42 @@ public class PaymentOrderService {
|
|
|
private PrepayResult createPrepay(PaymentChannel channel, String orderNo,
|
|
|
java.math.BigDecimal amount, String subject, String body) {
|
|
|
return switch (channel) {
|
|
|
- case WECHAT -> createWechatPrepay(orderNo, amount, subject);
|
|
|
+ case WECHAT -> createWechatPrepay(orderNo, amount, subject, body);
|
|
|
case ALIPAY -> createAlipayPrepay(orderNo, amount, subject, body);
|
|
|
};
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 构建微信支付 description,将商品标题与详细描述合并。
|
|
|
+ * 微信 Native 下单的 description 字段上限为 127 字节(UTF-8),超出时自动截断。
|
|
|
+ */
|
|
|
+ private String buildWechatDescription(String subject, String body) {
|
|
|
+ if (body == null || body.isBlank()) {
|
|
|
+ return truncateUtf8(subject, 127);
|
|
|
+ }
|
|
|
+ String combined = subject.trim() + " - " + body.trim();
|
|
|
+ return truncateUtf8(combined, 127);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 按 UTF-8 字节长度截断字符串,避免截断到多字节字符中间。
|
|
|
+ */
|
|
|
+ private String truncateUtf8(String value, int maxBytes) {
|
|
|
+ if (value == null) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ byte[] bytes = value.getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
|
|
+ if (bytes.length <= maxBytes) {
|
|
|
+ return value;
|
|
|
+ }
|
|
|
+ int cut = maxBytes;
|
|
|
+ // 避免截断到多字节字符中间:向前回退直到是合法 UTF-8 起始字节
|
|
|
+ while (cut > 0 && (bytes[cut] & 0xC0) == 0x80) {
|
|
|
+ cut--;
|
|
|
+ }
|
|
|
+ return new String(bytes, 0, cut, java.nio.charset.StandardCharsets.UTF_8);
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 微信Native支付预下单(API v3)
|
|
|
*
|
|
|
@@ -303,7 +334,7 @@ public class PaymentOrderService {
|
|
|
* 返回 codeUrl(二维码链接),前端可直接生成二维码展示。
|
|
|
* 用户扫码后在微信App中完成付款,微信异步回调 notifyUrl。
|
|
|
*/
|
|
|
- private PrepayResult createWechatPrepay(String orderNo, java.math.BigDecimal amount, String subject) {
|
|
|
+ private PrepayResult createWechatPrepay(String orderNo, java.math.BigDecimal amount, String subject, String body) {
|
|
|
PaymentProperties.Wechat wc = paymentProperties.getWechat();
|
|
|
if (wc.getAppId() == null || wc.getMchId() == null) {
|
|
|
throw new BusinessException(ErrorCode.PAY_CHANNEL_NOT_CONFIGURED,
|
|
|
@@ -319,7 +350,7 @@ public class PaymentOrderService {
|
|
|
try {
|
|
|
WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
|
|
|
request.setOutTradeNo(orderNo);
|
|
|
- request.setDescription(subject);
|
|
|
+ request.setDescription(buildWechatDescription(subject, body));
|
|
|
request.setNotifyUrl(wc.getNotifyUrl());
|
|
|
|
|
|
// 金额单位为分
|
|
|
@@ -418,16 +449,27 @@ public class PaymentOrderService {
|
|
|
}
|
|
|
|
|
|
private OrderResponse toResponse(PaymentOrder order) {
|
|
|
+ PaymentPlan plan = paymentPlanService.getPlanById(order.getPlanId());
|
|
|
String hint = PaymentStatus.PENDING.name().equals(order.getStatus())
|
|
|
? "请在" + paymentProperties.getOrderExpireMinutes() + "分钟内完成付款,超时订单将自动过期" : null;
|
|
|
+
|
|
|
+ // 待支付订单补充二维码与支付链接,支持重新查询后继续支付
|
|
|
+ String qrCodeBase64 = null;
|
|
|
+ String payUrl = null;
|
|
|
+ if (PaymentStatus.PENDING.name().equals(order.getStatus())
|
|
|
+ && order.getQrCodeUrl() != null && !order.getQrCodeUrl().isBlank()) {
|
|
|
+ qrCodeBase64 = qrCodeService.generateBase64(order.getQrCodeUrl());
|
|
|
+ payUrl = order.getQrCodeUrl();
|
|
|
+ }
|
|
|
+
|
|
|
return OrderResponse.builder()
|
|
|
.orderNo(order.getOrderNo())
|
|
|
- .planName(null) // 由调用方补充
|
|
|
+ .planName(plan != null ? plan.getPlanName() : null)
|
|
|
.amount(order.getAmount())
|
|
|
.channel(order.getChannel())
|
|
|
.status(order.getStatus())
|
|
|
- .qrCodeBase64(null)
|
|
|
- .payUrl(null) // 查询场景不再返回,仅创建时提供
|
|
|
+ .qrCodeBase64(qrCodeBase64)
|
|
|
+ .payUrl(payUrl)
|
|
|
.expireTime(order.getExpireTime())
|
|
|
.expireHint(hint)
|
|
|
.createTime(order.getCreateTime())
|