| | |
| | | import cc.mrbird.febs.common.exception.FebsException; |
| | | import cc.mrbird.febs.common.utils.ValidateEntityUtils; |
| | | import cc.mrbird.febs.mall.entity.MallOrderInfo; |
| | | import cc.mrbird.febs.mall.entity.MallOrderItem; |
| | | import cc.mrbird.febs.mall.mapper.MallOrderInfoMapper; |
| | | import cc.mrbird.febs.pay.model.FIUUInitPayRequest; |
| | | import cn.hutool.core.date.DateUtil; |
| | | import cn.hutool.json.JSONUtil; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.commons.codec.digest.DigestUtils; |
| | | import org.apache.commons.collections.CollectionUtils; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import javax.annotation.Resource; |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | @Slf4j |
| | | @RestController |
| | | @Api(value = "FIUUController", tags = "FIUU支付") |
| | | @RequestMapping(value = "/api/fuPay") |
| | | public class FIUUController { |
| | | |
| | |
| | | public FebsResponse initPayment(@RequestBody FIUUInitPayRequest orderRequest) { |
| | | Long orderId = orderRequest.getOrderId(); |
| | | MallOrderInfo mallOrderInfo = ValidateEntityUtils.ensureColumnReturnEntity(orderId, MallOrderInfo::getId, mallOrderInfoMapper::selectOne, "订单不存在"); |
| | | ValidateEntityUtils.ensureEqual(mallOrderInfo.getPayResult(), "1", "订单已支付"); |
| | | ValidateEntityUtils.ensureNotEqual("1", mallOrderInfo.getPayResult(), "订单已支付"); |
| | | String amount = mallOrderInfo.getAmount().toString(); |
| | | String productNames = getProductNames(mallOrderInfo.getMemberId(), mallOrderInfo.getId()); |
| | | try { |
| | | String merchantId = "e2umart01"; |
| | | String verifyKey = "4e3a4ed58e62ddbfacf41f6d5ec56bf2"; |
| | | String returnUrl = "https://www.mye2u.com/api/fuPay/callback"; // 支付结果回调地址 |
| | | String returnUrl = "https://api.mye2u.com/api/fuPay/callback"; // 支付结果回调地址 |
| | | |
| | | // 生成 vcode(MD5(amount + merchantId + orderId + verifyKey)) |
| | | String vcode = DigestUtils.md5Hex( |
| | |
| | | params.put("bill_name", orderRequest.getBuyerName()); |
| | | params.put("bill_email", orderRequest.getBuyerEmail()); |
| | | params.put("bill_mobile", orderRequest.getBuyerMobile()); |
| | | params.put("bill_desc", mallOrderInfo.getOrderNo()); |
| | | params.put("bill_desc", productNames); |
| | | params.put("currency", "MYR"); // 默认 MYR |
| | | params.put("vcode", vcode); |
| | | params.put("returnurl", returnUrl); |
| | |
| | | } |
| | | } |
| | | |
| | | // Java 回调接口 |
| | | /** |
| | | * FIUU 回调接口 |
| | | * @param request |
| | | */ |
| | | @PostMapping("/notify") |
| | | public void handlePaymentNotification(HttpServletRequest request) { |
| | | // 1. 从POST请求中获取参数 |
| | | Map<String, String> params = new HashMap<>(); |
| | | request.getParameterMap().forEach((key, values) -> params.put(key, values[0])); |
| | | log.info("notify: {}", JSONUtil.parseObj(params)); |
| | | |
| | | // 2. 验证skey的完整性 |
| | | boolean isValid = verifySkey(params); |
| | | if (!isValid) { |
| | | return; |
| | | } |
| | | |
| | | // 3. 解析关键参数 |
| | | String status = params.get("status"); |
| | | String orderId = params.get("orderid"); |
| | | String amount = params.get("amount"); |
| | | String tranID = params.get("tranID"); |
| | | String paydate = params.get("paydate"); |
| | | |
| | | log.info("notify status: {}", status); |
| | | // 4. 根据状态码更新订单 |
| | | if ("00".equals(status)) { |
| | | // 支付成功,更新订单状态 |
| | | updateOrderStatus(orderId, status, amount, paydate, tranID); |
| | | // 可选:记录交易ID防止重复处理 |
| | | log.info("Payment succeeded for order: {}", orderId); |
| | | } else { |
| | | // 支付失败或待处理 |
| | | log.warn("Payment failed/pending for order: {}", orderId); |
| | | } |
| | | |
| | | // 5. 返回ACK响应(可选,但推荐) |
| | | return; |
| | | } |
| | | |
| | | private boolean verifySkey(Map<String, String> params) { |
| | | // 从配置或数据库中获取Secret Key |
| | | String secretKey = "59c709fc18978a6a83b87f05d37cecbf"; |
| | | |
| | | // 按API文档生成skey |
| | | String tranID = params.get("tranID"); |
| | | String orderId = params.get("orderid"); |
| | | String status = params.get("status"); |
| | | String domain = params.get("domain"); |
| | | String amount = params.get("amount"); |
| | | String currency = params.get("currency"); |
| | | String appcode = params.get("appcode"); |
| | | String paydate = params.get("paydate"); |
| | | String receivedSkey = params.get("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); |
| | | |
| | | return calculatedSkey.equals(receivedSkey); |
| | | } |
| | | |
| | | private void updateOrderStatus(String orderId, String status, String amount, String paydate, String tranID) { |
| | | // 实现订单状态更新逻辑(如更新数据库) |
| | | MallOrderInfo mallOrderInfo = ValidateEntityUtils.ensureColumnReturnEntity(orderId, MallOrderInfo::getId, mallOrderInfoMapper::selectOne, "订单不存在"); |
| | | ValidateEntityUtils.ensureNotEqual(mallOrderInfo.getPayResult(), "1", "订单已支付"); |
| | | ValidateEntityUtils.ensureEqual(mallOrderInfo.getAmount().toString(), amount, "订单金额异常"); |
| | | // 更新订单状态 |
| | | if ("00".equals(status)) { |
| | | mallOrderInfo.setPayMethod("FIUU支付"); |
| | | mallOrderInfo.setStatus(OrderStatusEnum.WAIT_SHIPPING.getValue()); |
| | | mallOrderInfo.setPayResult("1"); |
| | | mallOrderInfo.setPayTime(DateUtil.parseDateTime(paydate)); |
| | | mallOrderInfo.setDeliveryState(OrderDeliveryStateEnum.DELIVERY_WAIT.getValue()); |
| | | mallOrderInfo.setPayOrderNo(tranID); |
| | | mallOrderInfoMapper.updateById(mallOrderInfo); |
| | | } |
| | | } |
| | | |
| | | // Java 通知接口 暂时停止使用 |
| | | @PostMapping("/callback") |
| | | public FebsResponse handlePaymentCallback(@RequestParam Map<String, String> params) { |
| | | try { |
| | | String secretKey = "59c709fc18978a6a83b87f05d37cecbf"; |
| | | String tranID = params.get("tranID"); |
| | | String orderId = params.get("orderid"); |
| | | String status = params.get("status"); |
| | | String domain = params.get("domain"); |
| | | String amount = params.get("amount"); |
| | | String currency = params.get("currency"); |
| | | String paydate = params.get("paydate"); |
| | | String skey = params.get("skey"); |
| | | String secretKey = "59c709fc18978a6a83b87f05d37cecbf"; |
| | | String tranID = params.get("tranID"); |
| | | String orderId = params.get("orderid"); |
| | | String status = params.get("status"); |
| | | String domain = params.get("domain"); |
| | | String amount = params.get("amount"); |
| | | String currency = params.get("currency"); |
| | | String paydate = params.get("paydate"); |
| | | String skey = params.get("skey"); |
| | | |
| | | // 计算 skey 验证 |
| | | String preSkey = DigestUtils.md5Hex(tranID + orderId + status + domain + amount + currency); |
| | | String calculatedSkey = DigestUtils.md5Hex(paydate + domain + preSkey + secretKey); |
| | | |
| | | if (!calculatedSkey.equals(skey)) { |
| | | throw new FebsException("订单回调失败,---"+orderId); |
| | | } |
| | | |
| | | MallOrderInfo mallOrderInfo = ValidateEntityUtils.ensureColumnReturnEntity(orderId, MallOrderInfo::getId, mallOrderInfoMapper::selectOne, "订单不存在"); |
| | | ValidateEntityUtils.ensureEqual(mallOrderInfo.getPayResult(), "1", "订单已支付"); |
| | | ValidateEntityUtils.ensureEqual(mallOrderInfo.getAmount().toString(), amount, "订单金额异常"); |
| | | // 更新订单状态 |
| | | if ("00".equals(status)) { |
| | | mallOrderInfo.setStatus(OrderStatusEnum.WAIT_SHIPPING.getValue()); |
| | | mallOrderInfo.setPayResult("1"); |
| | | mallOrderInfo.setPayTime(DateUtil.parseDateTime(paydate)); |
| | | mallOrderInfo.setDeliveryState(OrderDeliveryStateEnum.DELIVERY_WAIT.getValue()); |
| | | mallOrderInfo.setPayOrderNo(tranID); |
| | | mallOrderInfoMapper.updateById(mallOrderInfo); |
| | | } |
| | | return new FebsResponse().success().message("OK"); |
| | | } catch (Exception e) { |
| | | return new FebsResponse().fail().message("Internal Error"); |
| | | // 计算 skey 验证 |
| | | String preSkey = DigestUtils.md5Hex(tranID + orderId + status + domain + amount + currency); |
| | | String calculatedSkey = DigestUtils.md5Hex(paydate + domain + preSkey + secretKey); |
| | | MallOrderInfo mallOrderInfo = ValidateEntityUtils |
| | | .ensureColumnReturnEntity(orderId, MallOrderInfo::getId, mallOrderInfoMapper::selectOne, "订单不存在"); |
| | | log.info("callback payResult: {}", mallOrderInfo.getPayResult()); |
| | | if("1".equals(mallOrderInfo.getPayResult())){ |
| | | return new FebsResponse().success().data("/pages/order/pay/paySuccess?amount="+ amount +"&type=3"); |
| | | } |
| | | |
| | | log.info("callback skey: {}", preSkey); |
| | | log.info("callback calculatedSkey: {}", calculatedSkey); |
| | | |
| | | if (!calculatedSkey.equals(skey)) { |
| | | throw new FebsException("订单回调失败,---"+orderId); |
| | | } |
| | | |
| | | updateOrderStatus(orderId, status, amount, paydate, tranID); |
| | | if ("00".equals(status)) { |
| | | return new FebsResponse().success().data("/pages/order/pay/paySuccess?amount="+ amount +"&type=3"); |
| | | }else{ |
| | | return new FebsResponse().fail().message("支付失败"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 根据用户ID和订单ID获取所购买商品名称 |
| | | * @return 所含商品名称(多个以","隔开) |
| | | */ |
| | | public String getProductNames(Long memberId, Long orderId) { |
| | | MallOrderInfo mallOrderInfo = mallOrderInfoMapper.selectOrderByMemberIdAndId(memberId, orderId); |
| | | List<MallOrderItem> details = mallOrderInfo.getItems(); |
| | | if (CollectionUtils.isEmpty(details)) { |
| | | return ""; |
| | | } |
| | | StringBuffer productNameBuffer = new StringBuffer(); |
| | | Integer maxLength = 30; |
| | | for (int i = 0; i< details.size(); i++) { |
| | | MallOrderItem mallOrderItem = details.get(i); |
| | | String goodsName = mallOrderItem.getGoodsName(); |
| | | if (goodsName == null) { |
| | | continue; |
| | | } |
| | | if (i == 0 && goodsName.length() > maxLength) { |
| | | productNameBuffer.append(goodsName.substring(0, maxLength) + "..."); |
| | | break; |
| | | } |
| | | if ((productNameBuffer.length() + goodsName.length()) > maxLength) { |
| | | productNameBuffer.append("等"); |
| | | break; |
| | | } |
| | | productNameBuffer.append(goodsName + ","); |
| | | } |
| | | String productNames = productNameBuffer.toString(); |
| | | if (productNames.endsWith(",")) { |
| | | productNames = productNames.substring(0, productNames.length() - 1); |
| | | } |
| | | if (productNames.endsWith(",等")) { |
| | | productNames = productNames.substring(0, productNames.length() - 2) + "等"; |
| | | } |
| | | return productNames; |
| | | } |
| | | } |