| | |
| | | import com.doumee.core.constants.ResponseStatus; |
| | | import com.doumee.core.douyin.DouyinClient; |
| | | import com.doumee.core.douyin.dto.DouyinBaseResp; |
| | | import com.doumee.core.douyin.dto.DouyinBoundProduct; |
| | | import com.doumee.core.douyin.dto.DouyinCancelParam; |
| | | import com.doumee.core.douyin.dto.DouyinCancelReq; |
| | | import com.doumee.core.douyin.dto.DouyinCancelResp; |
| | |
| | | */ |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public DouyinVerifyRecord verify(DouyinVerifyParam param, String operator) { |
| | | public DouyinVerifyRecord verify(DouyinVerifyParam param, String operator, DouyinBoundProduct boundProduct) { |
| | | // 入参校验 |
| | | if (param == null || StringUtils.isBlank(param.getVerifyToken())) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "verifyToken 不能为空"); |
| | |
| | | if (StringUtils.isBlank(param.getSkuId())) { |
| | | // 无 skuId 则无法反查套餐(核销返回本身不含商品标识),直接拦截 |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "skuId 不能为空"); |
| | | } |
| | | if (boundProduct == null || boundProduct.getProduct() == null || boundProduct.getDiscount() == null) { |
| | | // 兜底:套餐绑定结果缺失(scanVerify 应已校验,这里防 NPE) |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "未找到该券对应的本地套餐,请先在管理端绑定"); |
| | | } |
| | | Date now = new Date(); |
| | | |
| | |
| | | } |
| | | |
| | | // 核销成功:为当前登录人开通套餐(任一步失败 → 整单回滚) |
| | | openDiscountForVerify(rec, param, operator); |
| | | // 注:scanVerify 已在核销前校验过套餐绑定并透传结果,这里不再重复查询 |
| | | openDiscountForVerify(rec, param, boundProduct, operator); |
| | | return rec; |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public DouyinVerifyRecord verify(DouyinVerifyParam param, String operator) { |
| | | // 无预校验结果的兼容入口:核销前先校验套餐绑定(核销动作在重载内完成) |
| | | return verify(param, operator, resolveBoundProduct(param.getSkuId())); |
| | | } |
| | | |
| | | /** |
| | |
| | | * |
| | | * @param rec 核销记录(含 originCode/certificateId 等抖音标识,开通后回填套餐卡ID) |
| | | * @param param 核销入参(含 skuId 反查套餐、payAmount 快照) |
| | | * @param boundProduct 核销前已校验的抖音商品 + 本地套餐(避免重复查询) |
| | | * @param operator 操作人 = 套餐归属人(web 端登录会员 id) |
| | | */ |
| | | private void openDiscountForVerify(DouyinVerifyRecord rec, DouyinVerifyParam param, String operator) { |
| | | // ① 反查套餐:skuId → douyin_product_sku → product_id → douyin_product.out_id → discount |
| | | DouyinProduct product = resolveProduct(param.getSkuId()); |
| | | Discount discount = resolveDiscount(product); |
| | | private void openDiscountForVerify(DouyinVerifyRecord rec, DouyinVerifyParam param, |
| | | DouyinBoundProduct boundProduct, String operator) { |
| | | // 商品 + 套餐已由 scanVerify 核销前校验(resolveBoundProduct)查出并透传,直接使用 |
| | | DouyinProduct product = boundProduct.getProduct(); |
| | | Discount discount = boundProduct.getDiscount(); |
| | | |
| | | Date now = new Date(); |
| | | |
| | | // ② 防重:同一券码已为该用户开过套餐卡则跳过(避免重复核销重开) |
| | | // ② 防重:同一券码已为该用户开过「正常」套餐卡则跳过(避免重复核销重开)。 |
| | | // 注意:已作废(status=1,如撤销核销后)的卡不参与防重——撤销后重新核销应正常开新卡。 |
| | | DiscountMember existCard = discountMemberMapper.selectOne(new QueryWrapper<DiscountMember>().lambda() |
| | | .eq(DiscountMember::getCode, rec.getOriginCode()) |
| | | .eq(DiscountMember::getMemberId, operator) |
| | | .eq(DiscountMember::getStatus, Constants.ZERO) |
| | | .eq(DiscountMember::getIsdeleted, Constants.ZERO) |
| | | .last("limit 1")); |
| | | if (existCard != null) { |
| | |
| | | private Discount resolveDiscount(DouyinProduct product) { |
| | | Discount discount = discountMapper.selectOne(new QueryWrapper<Discount>().lambda() |
| | | .eq(Discount::getId, product.getOutId()) |
| | | .eq(Discount::getStatus, Constants.ZERO) |
| | | .eq(Discount::getIsdeleted, Constants.ZERO) |
| | | .last("limit 1")); |
| | | if (discount == null) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "未找到该券对应的本地套餐,请先在管理端绑定"); |
| | | } |
| | | return discount; |
| | | } |
| | | |
| | | @Override |
| | | public DouyinBoundProduct resolveBoundProduct(String skuId) { |
| | | if (StringUtils.isBlank(skuId)) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "skuId 不能为空"); |
| | | } |
| | | // 复用既有校验链路:skuId → 抖音商品(在库 + 已绑 out_id)→ 本地套餐(有效) |
| | | DouyinProduct product = resolveProduct(skuId); |
| | | Discount discount = resolveDiscount(product); |
| | | return new DouyinBoundProduct(product, discount); |
| | | } |
| | | |
| | | @Override |
| | |
| | | rec.setRawResponse(respText); |
| | | // 撤销失败:更新记录描述后抛出(记录保留原核销状态) |
| | | if (!ok) { |
| | | // 失败原因:优先 extra.description,有 sub_description 时追加(sub_description 更具体) |
| | | String desc = resp == null || resp.getExtra() == null ? "无响应" : resp.getExtra().getDescription(); |
| | | rec.setCancelMsg("撤销失败:" + desc); |
| | | String subDesc = resp == null || resp.getExtra() == null ? null : resp.getExtra().getSubDescription(); |
| | | String failMsg = StringUtils.isBlank(subDesc) ? desc : desc + ";" + subDesc; |
| | | rec.setCancelMsg("撤销失败:" + failMsg); |
| | | douyinVerifyRecordMapper.updateById(rec); |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "撤销失败:" + desc); |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "撤销失败:" + failMsg); |
| | | } |
| | | // 撤销成功:置撤销状态、撤销时间与撤销人 |
| | | rec.setCancelStatus(Constants.DOUYIN_VERIFY_CANCEL_STATUS.DONE.getKey()); |
| | |
| | | // 同步作废本地已开通的套餐卡(防止抖音撤销后套餐仍被使用);参照 backGoodsorder 退卡 |
| | | cancelDiscountMember(rec, operator, now); |
| | | return rec; |
| | | } |
| | | |
| | | @Override |
| | | public void fillPackageInfo(DouyinVerifyRecord record) { |
| | | // 无套餐卡ID或查不到时置 null,不影响主流程(scanVerify 用于前端展示套餐信息) |
| | | if (record == null || StringUtils.isBlank(record.getDiscountMemberId())) { |
| | | return; |
| | | } |
| | | record.setPackageInfo(discountMemberMapper.selectById(record.getDiscountMemberId())); |
| | | } |
| | | |
| | | /** |
| | |
| | | .selectAs(DouyinProduct::getCategory, DouyinVerifyRecordPageVO::getCategory) |
| | | // 抖音券名:discount_member.name(本地开通套餐名) |
| | | .selectAs(DiscountMember::getName, DouyinVerifyRecordPageVO::getCouponName) |
| | | // 套餐使用次数:子查询统计该套餐卡在 member_rides 的骑行记录数(t 为主表 douyin_verify_record) |
| | | .select(" ( select count(1) from member_rides where discount_member_id = t.discount_member_id and isdeleted = 0 ) ", DouyinVerifyRecordPageVO::getUseCount) |
| | | // 三表 leftJoin:discount_member(经 discount_member_id)→ member(经 member_id);douyin_product(经 product_id) |
| | | .leftJoin(DiscountMember.class, DiscountMember::getId, DouyinVerifyRecord::getDiscountMemberId) |
| | | .leftJoin(Member.class, Member::getId, DiscountMember::getMemberId) |