| | |
| | | import cc.mrbird.febs.common.enumerates.OrderStatusEnum; |
| | | import cc.mrbird.febs.common.exception.FebsException; |
| | | import cc.mrbird.febs.common.utils.ValidateEntityUtils; |
| | | import cc.mrbird.febs.mall.entity.MallMoneyFlow; |
| | | import cc.mrbird.febs.mall.entity.MallOrderInfo; |
| | | import cc.mrbird.febs.mall.mapper.MallMoneyFlowMapper; |
| | | import cc.mrbird.febs.mall.mapper.MallOrderInfoMapper; |
| | | import cc.mrbird.febs.mall.service.IApiMallMemberWalletService; |
| | | import cn.hutool.core.date.DateUtil; |
| | | import cn.hutool.json.JSONObject; |
| | | import cn.hutool.json.JSONUtil; |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import io.swagger.annotations.Api; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.commons.codec.digest.DigestUtils; |
| | | import org.springframework.stereotype.Controller; |
| | | import org.springframework.ui.Model; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RequestParam; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.io.IOException; |
| | | import java.security.MessageDigest; |
| | | import java.security.NoSuchAlgorithmException; |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | |
| | | @Slf4j |
| | | @RestController |
| | | @Controller |
| | | //@RestController |
| | | @Api(value = "FiuuReturnController", tags = "FIUU支付-ReturnURL") |
| | | @RequestMapping(value = "/api/fuPayReturn") |
| | | public class FiuuReturnController { |
| | |
| | | private static final String SECRET_KEY = "59c709fc18978a6a83b87f05d37cecbf"; |
| | | @Resource |
| | | private MallOrderInfoMapper mallOrderInfoMapper; |
| | | @Resource |
| | | private MallMoneyFlowMapper mallMoneyFlowMapper; |
| | | @Resource |
| | | private IApiMallMemberWalletService memberWalletService; |
| | | |
| | | // Java 通知接口 暂时停止使用 |
| | | @PostMapping("/callback") |
| | | public void handlePaymentCallback( |
| | | @PostMapping("/payment/callback") |
| | | public String handlePaymentCallback( |
| | | @RequestParam("amount") String amount, |
| | | @RequestParam("orderid") String orderId, |
| | | @RequestParam("tranID") String tranId, |
| | | @RequestParam("status") String status, |
| | | @RequestParam("domain") String domain, |
| | | @RequestParam("currency") String currency, |
| | | @RequestParam("appcode") String appcode, |
| | | @RequestParam("paydate") String payDate, |
| | | @RequestParam("approcode") String appCode, |
| | | @RequestParam("skey") String receivedSkey) throws IOException{ |
| | | @RequestParam("skey") String receivedSkey, |
| | | Model model) { |
| | | |
| | | // 计算 skey 验证 |
| | | String calculatedSkey = calculateSkey(tranId, orderId, status, domain, amount, currency, payDate, appCode); |
| | | MallOrderInfo mallOrderInfo = ValidateEntityUtils |
| | | .ensureColumnReturnEntity(orderId, MallOrderInfo::getId, mallOrderInfoMapper::selectOne, "订单不存在"); |
| | | log.info("callback status: {}", status); |
| | | log.info("callback skey: {}", receivedSkey); |
| | | log.info("callback calculatedSkey: {}", calculatedSkey); |
| | | log.info("callback payResult: {}", mallOrderInfo.getPayResult()); |
| | | if("1".equals(mallOrderInfo.getPayResult())){ |
| | | return; |
| | | // 1. 格式化amount为两位小数(确保与Fiuu传递的格式一致) |
| | | BigDecimal amountDecimal; |
| | | try { |
| | | amountDecimal = new BigDecimal(amount).setScale(2, RoundingMode.HALF_UP); |
| | | } catch (NumberFormatException e) { |
| | | throw new FebsException("金额格式错误: " + amount); |
| | | } |
| | | String formattedAmount = amountDecimal.toPlainString(); // 例如 "100.00" |
| | | |
| | | // 2. 生成preSkey(严格按照参数顺序拼接) |
| | | log.info("callback Parameters for preSkey: tranId={}, orderId={}, status={}, domain={}, amount={}, currency={}", tranId, orderId, status, domain, amount, currency); |
| | | // 第一步哈希:pre_skey = md5(txnID + orderID + status + domain + amount + currency) |
| | | String preSkeyInput = tranId + orderId + status + domain + formattedAmount + currency; |
| | | String preSkey = DigestUtils.md5Hex(preSkeyInput); |
| | | log.info("callback preSkey生成参数: {}", preSkeyInput); |
| | | log.info("callback preSkey计算结果: {}", preSkey); |
| | | log.info("callback Parameters for calculatedSkey: payDate={}, domain={}, preSkey={}, appcode={}, SECRET_KEY={}", payDate, domain, preSkey, appcode, SECRET_KEY); |
| | | // 第二步哈希:skey = md5(paydate + domain + pre_skey + appcode + secret_key) |
| | | String skeyInput = payDate + domain + preSkey + appcode + SECRET_KEY; |
| | | String calculatedSkey = DigestUtils.md5Hex(skeyInput); |
| | | |
| | | log.info("callback skey生成参数: {}", skeyInput); |
| | | log.info("callback callback status: {}", status); |
| | | log.info("callback receivedSkey: {}", receivedSkey); |
| | | log.info("callback calculatedSkey: {}", calculatedSkey); |
| | | |
| | | if (!calculatedSkey.equalsIgnoreCase(receivedSkey)) { |
| | | // 记录安全警告日志 |
| | | throw new FebsException("订单回调失败,---"+orderId); |
| | | throw new FebsException("订单回调失败,---" + orderId); |
| | | } |
| | | if ("00".equals(status)) { |
| | | updateOrderStatus(orderId, status, amount, payDate, tranId); |
| | | return; |
| | | |
| | | // 将支付结果信息传递给支付成功页面 |
| | | model.addAttribute("amount", amount); |
| | | model.addAttribute("orderId", orderId); |
| | | model.addAttribute("tranId", tranId); |
| | | model.addAttribute("status", status); |
| | | model.addAttribute("currency", currency); |
| | | model.addAttribute("payDate", payDate); |
| | | |
| | | MallMoneyFlow mallMoneyFlow = mallMoneyFlowMapper.selectOne(new LambdaQueryWrapper<MallMoneyFlow>().eq(MallMoneyFlow::getOrderNo, orderId)); |
| | | if (mallMoneyFlow != null) { |
| | | if(1 == mallMoneyFlow.getStatus()){ |
| | | memberWalletService.addBalance(mallMoneyFlow.getAmount(),mallMoneyFlow.getMemberId()); |
| | | // 更新订单状态为已支付 |
| | | mallMoneyFlow.setStatus(2); |
| | | mallMoneyFlowMapper.updateById(mallMoneyFlow); |
| | | } |
| | | }else{ |
| | | MallOrderInfo mallOrderInfo = mallOrderInfoMapper.selectById(Long.parseLong(orderId)); |
| | | if(mallOrderInfo != null){ |
| | | if("1" == mallOrderInfo.getPayResult()){ |
| | | return "payment-success"; |
| | | } |
| | | updateOrderStatus(orderId, status, amount, payDate, tranId); |
| | | } |
| | | } |
| | | return "payment-success"; |
| | | } |
| | | |
| | | private String calculateSkey(String tranId, String orderId, String status, |
| | | String domain, String amount, String currency, |
| | | String payDate, String appCode) { |
| | | public static void main(String[] args) { |
| | | String secretKey = "59c709fc18978a6a83b87f05d37cecbf"; |
| | | String ss = "{\"nbcb\":\"2\",\"tranID\":\"2693805878\",\"amount\":\"1.00\",\"orderid\":\"359\",\"channel\":\"alipay\",\"paydate\":\"2025-02-10 10:24:20\",\"error_desc\":\"\",\"domain\":\"e2umart01\",\"skey\":\"29913d009b74cb8bc3650aed84394cfe\",\"error_code\":\"\",\"currency\":\"RM\",\"appcode\":\"2025021022001302841422314686\",\"status\":\"00\"}"; |
| | | |
| | | JSONObject params = JSONUtil.parseObj(ss); |
| | | |
| | | // 按API文档生成skey |
| | | String tranID = params.getStr("tranID"); |
| | | String orderId = params.getStr("orderid"); |
| | | String status = params.getStr("status"); |
| | | String domain = params.getStr("domain"); |
| | | String amount = params.getStr("amount"); |
| | | String currency = params.getStr("currency"); |
| | | String appcode = params.getStr("appcode"); |
| | | String paydate = params.getStr("paydate"); |
| | | String receivedSkey = params.getStr("skey"); |
| | | |
| | | // 第一步哈希:pre_skey = md5(txnID + orderID + status + domain + amount + currency) |
| | | String preSkey = DigestUtils.md5Hex(tranID + orderId + status + domain + amount + currency); |
| | | |
| | | log.info("notify preSkey: {}", preSkey); |
| | | // 第二步哈希:skey = md5(paydate + domain + pre_skey + appcode + secretKey) |
| | | String calculatedSkey = DigestUtils.md5Hex(paydate + domain + preSkey + appcode + secretKey); |
| | | |
| | | log.info("notify calculatedSkey: {}", calculatedSkey); |
| | | |
| | | // 1. 格式化amount为两位小数(确保与Fiuu传递的格式一致) |
| | | BigDecimal amountDecimal; |
| | | try { |
| | | // 第一步哈希计算 |
| | | String preSkey = tranId + orderId + status + domain + amount + currency; |
| | | String preSkeyHash = md5(preSkey); |
| | | |
| | | // 第二步哈希计算 |
| | | String finalInput = payDate + domain + preSkeyHash + appCode + SECRET_KEY; |
| | | return md5(finalInput); |
| | | } catch (NoSuchAlgorithmException e) { |
| | | throw new RuntimeException("MD5算法不可用", e); |
| | | amountDecimal = new BigDecimal(amount).setScale(2, RoundingMode.HALF_UP); |
| | | } catch (NumberFormatException e) { |
| | | throw new FebsException("金额格式错误: " + amount); |
| | | } |
| | | } |
| | | String formattedAmount = amountDecimal.toPlainString(); // 例如 "100.00" |
| | | |
| | | private String md5(String input) throws NoSuchAlgorithmException { |
| | | MessageDigest md = MessageDigest.getInstance("MD5"); |
| | | byte[] hashBytes = md.digest(input.getBytes()); |
| | | // 2. 生成preSkey(严格按照参数顺序拼接) |
| | | log.info("callback Parameters for preSkey: tranId={}, orderId={}, status={}, domain={}, amount={}, currency={}", tranID, orderId, status, domain, amount, currency); |
| | | // 第一步哈希:pre_skey = md5(txnID + orderID + status + domain + amount + currency) |
| | | String preSkeyInput = tranID + orderId + status + domain + formattedAmount + currency; |
| | | String preSkey1 = DigestUtils.md5Hex(preSkeyInput); |
| | | log.info("callback preSkey生成参数: {}", preSkeyInput); |
| | | log.info("callback preSkey计算结果: {}", preSkey1); |
| | | log.info("callback Parameters for calculatedSkey: payDate={}, domain={}, preSkey={}, appcode={}, SECRET_KEY={}", paydate, domain, preSkey, appcode, SECRET_KEY); |
| | | // 第二步哈希:skey = md5(paydate + domain + pre_skey + appcode + secret_key) |
| | | String skeyInput = paydate + domain + preSkey + appcode + secretKey; |
| | | String calculatedSkey1 = DigestUtils.md5Hex(skeyInput); |
| | | |
| | | StringBuilder hexString = new StringBuilder(); |
| | | for (byte b : hashBytes) { |
| | | String hex = Integer.toHexString(0xff & b); |
| | | if (hex.length() == 1) hexString.append('0'); |
| | | hexString.append(hex); |
| | | } |
| | | return hexString.toString(); |
| | | log.info("callback skey生成参数: {}", skeyInput); |
| | | log.info("callback callback status: {}", status); |
| | | log.info("callback receivedSkey: {}", receivedSkey); |
| | | log.info("callback calculatedSkey: {}", calculatedSkey1); |
| | | |
| | | } |
| | | |
| | | private void updateOrderStatus(String orderId, String status, String amount, String paydate, String tranID) { |