31 changed files with 877 additions and 175 deletions
@ -0,0 +1,43 @@ |
|||
package cc.hiver.mall.controller; |
|||
|
|||
import cc.hiver.core.common.utils.ResultUtil; |
|||
import cc.hiver.core.common.vo.Result; |
|||
import cc.hiver.mall.entity.MallRefundRecord; |
|||
import cc.hiver.mall.service.mybatis.MallRefundRecordService; |
|||
import io.swagger.annotations.Api; |
|||
import io.swagger.annotations.ApiOperation; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.web.bind.annotation.PostMapping; |
|||
import org.springframework.web.bind.annotation.RequestBody; |
|||
import org.springframework.web.bind.annotation.RequestMapping; |
|||
import org.springframework.web.bind.annotation.RestController; |
|||
|
|||
/** |
|||
* 订单接口 |
|||
*/ |
|||
@Slf4j |
|||
@RestController |
|||
@Api(tags = "订单售后接口") |
|||
@RequestMapping("/hiver/mall/refund") |
|||
public class MallRefundRecordController { |
|||
|
|||
@Autowired |
|||
private MallRefundRecordService mallRefundRecordService; |
|||
|
|||
/** |
|||
* 下单(普通购买 / 发起拼团 / 参团) |
|||
*/ |
|||
@PostMapping("/create") |
|||
@ApiOperation(value = "售后提交接口") |
|||
public Result create(@RequestBody MallRefundRecord mallRefundRecord) { |
|||
try { |
|||
mallRefundRecordService.create(mallRefundRecord); |
|||
return ResultUtil.success("提交售后成功"); |
|||
} catch (Exception e) { |
|||
log.error("下单失败: {}", e.getMessage(), e); |
|||
return ResultUtil.error(e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,247 @@ |
|||
package cc.hiver.mall.controller; |
|||
|
|||
import cc.hiver.mall.entity.MallOrder; |
|||
import cc.hiver.mall.service.mybatis.MallOrderService; |
|||
import cc.hiver.mall.serviceimpl.UnifiedOrderService; |
|||
import cc.hiver.mall.utils.KeyUtils; |
|||
import cc.hiver.mall.utils.WechatPayConfig; |
|||
import cc.hiver.mall.utils.WechatPaySigner; |
|||
import com.fasterxml.jackson.databind.ObjectMapper; |
|||
import okhttp3.MediaType; |
|||
import okhttp3.OkHttpClient; |
|||
import okhttp3.Request; |
|||
import okhttp3.Response; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.core.io.Resource; |
|||
import org.springframework.core.io.ResourceLoader; |
|||
import org.springframework.http.ResponseEntity; |
|||
import org.springframework.web.bind.annotation.PostMapping; |
|||
import org.springframework.web.bind.annotation.RequestBody; |
|||
import org.springframework.web.bind.annotation.RequestMapping; |
|||
import org.springframework.web.bind.annotation.RestController; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import java.io.ByteArrayOutputStream; |
|||
import java.io.IOException; |
|||
import java.io.InputStream; |
|||
import java.nio.charset.StandardCharsets; |
|||
import java.security.PrivateKey; |
|||
import java.util.Base64; |
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
import java.util.UUID; |
|||
|
|||
@RestController |
|||
@RequestMapping("/hiver/api/wechat/pay") |
|||
public class WechatPayController { |
|||
|
|||
private static final Logger log = LoggerFactory.getLogger(WechatPayController.class); |
|||
|
|||
@Autowired |
|||
private UnifiedOrderService orderService; |
|||
|
|||
@Autowired |
|||
private WechatPayConfig config; |
|||
|
|||
@Autowired |
|||
private MallOrderService mallOrderService; |
|||
|
|||
@Autowired |
|||
private WechatPaySigner signer; |
|||
|
|||
@Autowired |
|||
private ResourceLoader resourceLoader; |
|||
|
|||
private PrivateKey privateKey; |
|||
|
|||
private final OkHttpClient httpClient = new OkHttpClient(); |
|||
private final ObjectMapper objectMapper = new ObjectMapper(); |
|||
|
|||
@PostConstruct |
|||
public void init() throws Exception { |
|||
Resource resource = resourceLoader.getResource(config.getPrivateKeyPath()); |
|||
byte[] keyBytes; |
|||
try (InputStream is = resource.getInputStream()) { |
|||
keyBytes = readAllBytes(is); |
|||
} |
|||
|
|||
String key = new String(keyBytes, StandardCharsets.UTF_8) |
|||
.replace("-----BEGIN PRIVATE KEY-----", "") |
|||
.replace("-----END PRIVATE KEY-----", "") |
|||
.replaceAll("\\s+", ""); |
|||
|
|||
byte[] decodedKey = Base64.getDecoder().decode(key); |
|||
privateKey = KeyUtils.loadPrivateKey(decodedKey); |
|||
} |
|||
|
|||
private byte[] readAllBytes(InputStream inputStream) throws IOException { |
|||
ByteArrayOutputStream buffer = new ByteArrayOutputStream(); |
|||
byte[] data = new byte[1024]; |
|||
int nRead; |
|||
while ((nRead = inputStream.read(data, 0, data.length)) != -1) { |
|||
buffer.write(data, 0, nRead); |
|||
} |
|||
return buffer.toByteArray(); |
|||
} |
|||
|
|||
@PostMapping("/unified-order") |
|||
public ResponseEntity<Map<String, String>> jsapiPay(@RequestBody Map<String, String> request) throws Exception { |
|||
String outTradeNo = "ORDER_" + request.get("outTradeNo"); |
|||
String description = request.get("description"); |
|||
String openid = request.get("openid"); |
|||
long amount = Long.parseLong(request.get("amount")); // 单位:分
|
|||
|
|||
Map<String, Object> orderResult = orderService.createJsapiOrder(outTradeNo, description, openid, amount); |
|||
String prepayId = (String) orderResult.get("prepay_id"); |
|||
|
|||
if (prepayId == null) { |
|||
Map<String, String> payParams = new HashMap<>(); |
|||
payParams.put("code","101"); |
|||
payParams.put("message","调起微信支付失败"); |
|||
return ResponseEntity.ok(payParams); |
|||
}else{ |
|||
// 2. 生成前端支付参数
|
|||
Map<String, String> payParams = buildPaySign(prepayId); |
|||
payParams.put("code","200"); |
|||
return ResponseEntity.ok(payParams); |
|||
} |
|||
} |
|||
|
|||
@PostMapping("/paySuccess") |
|||
public ResponseEntity<Map<String, String>> paySuccess(@RequestBody Map<String, String> request) throws Exception { |
|||
//订单支付成功逻辑
|
|||
mallOrderService.paySuccess(request.get("outTradeNo")); |
|||
Map<String, String> payParams = new HashMap<>(); |
|||
payParams.put("code","200"); |
|||
payParams.put("message","支付成功订单更新"); |
|||
return ResponseEntity.ok(payParams); |
|||
} |
|||
|
|||
public Map<String, String> buildPaySign(String prepayId) throws Exception { |
|||
// 1. 生成参数
|
|||
String timeStamp = String.valueOf(System.currentTimeMillis() / 1000); // 秒
|
|||
String nonceStr = UUID.randomUUID().toString().replace("-", "").substring(0, 32); |
|||
String packageValue = "prepay_id=" + prepayId; |
|||
String signType = "RSA"; |
|||
|
|||
// 2. 构建签名字符串
|
|||
String message = String.join("\n", |
|||
config.getAppId(), |
|||
timeStamp, |
|||
nonceStr, |
|||
packageValue, |
|||
"" // 最后一个 \n
|
|||
); |
|||
|
|||
// 3. 使用商户私钥签名(和 API v3 同一套私钥)
|
|||
String paySign = KeyUtils.signWithSha256Rsa(message, privateKey); // ← 复用之前的签名方法
|
|||
|
|||
// 4. 返回给前端
|
|||
Map<String, String> result = new HashMap<>(); |
|||
result.put("timeStamp", timeStamp); |
|||
result.put("nonceStr", nonceStr); |
|||
result.put("package", packageValue); |
|||
result.put("signType", signType); |
|||
result.put("paySign", paySign); |
|||
return result; |
|||
} |
|||
|
|||
/** |
|||
* 微信退款通用方法 |
|||
* 传入 orderId,根据订单信息发起微信支付退款(全额退款) |
|||
*/ |
|||
@PostMapping("/refund") |
|||
public ResponseEntity<Map<String, String>> refund(@RequestBody Map<String, String> request) { |
|||
Map<String, String> result = new HashMap<>(); |
|||
String orderId = request.get("orderId"); |
|||
|
|||
try { |
|||
// 1. 查询订单信息
|
|||
MallOrder order = mallOrderService.getById(orderId); |
|||
if (order == null) { |
|||
result.put("code", "101"); |
|||
result.put("message", "订单不存在"); |
|||
return ResponseEntity.ok(result); |
|||
} |
|||
|
|||
// 2. 构建退款请求参数
|
|||
// 原支付交易号(与下单时一致,带 ORDER_ 前缀)
|
|||
String outTradeNo = "ORDER_" + orderId; |
|||
// 退款单号(唯一,使用 REFUND_ + orderId + 时间戳避免重复)
|
|||
String outRefundNo = "REFUND_" + orderId + "_" + System.currentTimeMillis(); |
|||
// 订单总金额(元 → 分)
|
|||
long totalFee = order.getTotalAmount() |
|||
.multiply(new java.math.BigDecimal("100")) |
|||
.longValue(); |
|||
// 退款金额(默认全额退款,如果前端传了 refundAmount 则使用前端的值)
|
|||
long refundFee = totalFee; |
|||
if (request.get("refundAmount") != null) { |
|||
refundFee = new java.math.BigDecimal(request.get("refundAmount")) |
|||
.multiply(new java.math.BigDecimal("100")) |
|||
.longValue(); |
|||
} |
|||
|
|||
// 退款原因
|
|||
String reason = request.get("reason") != null ? request.get("reason") : "订单退款"; |
|||
|
|||
// 3. 构建请求 Body
|
|||
Map<String, Object> reqBody = new HashMap<>(); |
|||
reqBody.put("out_trade_no", outTradeNo); |
|||
reqBody.put("out_refund_no", outRefundNo); |
|||
reqBody.put("reason", reason); |
|||
|
|||
Map<String, Object> amountMap = new HashMap<>(); |
|||
amountMap.put("refund", refundFee); |
|||
amountMap.put("total", totalFee); |
|||
amountMap.put("currency", "CNY"); |
|||
reqBody.put("amount", amountMap); |
|||
|
|||
String jsonBody = objectMapper.writeValueAsString(reqBody); |
|||
|
|||
// 4. 签名并调用微信退款 API
|
|||
String refundUrl = WechatPayConfig.REFUND_URL; |
|||
String authorization = signer.sign("POST", refundUrl, jsonBody); |
|||
|
|||
// 【修改点】使用标准的 create 方法,参数顺序是 (MediaType, String)
|
|||
okhttp3.RequestBody body = okhttp3.RequestBody.create( |
|||
MediaType.parse("application/json; charset=utf-8"), |
|||
jsonBody |
|||
); |
|||
|
|||
Request httpRequest = new Request.Builder() |
|||
.url(refundUrl) |
|||
.post(body) |
|||
.addHeader("Authorization", "WECHATPAY2-SHA256-RSA2048 " + authorization) |
|||
.addHeader("Content-Type", "application/json") |
|||
.addHeader("Accept", "application/json") |
|||
.addHeader("User-Agent", "MyApp/1.0") |
|||
.build(); |
|||
|
|||
try (Response response = httpClient.newCall(httpRequest).execute()) { |
|||
String responseBody = response.body() != null ? response.body().string() : ""; |
|||
|
|||
if (response.isSuccessful()) { |
|||
// 退款申请成功(注意:微信退款是异步的,此处只表示申请提交成功)
|
|||
log.info("微信退款申请成功, orderId={}, outRefundNo={}, response={}", orderId, outRefundNo, responseBody); |
|||
result.put("code", "200"); |
|||
result.put("message", "退款申请成功"); |
|||
result.put("outRefundNo", outRefundNo); |
|||
result.put("refundResponse", responseBody); |
|||
} else { |
|||
log.error("微信退款申请失败, orderId={}, httpCode={}, response={}", orderId, response.code(), responseBody); |
|||
result.put("code", "102"); |
|||
result.put("message", "微信退款申请失败: " + responseBody); |
|||
} |
|||
} |
|||
|
|||
} catch (Exception e) { |
|||
log.error("微信退款异常, orderId={}", orderId, e); |
|||
result.put("code", "500"); |
|||
result.put("message", "退款异常: " + e.getMessage()); |
|||
} |
|||
|
|||
return ResponseEntity.ok(result); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
package cc.hiver.mall.dao.mapper; |
|||
|
|||
import cc.hiver.mall.entity.MallReturnOrderGoods; |
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
import org.springframework.stereotype.Repository; |
|||
|
|||
/** |
|||
* 订单商品详情 Mapper 接口 |
|||
*/ |
|||
@Repository |
|||
public interface MallReturnOrderGoodsMapper extends BaseMapper<MallReturnOrderGoods> { |
|||
|
|||
} |
|||
@ -1,5 +1,7 @@ |
|||
package cc.hiver.social.controller; |
|||
package cc.hiver.mall.serviceimpl; |
|||
|
|||
import cc.hiver.mall.utils.WechatPayConfig; |
|||
import cc.hiver.mall.utils.WechatPaySigner; |
|||
import com.fasterxml.jackson.databind.ObjectMapper; |
|||
import okhttp3.*; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
@ -1,15 +1,50 @@ |
|||
package cc.hiver.mall.serviceimpl.mybatis; |
|||
|
|||
import cc.hiver.mall.dao.mapper.MallRefundRecordMapper; |
|||
import cc.hiver.mall.dao.mapper.MallReturnOrderGoodsMapper; |
|||
import cc.hiver.mall.entity.MallOrder; |
|||
import cc.hiver.mall.entity.MallRefundRecord; |
|||
import cc.hiver.mall.service.mybatis.MallOrderService; |
|||
import cc.hiver.mall.service.mybatis.MallRefundRecordService; |
|||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
|||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Service; |
|||
|
|||
import java.util.Date; |
|||
|
|||
/** |
|||
* 退款记录 Service 实现 |
|||
*/ |
|||
@Service |
|||
public class MallRefundRecordServiceImpl extends ServiceImpl<MallRefundRecordMapper, MallRefundRecord> |
|||
implements MallRefundRecordService { |
|||
|
|||
@Autowired |
|||
private MallRefundRecordMapper mallRefundRecordMapper; |
|||
|
|||
@Autowired |
|||
private MallReturnOrderGoodsMapper mallReturnOrderGoodsMapper; |
|||
|
|||
@Autowired |
|||
private MallOrderService mallOrderService; |
|||
|
|||
@Override |
|||
public void create(MallRefundRecord mallRefundRecord) { |
|||
//更新订单为 待售后
|
|||
LambdaUpdateWrapper<MallOrder> oUw = new LambdaUpdateWrapper<>(); |
|||
oUw.eq(MallOrder::getId, mallRefundRecord.getOrderId()) |
|||
.set(MallOrder::getStatus, 11); |
|||
mallOrderService.update(oUw); |
|||
mallRefundRecord.setStatus(3); // 申请售后
|
|||
mallRefundRecord.setCreateTime(new Date()); |
|||
mallRefundRecordMapper.insert(mallRefundRecord); |
|||
|
|||
if(!mallRefundRecord.getItems().isEmpty()){ |
|||
mallRefundRecord.getItems().forEach(item -> { |
|||
mallReturnOrderGoodsMapper.insert(item); |
|||
}); |
|||
} |
|||
|
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,25 @@ |
|||
package cc.hiver.mall.utils; |
|||
|
|||
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; |
|||
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; |
|||
|
|||
import java.security.PrivateKey; |
|||
import java.security.Signature; |
|||
import java.util.Base64; |
|||
|
|||
public class KeyUtils { |
|||
|
|||
public static PrivateKey loadPrivateKey(byte[] keyBytes) throws Exception { |
|||
PrivateKeyInfo pkInfo = PrivateKeyInfo.getInstance(keyBytes); |
|||
JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); |
|||
return converter.getPrivateKey(pkInfo); |
|||
} |
|||
|
|||
public static String signWithSha256Rsa(String message, PrivateKey privateKey) throws Exception { |
|||
Signature sign = Signature.getInstance("SHA256withRSA"); |
|||
sign.initSign(privateKey); |
|||
sign.update(message.getBytes("utf-8")); |
|||
byte[] signed = sign.sign(); |
|||
return Base64.getEncoder().encodeToString(signed); |
|||
} |
|||
} |
|||
@ -1,4 +1,4 @@ |
|||
package cc.hiver.social.controller; |
|||
package cc.hiver.mall.utils; |
|||
|
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.context.annotation.Configuration; |
|||
@ -0,0 +1,92 @@ |
|||
package cc.hiver.mall.utils; |
|||
|
|||
import com.fasterxml.jackson.databind.ObjectMapper; |
|||
import okhttp3.MediaType; |
|||
import okhttp3.OkHttpClient; |
|||
import okhttp3.Request; |
|||
import okhttp3.Response; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
|
|||
/** |
|||
* 微信支付工具类 |
|||
* |
|||
* @author Yazhi Li |
|||
*/ |
|||
@Component |
|||
public class WechatPayUtil { |
|||
@Autowired |
|||
private WechatPaySigner signer; |
|||
|
|||
private static final Logger log = LoggerFactory.getLogger(WechatPayUtil.class); |
|||
|
|||
private final OkHttpClient httpClient = new OkHttpClient(); |
|||
private final ObjectMapper objectMapper = new ObjectMapper(); |
|||
public Boolean refund(String orderId,long totalFee) { |
|||
try { |
|||
// 2. 构建退款请求参数
|
|||
// 原支付交易号(与下单时一致,带 ORDER_ 前缀)
|
|||
String outTradeNo = "ORDER_" + orderId; |
|||
// 退款单号(唯一,使用 REFUND_ + orderId + 时间戳避免重复)
|
|||
String outRefundNo = "REFUND_" + orderId + "_" + System.currentTimeMillis(); |
|||
|
|||
// 退款原因
|
|||
String reason = "订单退款"; |
|||
// 3. 构建请求 Body
|
|||
Map<String, Object> reqBody = new HashMap<>(); |
|||
reqBody.put("out_trade_no", outTradeNo); |
|||
reqBody.put("out_refund_no", outRefundNo); |
|||
reqBody.put("reason", reason); |
|||
|
|||
Map<String, Object> amountMap = new HashMap<>(); |
|||
amountMap.put("refund", totalFee); |
|||
amountMap.put("total", totalFee); |
|||
amountMap.put("currency", "CNY"); |
|||
reqBody.put("amount", amountMap); |
|||
|
|||
String jsonBody = objectMapper.writeValueAsString(reqBody); |
|||
|
|||
// 4. 签名并调用微信退款 API
|
|||
String refundUrl = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds"; |
|||
String authorization = signer.sign("POST", refundUrl, jsonBody); |
|||
|
|||
// 【修改点】使用标准的 create 方法,参数顺序是 (MediaType, String)
|
|||
okhttp3.RequestBody body = okhttp3.RequestBody.create( |
|||
MediaType.parse("application/json; charset=utf-8"), |
|||
jsonBody |
|||
); |
|||
|
|||
Request httpRequest = new Request.Builder() |
|||
.url(refundUrl) |
|||
.post(body) |
|||
.addHeader("Authorization", "WECHATPAY2-SHA256-RSA2048 " + authorization) |
|||
.addHeader("Content-Type", "application/json") |
|||
.addHeader("Accept", "application/json") |
|||
.addHeader("User-Agent", "MyApp/1.0") |
|||
.build(); |
|||
|
|||
try (Response response = httpClient.newCall(httpRequest).execute()) { |
|||
String responseBody = response.body() != null ? response.body().string() : ""; |
|||
|
|||
if (response.isSuccessful()) { |
|||
// 退款申请成功(注意:微信退款是异步的,此处只表示申请提交成功)
|
|||
log.info("微信退款申请成功, orderId={}, outRefundNo={}, response={}", orderId, outRefundNo, responseBody); |
|||
return true; |
|||
} else { |
|||
log.error("微信退款申请失败, orderId={}, httpCode={}, response={}", orderId, response.code(), responseBody); |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
} catch (Exception e) { |
|||
log.error("微信退款异常, orderId={}", orderId, e); |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -1,129 +0,0 @@ |
|||
package cc.hiver.social.controller; |
|||
|
|||
import cc.hiver.mall.service.mybatis.MallOrderService; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.core.io.Resource; |
|||
import org.springframework.core.io.ResourceLoader; |
|||
import org.springframework.http.ResponseEntity; |
|||
import org.springframework.web.bind.annotation.PostMapping; |
|||
import org.springframework.web.bind.annotation.RequestBody; |
|||
import org.springframework.web.bind.annotation.RequestMapping; |
|||
import org.springframework.web.bind.annotation.RestController; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import java.io.ByteArrayOutputStream; |
|||
import java.io.IOException; |
|||
import java.io.InputStream; |
|||
import java.nio.charset.StandardCharsets; |
|||
import java.security.PrivateKey; |
|||
import java.util.Base64; |
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
import java.util.UUID; |
|||
|
|||
@RestController |
|||
@RequestMapping("/hiver/api/wechat/pay") |
|||
public class WechatPayController { |
|||
@Autowired |
|||
private UnifiedOrderService orderService; |
|||
|
|||
@Autowired |
|||
private WechatPayConfig config; |
|||
|
|||
@Autowired |
|||
private MallOrderService mallOrderService; |
|||
|
|||
@Autowired |
|||
private ResourceLoader resourceLoader; |
|||
|
|||
private PrivateKey privateKey; |
|||
|
|||
@PostConstruct |
|||
public void init() throws Exception { |
|||
Resource resource = resourceLoader.getResource(config.getPrivateKeyPath()); |
|||
byte[] keyBytes; |
|||
try (InputStream is = resource.getInputStream()) { |
|||
keyBytes = readAllBytes(is); |
|||
} |
|||
|
|||
String key = new String(keyBytes, StandardCharsets.UTF_8) |
|||
.replace("-----BEGIN PRIVATE KEY-----", "") |
|||
.replace("-----END PRIVATE KEY-----", "") |
|||
.replaceAll("\\s+", ""); |
|||
|
|||
byte[] decodedKey = Base64.getDecoder().decode(key); |
|||
privateKey = KeyUtils.loadPrivateKey(decodedKey); |
|||
} |
|||
|
|||
private byte[] readAllBytes(InputStream inputStream) throws IOException { |
|||
ByteArrayOutputStream buffer = new ByteArrayOutputStream(); |
|||
byte[] data = new byte[1024]; |
|||
int nRead; |
|||
while ((nRead = inputStream.read(data, 0, data.length)) != -1) { |
|||
buffer.write(data, 0, nRead); |
|||
} |
|||
return buffer.toByteArray(); |
|||
} |
|||
|
|||
@PostMapping("/unified-order") |
|||
public ResponseEntity<Map<String, String>> jsapiPay(@RequestBody Map<String, String> request) throws Exception { |
|||
String outTradeNo = "ORDER_" + request.get("outTradeNo"); |
|||
String description = request.get("description"); |
|||
String openid = request.get("openid"); |
|||
long amount = Long.parseLong(request.get("amount")); // 单位:分
|
|||
|
|||
Map<String, Object> orderResult = orderService.createJsapiOrder(outTradeNo, description, openid, amount); |
|||
String prepayId = (String) orderResult.get("prepay_id"); |
|||
|
|||
if (prepayId == null) { |
|||
Map<String, String> payParams = new HashMap<>(); |
|||
payParams.put("code","101"); |
|||
payParams.put("message","调起微信支付失败"); |
|||
return ResponseEntity.ok(payParams); |
|||
}else{ |
|||
// 2. 生成前端支付参数
|
|||
Map<String, String> payParams = buildPaySign(prepayId); |
|||
payParams.put("code","200"); |
|||
return ResponseEntity.ok(payParams); |
|||
} |
|||
} |
|||
|
|||
@PostMapping("/paySuccess") |
|||
public ResponseEntity<Map<String, String>> paySuccess(@RequestBody Map<String, String> request) throws Exception { |
|||
//订单支付成功逻辑
|
|||
mallOrderService.paySuccess(request.get("outTradeNo")); |
|||
Map<String, String> payParams = new HashMap<>(); |
|||
payParams.put("code","200"); |
|||
payParams.put("message","支付成功订单更新"); |
|||
return ResponseEntity.ok(payParams); |
|||
} |
|||
|
|||
public Map<String, String> buildPaySign(String prepayId) throws Exception { |
|||
// 1. 生成参数
|
|||
String timeStamp = String.valueOf(System.currentTimeMillis() / 1000); // 秒
|
|||
String nonceStr = UUID.randomUUID().toString().replace("-", "").substring(0, 32); |
|||
String packageValue = "prepay_id=" + prepayId; |
|||
String signType = "RSA"; |
|||
|
|||
// 2. 构建签名字符串
|
|||
String message = String.join("\n", |
|||
config.getAppId(), |
|||
timeStamp, |
|||
nonceStr, |
|||
packageValue, |
|||
"" // 最后一个 \n
|
|||
); |
|||
|
|||
// 3. 使用商户私钥签名(和 API v3 同一套私钥)
|
|||
String paySign = KeyUtils.signWithSha256Rsa(message, privateKey); // ← 复用之前的签名方法
|
|||
|
|||
// 4. 返回给前端
|
|||
Map<String, String> result = new HashMap<>(); |
|||
result.put("timeStamp", timeStamp); |
|||
result.put("nonceStr", nonceStr); |
|||
result.put("package", packageValue); |
|||
result.put("signType", signType); |
|||
result.put("paySign", paySign); |
|||
return result; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue