21 changed files with 1277 additions and 14 deletions
@ -0,0 +1,121 @@ |
|||
package cc.hiver.mall.controller; |
|||
|
|||
import cc.hiver.core.common.utils.ResultUtil; |
|||
import cc.hiver.core.common.utils.SecurityUtil; |
|||
import cc.hiver.core.common.vo.PageVo; |
|||
import cc.hiver.core.common.vo.Result; |
|||
import cc.hiver.core.entity.User; |
|||
import cc.hiver.mall.invite.entity.InviteRewardConfig; |
|||
import cc.hiver.mall.invite.entity.InviteRewardRecord; |
|||
import cc.hiver.mall.invite.service.InviteRewardService; |
|||
import cc.hiver.mall.invite.service.WechatTransferAuthorizationResult; |
|||
import cc.hiver.mall.utils.WechatPayConfig; |
|||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
|||
import io.swagger.annotations.Api; |
|||
import io.swagger.annotations.ApiOperation; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.web.bind.annotation.RequestMapping; |
|||
import org.springframework.web.bind.annotation.RequestMethod; |
|||
import org.springframework.web.bind.annotation.RequestParam; |
|||
import org.springframework.web.bind.annotation.RestController; |
|||
|
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
|
|||
@RestController |
|||
@Api(tags = "拉新返现接口") |
|||
@RequestMapping("/hiver/app/inviteReward") |
|||
public class InviteRewardController { |
|||
|
|||
@Autowired |
|||
private SecurityUtil securityUtil; |
|||
|
|||
@Autowired |
|||
private InviteRewardService inviteRewardService; |
|||
|
|||
@Autowired |
|||
private WechatPayConfig wechatPayConfig; |
|||
|
|||
@Value("${wechatpay.mchId:}") |
|||
private String mchId; |
|||
|
|||
private PageVo buildPage(Integer pageNumber, Integer pageSize) { |
|||
PageVo pageVo = new PageVo(); |
|||
pageVo.setPageNumber(pageNumber == null ? 1 : pageNumber); |
|||
pageVo.setPageSize(pageSize == null ? 10 : pageSize); |
|||
return pageVo; |
|||
} |
|||
|
|||
@ApiOperation(value = "我的拉新返现记录") |
|||
@RequestMapping(value = "/my/page", method = RequestMethod.GET) |
|||
public Result<Page<InviteRewardRecord>> myPage(@RequestParam(required = false) Integer pageNumber, |
|||
@RequestParam(required = false) Integer pageSize) { |
|||
User user = securityUtil.getCurrUser(); |
|||
if (user == null || user.getId() == null) { |
|||
return ResultUtil.error("请先登录"); |
|||
} |
|||
return new ResultUtil<Page<InviteRewardRecord>>() |
|||
.setData(inviteRewardService.pageRecords(buildPage(pageNumber, pageSize), null, user.getId())); |
|||
} |
|||
|
|||
@ApiOperation(value = "当前区域拉新返现配置") |
|||
@RequestMapping(value = "/config", method = RequestMethod.GET) |
|||
public Result<InviteRewardConfig> config(@RequestParam(required = false) String regionId) { |
|||
return new ResultUtil<InviteRewardConfig>().setData(inviteRewardService.getEnabledConfig(regionId)); |
|||
} |
|||
|
|||
@ApiOperation(value = "免确认收款授权状态") |
|||
@RequestMapping(value = "/transferAuth/status", method = RequestMethod.GET) |
|||
public Result<Map<String, Object>> transferAuthStatus() { |
|||
User user = securityUtil.getCurrUser(); |
|||
if (user == null || user.getId() == null) { |
|||
return ResultUtil.error("请先登录"); |
|||
} |
|||
WechatTransferAuthorizationResult result = inviteRewardService.refreshTransferAuthorization(user, false); |
|||
if (result.isSuccess()) { |
|||
return new ResultUtil<Map<String, Object>>().setData(buildAuthData(user, result)); |
|||
} |
|||
Map<String, Object> data = buildAuthData(user, null); |
|||
return new ResultUtil<Map<String, Object>>().setData(data); |
|||
} |
|||
|
|||
@ApiOperation(value = "申请免确认收款授权") |
|||
@RequestMapping(value = "/transferAuth/apply", method = RequestMethod.POST) |
|||
public Result<Map<String, Object>> applyTransferAuth() { |
|||
User user = securityUtil.getCurrUser(); |
|||
if (user == null || user.getId() == null) { |
|||
return ResultUtil.error("请先登录"); |
|||
} |
|||
WechatTransferAuthorizationResult result = inviteRewardService.applyTransferAuthorization(user); |
|||
if (!result.isSuccess()) { |
|||
return ResultUtil.error(result.getMessage()); |
|||
} |
|||
return new ResultUtil<Map<String, Object>>().setData(buildAuthData(user, result)); |
|||
} |
|||
|
|||
@ApiOperation(value = "刷新免确认收款授权状态") |
|||
@RequestMapping(value = "/transferAuth/query", method = RequestMethod.POST) |
|||
public Result<Map<String, Object>> queryTransferAuth() { |
|||
User user = securityUtil.getCurrUser(); |
|||
if (user == null || user.getId() == null) { |
|||
return ResultUtil.error("请先登录"); |
|||
} |
|||
WechatTransferAuthorizationResult result = inviteRewardService.refreshTransferAuthorization(user, true); |
|||
if (!result.isSuccess()) { |
|||
return ResultUtil.error(result.getMessage()); |
|||
} |
|||
return new ResultUtil<Map<String, Object>>().setData(buildAuthData(user, result)); |
|||
} |
|||
|
|||
private Map<String, Object> buildAuthData(User user, WechatTransferAuthorizationResult result) { |
|||
Map<String, Object> data = new HashMap<>(); |
|||
data.put("mchId", mchId); |
|||
data.put("appId", wechatPayConfig.getAppId()); |
|||
data.put("authNo", result == null ? user.getInviteTransferAuthNo() : result.getOutAuthorizationNo()); |
|||
data.put("authId", result == null ? user.getInviteTransferAuthId() : result.getAuthorizationId()); |
|||
data.put("state", result == null ? user.getInviteTransferAuthState() : result.getState()); |
|||
data.put("packageInfo", result == null ? "" : result.getPackageInfo()); |
|||
return data; |
|||
} |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
package cc.hiver.mall.invite.entity; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.TableId; |
|||
import com.baomidou.mybatisplus.annotation.TableName; |
|||
import com.fasterxml.jackson.annotation.JsonFormat; |
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
import org.springframework.format.annotation.DateTimeFormat; |
|||
|
|||
import javax.persistence.Id; |
|||
import java.io.Serializable; |
|||
import java.math.BigDecimal; |
|||
import java.util.Date; |
|||
|
|||
@Data |
|||
@ApiModel(value = "拉新返现配置") |
|||
@TableName(value = "t_invite_reward_config", autoResultMap = true) |
|||
public class InviteRewardConfig implements Serializable { |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@Id |
|||
@TableId |
|||
@ApiModelProperty(value = "主键") |
|||
private String id; |
|||
|
|||
@ApiModelProperty(value = "区域id") |
|||
private String regionId; |
|||
|
|||
@ApiModelProperty(value = "每个有效新用户奖励金额") |
|||
private BigDecimal rewardAmount; |
|||
|
|||
@ApiModelProperty(value = "每日发放上限 0不限") |
|||
private Integer dailyLimit; |
|||
|
|||
@ApiModelProperty(value = "总预算 0不限") |
|||
private BigDecimal totalBudget; |
|||
|
|||
@ApiModelProperty(value = "已发放预算") |
|||
private BigDecimal usedBudget; |
|||
|
|||
@ApiModelProperty(value = "是否启用 1是 0否") |
|||
private Integer enabled; |
|||
|
|||
@ApiModelProperty(value = "备注") |
|||
private String remark; |
|||
|
|||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@ApiModelProperty(value = "创建时间") |
|||
private Date createTime; |
|||
|
|||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@ApiModelProperty(value = "更新时间") |
|||
private Date updateTime; |
|||
} |
|||
@ -0,0 +1,69 @@ |
|||
package cc.hiver.mall.invite.entity; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.TableId; |
|||
import com.baomidou.mybatisplus.annotation.TableName; |
|||
import com.fasterxml.jackson.annotation.JsonFormat; |
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
import org.springframework.format.annotation.DateTimeFormat; |
|||
|
|||
import javax.persistence.Id; |
|||
import java.io.Serializable; |
|||
import java.math.BigDecimal; |
|||
import java.util.Date; |
|||
|
|||
@Data |
|||
@ApiModel(value = "拉新返现奖励记录") |
|||
@TableName(value = "t_invite_reward_record", autoResultMap = true) |
|||
public class InviteRewardRecord implements Serializable { |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@Id |
|||
@TableId |
|||
@ApiModelProperty(value = "主键") |
|||
private String id; |
|||
|
|||
@ApiModelProperty(value = "邀请人用户id") |
|||
private String inviterId; |
|||
|
|||
@ApiModelProperty(value = "被邀请人用户id") |
|||
private String invitedUserId; |
|||
|
|||
@ApiModelProperty(value = "被邀请人手机号") |
|||
private String invitedMobile; |
|||
|
|||
@ApiModelProperty(value = "邀请设备标识") |
|||
private String inviteDeviceId; |
|||
|
|||
@ApiModelProperty(value = "发生奖励时的区域id") |
|||
private String regionId; |
|||
|
|||
@ApiModelProperty(value = "奖励金额") |
|||
private BigDecimal amount; |
|||
|
|||
@ApiModelProperty(value = "状态 0处理中 1成功 2失败 3跳过") |
|||
private Integer status; |
|||
|
|||
@ApiModelProperty(value = "微信商家转账商户单号") |
|||
private String outBillNo; |
|||
|
|||
@ApiModelProperty(value = "微信商家转账单号") |
|||
private String transferBillNo; |
|||
|
|||
@ApiModelProperty(value = "失败或跳过原因") |
|||
private String failReason; |
|||
|
|||
@ApiModelProperty(value = "微信响应") |
|||
private String wxResponse; |
|||
|
|||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@ApiModelProperty(value = "创建时间") |
|||
private Date createTime; |
|||
|
|||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@ApiModelProperty(value = "更新时间") |
|||
private Date updateTime; |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
package cc.hiver.mall.invite.mapper; |
|||
|
|||
import cc.hiver.mall.invite.entity.InviteRewardConfig; |
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
import org.springframework.stereotype.Repository; |
|||
|
|||
@Repository |
|||
public interface InviteRewardConfigMapper extends BaseMapper<InviteRewardConfig> { |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
package cc.hiver.mall.invite.mapper; |
|||
|
|||
import cc.hiver.mall.invite.entity.InviteRewardRecord; |
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
import org.springframework.stereotype.Repository; |
|||
|
|||
@Repository |
|||
public interface InviteRewardRecordMapper extends BaseMapper<InviteRewardRecord> { |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
package cc.hiver.mall.invite.service; |
|||
|
|||
import lombok.Data; |
|||
|
|||
import java.math.BigDecimal; |
|||
|
|||
@Data |
|||
public class InviteRewardResult { |
|||
private boolean rewarded; |
|||
private boolean skipped; |
|||
private boolean processing; |
|||
private BigDecimal amount; |
|||
private String message; |
|||
|
|||
public static InviteRewardResult rewarded(BigDecimal amount, String message) { |
|||
InviteRewardResult result = new InviteRewardResult(); |
|||
result.setRewarded(true); |
|||
result.setAmount(amount); |
|||
result.setMessage(message); |
|||
return result; |
|||
} |
|||
|
|||
public static InviteRewardResult skipped(String message) { |
|||
InviteRewardResult result = new InviteRewardResult(); |
|||
result.setSkipped(true); |
|||
result.setMessage(message); |
|||
return result; |
|||
} |
|||
|
|||
public static InviteRewardResult processing(String message) { |
|||
InviteRewardResult result = new InviteRewardResult(); |
|||
result.setProcessing(true); |
|||
result.setMessage(message); |
|||
return result; |
|||
} |
|||
|
|||
public static InviteRewardResult failed(String message) { |
|||
InviteRewardResult result = new InviteRewardResult(); |
|||
result.setMessage(message); |
|||
return result; |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
package cc.hiver.mall.invite.service; |
|||
|
|||
import cc.hiver.core.common.vo.PageVo; |
|||
import cc.hiver.core.entity.User; |
|||
import cc.hiver.mall.invite.entity.InviteRewardConfig; |
|||
import cc.hiver.mall.invite.entity.InviteRewardRecord; |
|||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
|||
|
|||
public interface InviteRewardService { |
|||
|
|||
Page<InviteRewardConfig> pageConfigs(PageVo pageVo, String regionId); |
|||
|
|||
Page<InviteRewardRecord> pageRecords(PageVo pageVo, String regionId, String inviterId); |
|||
|
|||
InviteRewardConfig getEnabledConfig(String regionId); |
|||
|
|||
InviteRewardConfig saveConfig(InviteRewardConfig config); |
|||
|
|||
void deleteConfig(String id); |
|||
|
|||
WechatTransferAuthorizationResult applyTransferAuthorization(User user); |
|||
|
|||
WechatTransferAuthorizationResult refreshTransferAuthorization(User user, boolean displayAuthorization); |
|||
|
|||
InviteRewardResult rewardAfterPhoneBound(User invitedUser, |
|||
String phoneNumber, |
|||
String inviterId, |
|||
String regionId, |
|||
String deviceId, |
|||
Double lat, |
|||
Double lng, |
|||
boolean firstPhoneBind); |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
package cc.hiver.mall.invite.service; |
|||
|
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
public class WechatMerchantTransferResult { |
|||
private boolean success; |
|||
private boolean processing; |
|||
private String state; |
|||
private String transferBillNo; |
|||
private String packageInfo; |
|||
private String responseBody; |
|||
private String message; |
|||
|
|||
public static WechatMerchantTransferResult success(String state, String transferBillNo, String packageInfo, String responseBody) { |
|||
WechatMerchantTransferResult result = new WechatMerchantTransferResult(); |
|||
result.setSuccess(true); |
|||
result.setState(state); |
|||
result.setTransferBillNo(transferBillNo); |
|||
result.setPackageInfo(packageInfo); |
|||
result.setResponseBody(responseBody); |
|||
return result; |
|||
} |
|||
|
|||
public static WechatMerchantTransferResult processing(String state, String transferBillNo, String packageInfo, String message, String responseBody) { |
|||
WechatMerchantTransferResult result = new WechatMerchantTransferResult(); |
|||
result.setProcessing(true); |
|||
result.setState(state); |
|||
result.setTransferBillNo(transferBillNo); |
|||
result.setPackageInfo(packageInfo); |
|||
result.setMessage(message); |
|||
result.setResponseBody(responseBody); |
|||
return result; |
|||
} |
|||
|
|||
public static WechatMerchantTransferResult fail(String message, String responseBody) { |
|||
WechatMerchantTransferResult result = new WechatMerchantTransferResult(); |
|||
result.setSuccess(false); |
|||
result.setMessage(message); |
|||
result.setResponseBody(responseBody); |
|||
return result; |
|||
} |
|||
} |
|||
@ -0,0 +1,275 @@ |
|||
package cc.hiver.mall.invite.service; |
|||
|
|||
import cc.hiver.mall.utils.WechatPayConfig; |
|||
import cc.hiver.mall.utils.WechatPaySigner; |
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import com.fasterxml.jackson.databind.ObjectMapper; |
|||
import okhttp3.*; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.stereotype.Service; |
|||
|
|||
import java.math.BigDecimal; |
|||
import java.util.ArrayList; |
|||
import java.util.HashMap; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
|
|||
@Service |
|||
public class WechatMerchantTransferService { |
|||
|
|||
@Value("${wechatpay.transferUrl:https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills}") |
|||
private String transferUrl; |
|||
|
|||
@Value("${wechatpay.transferSceneId:1000}") |
|||
private String transferSceneId; |
|||
|
|||
@Value("${wechatpay.transferRemark:邀请好友注册奖励}") |
|||
private String transferRemark; |
|||
|
|||
@Value("${wechatpay.transferUserRecvPerception:现金奖励}") |
|||
private String userRecvPerception; |
|||
|
|||
@Value("${wechatpay.transferNotifyUrl:}") |
|||
private String transferNotifyUrl; |
|||
|
|||
@Value("${wechatpay.transferAuthorizationUrl:https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/user-confirm-authorization}") |
|||
private String transferAuthorizationUrl; |
|||
|
|||
@Value("${wechatpay.transferAuthorizationQueryUrl:https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/user-confirm-authorization/out-authorization-no}") |
|||
private String transferAuthorizationQueryUrl; |
|||
|
|||
@Value("${wechatpay.transferAuthorizedTransferUrl:https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills/transfer}") |
|||
private String transferAuthorizedTransferUrl; |
|||
|
|||
@Value("${wechatpay.transferAuthorizationNotifyUrl:}") |
|||
private String transferAuthorizationNotifyUrl; |
|||
|
|||
@Value("${wechatpay.transferSceneReportInfoType1:活动名称}") |
|||
private String transferSceneReportInfoType1; |
|||
|
|||
@Value("${wechatpay.transferSceneReportInfoContent1:邀请好友注册奖励}") |
|||
private String transferSceneReportInfoContent1; |
|||
|
|||
@Value("${wechatpay.transferSceneReportInfoType2:奖励说明}") |
|||
private String transferSceneReportInfoType2; |
|||
|
|||
@Value("${wechatpay.transferSceneReportInfoContent2:好友完成注册并绑定手机号奖励}") |
|||
private String transferSceneReportInfoContent2; |
|||
|
|||
@Autowired |
|||
private WechatPayConfig config; |
|||
|
|||
@Autowired |
|||
private WechatPaySigner signer; |
|||
|
|||
private final OkHttpClient httpClient = new OkHttpClient(); |
|||
private final ObjectMapper objectMapper = new ObjectMapper(); |
|||
|
|||
public WechatTransferAuthorizationResult applyAuthorization(String openid, String displayName, String outAuthorizationNo) { |
|||
if (StringUtils.isBlank(openid)) { |
|||
return WechatTransferAuthorizationResult.fail("用户缺少小程序openid", ""); |
|||
} |
|||
if (StringUtils.isBlank(outAuthorizationNo)) { |
|||
return WechatTransferAuthorizationResult.fail("授权单号不能为空", ""); |
|||
} |
|||
if (StringUtils.isBlank(transferAuthorizationNotifyUrl)) { |
|||
return WechatTransferAuthorizationResult.fail("未配置免确认收款授权回调地址", ""); |
|||
} |
|||
try { |
|||
Map<String, Object> reqBody = new HashMap<>(); |
|||
reqBody.put("out_authorization_no", outAuthorizationNo); |
|||
reqBody.put("appid", config.getAppId()); |
|||
reqBody.put("openid", openid); |
|||
reqBody.put("transfer_scene_id", transferSceneId); |
|||
reqBody.put("user_display_name", StringUtils.defaultIfBlank(displayName, "用户")); |
|||
reqBody.put("user_recv_perception", userRecvPerception); |
|||
reqBody.put("authorization_notify_url", transferAuthorizationNotifyUrl); |
|||
|
|||
String jsonBody = objectMapper.writeValueAsString(reqBody); |
|||
Request request = newJsonRequest(transferAuthorizationUrl, jsonBody); |
|||
try (Response response = httpClient.newCall(request).execute()) { |
|||
String responseBody = response.body() == null ? "" : response.body().string(); |
|||
if (!response.isSuccessful()) { |
|||
return WechatTransferAuthorizationResult.fail("微信免确认收款授权申请失败: " + response.code(), responseBody); |
|||
} |
|||
JsonNode root = objectMapper.readTree(responseBody); |
|||
return WechatTransferAuthorizationResult.success( |
|||
root.path("out_authorization_no").asText(outAuthorizationNo), |
|||
root.path("authorization_id").asText(""), |
|||
root.path("state").asText(""), |
|||
root.path("package_info").asText(""), |
|||
responseBody |
|||
); |
|||
} |
|||
} catch (Exception e) { |
|||
return WechatTransferAuthorizationResult.fail(e.getMessage(), ""); |
|||
} |
|||
} |
|||
|
|||
public WechatTransferAuthorizationResult queryAuthorization(String outAuthorizationNo, boolean displayAuthorization) { |
|||
if (StringUtils.isBlank(outAuthorizationNo)) { |
|||
return WechatTransferAuthorizationResult.fail("授权单号不能为空", ""); |
|||
} |
|||
try { |
|||
String url = transferAuthorizationQueryUrl + "/" + outAuthorizationNo |
|||
+ "?is_display_authorization=" + displayAuthorization; |
|||
Request request = new Request.Builder() |
|||
.url(url) |
|||
.get() |
|||
.addHeader("Authorization", "WECHATPAY2-SHA256-RSA2048 " + signer.sign("GET", url, "")) |
|||
.addHeader("Accept", "application/json") |
|||
.addHeader("User-Agent", "Hiver/1.0") |
|||
.build(); |
|||
try (Response response = httpClient.newCall(request).execute()) { |
|||
String responseBody = response.body() == null ? "" : response.body().string(); |
|||
if (!response.isSuccessful()) { |
|||
return WechatTransferAuthorizationResult.fail("微信免确认收款授权查询失败: " + response.code(), responseBody); |
|||
} |
|||
JsonNode root = objectMapper.readTree(responseBody); |
|||
return WechatTransferAuthorizationResult.success( |
|||
root.path("out_authorization_no").asText(outAuthorizationNo), |
|||
root.path("authorization_id").asText(""), |
|||
root.path("state").asText(""), |
|||
root.path("package_info").asText(""), |
|||
responseBody |
|||
); |
|||
} |
|||
} catch (Exception e) { |
|||
return WechatTransferAuthorizationResult.fail(e.getMessage(), ""); |
|||
} |
|||
} |
|||
|
|||
public WechatMerchantTransferResult transferWithAuthorization(String authorizationId, String outAuthorizationNo, |
|||
BigDecimal amount, String outBillNo) { |
|||
if (StringUtils.isAllBlank(authorizationId, outAuthorizationNo)) { |
|||
return WechatMerchantTransferResult.fail("邀请人未开通免确认收款授权", ""); |
|||
} |
|||
if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) { |
|||
return WechatMerchantTransferResult.fail("转账金额必须大于0", ""); |
|||
} |
|||
try { |
|||
Map<String, Object> reqBody = new HashMap<>(); |
|||
reqBody.put("appid", config.getAppId()); |
|||
reqBody.put("out_bill_no", outBillNo); |
|||
reqBody.put("transfer_scene_id", transferSceneId); |
|||
reqBody.put("transfer_amount", amount.multiply(BigDecimal.valueOf(100)).intValue()); |
|||
reqBody.put("transfer_remark", transferRemark); |
|||
reqBody.put("user_recv_perception", userRecvPerception); |
|||
if (StringUtils.isNotBlank(authorizationId)) { |
|||
reqBody.put("authorization_id", authorizationId); |
|||
} else { |
|||
reqBody.put("out_authorization_no", outAuthorizationNo); |
|||
} |
|||
List<Map<String, String>> reportInfos = buildSceneReportInfos(); |
|||
if (!reportInfos.isEmpty()) { |
|||
reqBody.put("transfer_scene_report_infos", reportInfos); |
|||
} |
|||
|
|||
String jsonBody = objectMapper.writeValueAsString(reqBody); |
|||
Request request = newJsonRequest(transferAuthorizedTransferUrl, jsonBody); |
|||
try (Response response = httpClient.newCall(request).execute()) { |
|||
String responseBody = response.body() == null ? "" : response.body().string(); |
|||
if (!response.isSuccessful()) { |
|||
return WechatMerchantTransferResult.fail("微信商家授权转账失败: " + response.code(), responseBody); |
|||
} |
|||
JsonNode root = objectMapper.readTree(responseBody); |
|||
String transferBillNo = root.path("transfer_bill_no").asText(""); |
|||
String state = root.path("state").asText(""); |
|||
if ("SUCCESS".equals(state)) { |
|||
return WechatMerchantTransferResult.success(state, transferBillNo, "", responseBody); |
|||
} |
|||
return WechatMerchantTransferResult.fail("微信商家授权转账状态: " + state, responseBody); |
|||
} |
|||
} catch (Exception e) { |
|||
return WechatMerchantTransferResult.fail(e.getMessage(), ""); |
|||
} |
|||
} |
|||
|
|||
public WechatMerchantTransferResult transferToOpenid(String openid, BigDecimal amount, String outBillNo) { |
|||
if (StringUtils.isBlank(openid)) { |
|||
return WechatMerchantTransferResult.fail("邀请人缺少小程序openid", ""); |
|||
} |
|||
if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) { |
|||
return WechatMerchantTransferResult.fail("转账金额必须大于0", ""); |
|||
} |
|||
try { |
|||
Map<String, Object> reqBody = new HashMap<>(); |
|||
reqBody.put("appid", config.getAppId()); |
|||
reqBody.put("out_bill_no", outBillNo); |
|||
reqBody.put("transfer_scene_id", transferSceneId); |
|||
reqBody.put("openid", openid); |
|||
reqBody.put("transfer_amount", amount.multiply(BigDecimal.valueOf(100)).intValue()); |
|||
reqBody.put("transfer_remark", transferRemark); |
|||
reqBody.put("user_recv_perception", userRecvPerception); |
|||
List<Map<String, String>> reportInfos = buildSceneReportInfos(); |
|||
if (!reportInfos.isEmpty()) { |
|||
reqBody.put("transfer_scene_report_infos", reportInfos); |
|||
} |
|||
if (StringUtils.isNotBlank(transferNotifyUrl)) { |
|||
reqBody.put("notify_url", transferNotifyUrl); |
|||
} |
|||
|
|||
String jsonBody = objectMapper.writeValueAsString(reqBody); |
|||
String authorization = signer.sign("POST", transferUrl, jsonBody); |
|||
Request request = new Request.Builder() |
|||
.url(transferUrl) |
|||
.post(RequestBody.create(MediaType.get("application/json"), jsonBody)) |
|||
.addHeader("Authorization", "WECHATPAY2-SHA256-RSA2048 " + authorization) |
|||
.addHeader("Content-Type", "application/json") |
|||
.addHeader("Accept", "application/json") |
|||
.addHeader("User-Agent", "Hiver/1.0") |
|||
.build(); |
|||
|
|||
try (Response response = httpClient.newCall(request).execute()) { |
|||
String responseBody = response.body() == null ? "" : response.body().string(); |
|||
if (!response.isSuccessful()) { |
|||
return WechatMerchantTransferResult.fail("微信商家转账失败: " + response.code(), responseBody); |
|||
} |
|||
JsonNode root = objectMapper.readTree(responseBody); |
|||
String transferBillNo = root.path("transfer_bill_no").asText(""); |
|||
String state = root.path("state").asText(""); |
|||
String packageInfo = root.path("package_info").asText(""); |
|||
if ("SUCCESS".equals(state)) { |
|||
return WechatMerchantTransferResult.success(state, transferBillNo, packageInfo, responseBody); |
|||
} |
|||
if ("WAIT_USER_CONFIRM".equals(state)) { |
|||
return WechatMerchantTransferResult.processing(state, transferBillNo, packageInfo, "待邀请人确认收款", responseBody); |
|||
} |
|||
return WechatMerchantTransferResult.fail("微信商家转账状态: " + state, responseBody); |
|||
} |
|||
} catch (Exception e) { |
|||
return WechatMerchantTransferResult.fail(e.getMessage(), ""); |
|||
} |
|||
} |
|||
|
|||
private Request newJsonRequest(String url, String jsonBody) throws Exception { |
|||
String authorization = signer.sign("POST", url, jsonBody); |
|||
return new Request.Builder() |
|||
.url(url) |
|||
.post(RequestBody.create(MediaType.get("application/json"), jsonBody)) |
|||
.addHeader("Authorization", "WECHATPAY2-SHA256-RSA2048 " + authorization) |
|||
.addHeader("Content-Type", "application/json") |
|||
.addHeader("Accept", "application/json") |
|||
.addHeader("User-Agent", "Hiver/1.0") |
|||
.build(); |
|||
} |
|||
|
|||
private List<Map<String, String>> buildSceneReportInfos() { |
|||
List<Map<String, String>> reportInfos = new ArrayList<>(); |
|||
addSceneReportInfo(reportInfos, transferSceneReportInfoType1, transferSceneReportInfoContent1); |
|||
addSceneReportInfo(reportInfos, transferSceneReportInfoType2, transferSceneReportInfoContent2); |
|||
return reportInfos; |
|||
} |
|||
|
|||
private void addSceneReportInfo(List<Map<String, String>> reportInfos, String infoType, String infoContent) { |
|||
if (StringUtils.isBlank(infoType) || StringUtils.isBlank(infoContent)) { |
|||
return; |
|||
} |
|||
Map<String, String> info = new HashMap<>(); |
|||
info.put("info_type", infoType); |
|||
info.put("info_content", infoContent); |
|||
reportInfos.add(info); |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
package cc.hiver.mall.invite.service; |
|||
|
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
public class WechatTransferAuthorizationResult { |
|||
private boolean success; |
|||
private String outAuthorizationNo; |
|||
private String authorizationId; |
|||
private String state; |
|||
private String packageInfo; |
|||
private String responseBody; |
|||
private String message; |
|||
|
|||
public static WechatTransferAuthorizationResult success(String outAuthorizationNo, String authorizationId, |
|||
String state, String packageInfo, String responseBody) { |
|||
WechatTransferAuthorizationResult result = new WechatTransferAuthorizationResult(); |
|||
result.setSuccess(true); |
|||
result.setOutAuthorizationNo(outAuthorizationNo); |
|||
result.setAuthorizationId(authorizationId); |
|||
result.setState(state); |
|||
result.setPackageInfo(packageInfo); |
|||
result.setResponseBody(responseBody); |
|||
return result; |
|||
} |
|||
|
|||
public static WechatTransferAuthorizationResult fail(String message, String responseBody) { |
|||
WechatTransferAuthorizationResult result = new WechatTransferAuthorizationResult(); |
|||
result.setSuccess(false); |
|||
result.setMessage(message); |
|||
result.setResponseBody(responseBody); |
|||
return result; |
|||
} |
|||
} |
|||
@ -0,0 +1,380 @@ |
|||
package cc.hiver.mall.invite.service.impl; |
|||
|
|||
import cc.hiver.core.common.vo.PageVo; |
|||
import cc.hiver.core.entity.User; |
|||
import cc.hiver.core.service.UserService; |
|||
import cc.hiver.mall.entity.ShopArea; |
|||
import cc.hiver.mall.invite.entity.InviteRewardConfig; |
|||
import cc.hiver.mall.invite.entity.InviteRewardRecord; |
|||
import cc.hiver.mall.invite.mapper.InviteRewardConfigMapper; |
|||
import cc.hiver.mall.invite.mapper.InviteRewardRecordMapper; |
|||
import cc.hiver.mall.invite.service.*; |
|||
import cc.hiver.mall.service.ShopAreaService; |
|||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
|||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Service; |
|||
import org.springframework.transaction.annotation.Transactional; |
|||
|
|||
import java.math.BigDecimal; |
|||
import java.util.Calendar; |
|||
import java.util.Date; |
|||
import java.util.UUID; |
|||
|
|||
@Service |
|||
public class InviteRewardServiceImpl implements InviteRewardService { |
|||
|
|||
private static final int STATUS_PROCESSING = 0; |
|||
private static final int STATUS_SUCCESS = 1; |
|||
private static final int STATUS_FAILED = 2; |
|||
private static final int STATUS_SKIPPED = 3; |
|||
|
|||
@Autowired |
|||
private InviteRewardConfigMapper configMapper; |
|||
|
|||
@Autowired |
|||
private InviteRewardRecordMapper recordMapper; |
|||
|
|||
@Autowired |
|||
private UserService userService; |
|||
|
|||
@Autowired |
|||
private ShopAreaService shopAreaService; |
|||
|
|||
@Autowired |
|||
private WechatMerchantTransferService transferService; |
|||
|
|||
private static String genId() { |
|||
return UUID.randomUUID().toString().replace("-", ""); |
|||
} |
|||
|
|||
private <T> Page<T> page(PageVo pageVo) { |
|||
int num = pageVo == null || pageVo.getPageNumber() <= 0 ? 1 : pageVo.getPageNumber(); |
|||
int size = pageVo == null || pageVo.getPageSize() <= 0 ? 10 : pageVo.getPageSize(); |
|||
return new Page<>(num, size); |
|||
} |
|||
|
|||
@Override |
|||
public Page<InviteRewardConfig> pageConfigs(PageVo pageVo, String regionId) { |
|||
LambdaQueryWrapper<InviteRewardConfig> qw = new LambdaQueryWrapper<>(); |
|||
if (StringUtils.isNotBlank(regionId)) { |
|||
qw.eq(InviteRewardConfig::getRegionId, regionId); |
|||
} |
|||
qw.orderByDesc(InviteRewardConfig::getCreateTime); |
|||
return configMapper.selectPage(page(pageVo), qw); |
|||
} |
|||
|
|||
@Override |
|||
public Page<InviteRewardRecord> pageRecords(PageVo pageVo, String regionId, String inviterId) { |
|||
LambdaQueryWrapper<InviteRewardRecord> qw = new LambdaQueryWrapper<>(); |
|||
if (StringUtils.isNotBlank(regionId)) { |
|||
qw.eq(InviteRewardRecord::getRegionId, regionId); |
|||
} |
|||
if (StringUtils.isNotBlank(inviterId)) { |
|||
qw.eq(InviteRewardRecord::getInviterId, inviterId); |
|||
} |
|||
qw.orderByDesc(InviteRewardRecord::getCreateTime); |
|||
return recordMapper.selectPage(page(pageVo), qw); |
|||
} |
|||
|
|||
@Override |
|||
public InviteRewardConfig saveConfig(InviteRewardConfig config) { |
|||
Date now = new Date(); |
|||
if (config.getRewardAmount() == null) { |
|||
config.setRewardAmount(BigDecimal.ZERO); |
|||
} |
|||
if (config.getDailyLimit() == null) { |
|||
config.setDailyLimit(0); |
|||
} |
|||
if (config.getTotalBudget() == null) { |
|||
config.setTotalBudget(BigDecimal.ZERO); |
|||
} |
|||
if (config.getUsedBudget() == null) { |
|||
config.setUsedBudget(BigDecimal.ZERO); |
|||
} |
|||
if (config.getEnabled() == null) { |
|||
config.setEnabled(1); |
|||
} |
|||
config.setUpdateTime(now); |
|||
if (StringUtils.isBlank(config.getId())) { |
|||
config.setId(genId()); |
|||
config.setCreateTime(now); |
|||
configMapper.insert(config); |
|||
} else { |
|||
configMapper.updateById(config); |
|||
} |
|||
return config; |
|||
} |
|||
|
|||
@Override |
|||
public void deleteConfig(String id) { |
|||
configMapper.deleteById(id); |
|||
} |
|||
|
|||
@Override |
|||
public WechatTransferAuthorizationResult applyTransferAuthorization(User user) { |
|||
if (user == null || StringUtils.isBlank(user.getId())) { |
|||
return WechatTransferAuthorizationResult.fail("用户不存在", ""); |
|||
} |
|||
if (StringUtils.isBlank(user.getMiniProgramOpenid())) { |
|||
return WechatTransferAuthorizationResult.fail("用户缺少小程序openid", ""); |
|||
} |
|||
String outAuthorizationNo = StringUtils.defaultIfBlank(user.getInviteTransferAuthNo(), buildAuthorizationNo(user)); |
|||
WechatTransferAuthorizationResult queryResult = transferService.queryAuthorization(outAuthorizationNo, true); |
|||
if (queryResult.isSuccess()) { |
|||
saveAuthorizationResult(user, outAuthorizationNo, queryResult); |
|||
if ("TAKING_EFFECT".equals(queryResult.getState()) || StringUtils.isNotBlank(queryResult.getPackageInfo())) { |
|||
return queryResult; |
|||
} |
|||
} else if (!isAuthorizationNotFound(queryResult)) { |
|||
return queryResult; |
|||
} |
|||
WechatTransferAuthorizationResult result = transferService.applyAuthorization( |
|||
user.getMiniProgramOpenid(), |
|||
StringUtils.defaultIfBlank(user.getNickname(), StringUtils.defaultIfBlank(user.getUsername(), user.getMobile())), |
|||
outAuthorizationNo |
|||
); |
|||
if (result.isSuccess()) { |
|||
saveAuthorizationResult(user, outAuthorizationNo, result); |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
@Override |
|||
public WechatTransferAuthorizationResult refreshTransferAuthorization(User user, boolean displayAuthorization) { |
|||
if (user == null || StringUtils.isBlank(user.getId())) { |
|||
return WechatTransferAuthorizationResult.fail("用户不存在", ""); |
|||
} |
|||
String outAuthorizationNo = StringUtils.defaultIfBlank(user.getInviteTransferAuthNo(), buildAuthorizationNo(user)); |
|||
WechatTransferAuthorizationResult result = transferService.queryAuthorization(outAuthorizationNo, displayAuthorization); |
|||
if (result.isSuccess()) { |
|||
saveAuthorizationResult(user, outAuthorizationNo, result); |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
private void saveAuthorizationResult(User user, String outAuthorizationNo, WechatTransferAuthorizationResult result) { |
|||
Date now = new Date(); |
|||
user.setInviteTransferAuthNo(outAuthorizationNo); |
|||
user.setInviteTransferAuthState(result.getState()); |
|||
if (StringUtils.isNotBlank(result.getAuthorizationId())) { |
|||
user.setInviteTransferAuthId(result.getAuthorizationId()); |
|||
} |
|||
if ("TAKING_EFFECT".equals(result.getState())) { |
|||
user.setInviteTransferAuthTime(now); |
|||
} |
|||
userService.update(user); |
|||
} |
|||
|
|||
private boolean isAuthorizationNotFound(WechatTransferAuthorizationResult result) { |
|||
String message = StringUtils.defaultString(result == null ? "" : result.getMessage()); |
|||
String body = StringUtils.defaultString(result == null ? "" : result.getResponseBody()); |
|||
return message.contains("404") |
|||
|| body.contains("RESOURCE_NOT_EXISTS") |
|||
|| body.contains("NOT_FOUND") |
|||
|| body.contains("不存在"); |
|||
} |
|||
|
|||
@Override |
|||
@Transactional(rollbackFor = Exception.class) |
|||
public InviteRewardResult rewardAfterPhoneBound(User invitedUser, String phoneNumber, String inviterId, String regionId, |
|||
String deviceId, Double lat, Double lng, boolean firstPhoneBind) { |
|||
if (invitedUser == null || StringUtils.isBlank(invitedUser.getId())) { |
|||
return InviteRewardResult.skipped("用户不存在"); |
|||
} |
|||
String finalInviterId = StringUtils.defaultIfBlank(invitedUser.getInviterId(), inviterId); |
|||
String finalRegionId = StringUtils.defaultIfBlank(invitedUser.getInviteRegionId(), regionId); |
|||
String finalDeviceId = StringUtils.defaultIfBlank(invitedUser.getInviteDeviceId(), deviceId); |
|||
if (!firstPhoneBind) { |
|||
return InviteRewardResult.skipped("非首次绑定手机号"); |
|||
} |
|||
if (!Integer.valueOf(1).equals(invitedUser.getInviteNewUser())) { |
|||
return InviteRewardResult.skipped("非邀请链路新用户"); |
|||
} |
|||
if (StringUtils.isBlank(finalInviterId) || StringUtils.isBlank(finalRegionId)) { |
|||
return InviteRewardResult.skipped("缺少邀请信息"); |
|||
} |
|||
if (StringUtils.equals(finalInviterId, invitedUser.getId())) { |
|||
markUserSkipped(invitedUser, finalRegionId, finalDeviceId); |
|||
return InviteRewardResult.skipped("不能邀请自己"); |
|||
} |
|||
User inviter = userService.findById(finalInviterId); |
|||
if (inviter == null || StringUtils.isBlank(inviter.getMiniProgramOpenid())) { |
|||
markUserSkipped(invitedUser, finalRegionId, finalDeviceId); |
|||
return InviteRewardResult.skipped("邀请人缺少微信收款身份"); |
|||
} |
|||
if (!"TAKING_EFFECT".equals(inviter.getInviteTransferAuthState()) |
|||
|| StringUtils.isAllBlank(inviter.getInviteTransferAuthId(), inviter.getInviteTransferAuthNo())) { |
|||
return InviteRewardResult.skipped("邀请人未开通免确认收款授权"); |
|||
} |
|||
if (!isLocationInRegion(finalRegionId, lat, lng)) { |
|||
markUserSkipped(invitedUser, finalRegionId, finalDeviceId); |
|||
return InviteRewardResult.skipped("当前位置不在邀请区域内"); |
|||
} |
|||
InviteRewardConfig config = getEnabledConfig(finalRegionId); |
|||
if (config == null || config.getRewardAmount() == null || config.getRewardAmount().compareTo(BigDecimal.ZERO) <= 0) { |
|||
return InviteRewardResult.skipped("区域未配置拉新奖励"); |
|||
} |
|||
if (hasAnyRewardRecord(invitedUser.getId())) { |
|||
return InviteRewardResult.skipped("该用户拉新奖励已处理"); |
|||
} |
|||
if (hasSuccessfulReward(invitedUser.getId(), phoneNumber, finalDeviceId)) { |
|||
markUserSkipped(invitedUser, finalRegionId, finalDeviceId); |
|||
return InviteRewardResult.skipped("手机号或设备已领取过拉新奖励"); |
|||
} |
|||
if (exceedDailyLimit(config) || exceedTotalBudget(config)) { |
|||
return InviteRewardResult.skipped("区域奖励预算或上限已用完"); |
|||
} |
|||
|
|||
InviteRewardRecord record = createProcessingRecord(invitedUser, phoneNumber, finalInviterId, finalRegionId, finalDeviceId, config.getRewardAmount()); |
|||
WechatMerchantTransferResult transferResult = transferService.transferWithAuthorization( |
|||
inviter.getInviteTransferAuthId(), |
|||
inviter.getInviteTransferAuthNo(), |
|||
config.getRewardAmount(), |
|||
record.getOutBillNo() |
|||
); |
|||
Date now = new Date(); |
|||
record.setUpdateTime(now); |
|||
record.setWxResponse(transferResult.getResponseBody()); |
|||
record.setTransferBillNo(transferResult.getTransferBillNo()); |
|||
if (transferResult.isSuccess()) { |
|||
record.setStatus(STATUS_SUCCESS); |
|||
recordMapper.updateById(record); |
|||
config.setUsedBudget((config.getUsedBudget() == null ? BigDecimal.ZERO : config.getUsedBudget()).add(config.getRewardAmount())); |
|||
config.setUpdateTime(now); |
|||
configMapper.updateById(config); |
|||
invitedUser.setInviteRegionId(finalRegionId); |
|||
invitedUser.setInviteDeviceId(finalDeviceId); |
|||
invitedUser.setInviteRewardStatus(STATUS_SUCCESS); |
|||
invitedUser.setInviteRewardAmount(config.getRewardAmount()); |
|||
invitedUser.setInviteRewardTime(now); |
|||
userService.update(invitedUser); |
|||
return InviteRewardResult.rewarded(config.getRewardAmount(), "拉新奖励已发放"); |
|||
} |
|||
if (transferResult.isProcessing()) { |
|||
record.setStatus(STATUS_PROCESSING); |
|||
record.setFailReason(StringUtils.defaultIfBlank(transferResult.getMessage(), "待邀请人确认收款")); |
|||
recordMapper.updateById(record); |
|||
invitedUser.setInviteRegionId(finalRegionId); |
|||
invitedUser.setInviteDeviceId(finalDeviceId); |
|||
invitedUser.setInviteRewardStatus(STATUS_PROCESSING); |
|||
userService.update(invitedUser); |
|||
return InviteRewardResult.processing(record.getFailReason()); |
|||
} |
|||
record.setStatus(STATUS_FAILED); |
|||
record.setFailReason(StringUtils.defaultIfBlank(transferResult.getMessage(), "微信商家转账失败")); |
|||
recordMapper.updateById(record); |
|||
invitedUser.setInviteRegionId(finalRegionId); |
|||
invitedUser.setInviteDeviceId(finalDeviceId); |
|||
invitedUser.setInviteRewardStatus(STATUS_FAILED); |
|||
userService.update(invitedUser); |
|||
return InviteRewardResult.failed(record.getFailReason()); |
|||
} |
|||
|
|||
@Override |
|||
public InviteRewardConfig getEnabledConfig(String regionId) { |
|||
if (StringUtils.isBlank(regionId)) { |
|||
return null; |
|||
} |
|||
return configMapper.selectOne(new LambdaQueryWrapper<InviteRewardConfig>() |
|||
.eq(InviteRewardConfig::getRegionId, regionId) |
|||
.eq(InviteRewardConfig::getEnabled, 1) |
|||
.last("limit 1")); |
|||
} |
|||
|
|||
private boolean isLocationInRegion(String regionId, Double lat, Double lng) { |
|||
if (StringUtils.isBlank(regionId) || lat == null || lng == null) { |
|||
return false; |
|||
} |
|||
ShopArea matched = shopAreaService.matchByLocation(lat, lng); |
|||
return matched != null && StringUtils.equals(regionId, matched.getId()); |
|||
} |
|||
|
|||
private boolean hasSuccessfulReward(String invitedUserId, String phoneNumber, String deviceId) { |
|||
Long userCount = recordMapper.selectCount(new LambdaQueryWrapper<InviteRewardRecord>() |
|||
.eq(InviteRewardRecord::getStatus, STATUS_SUCCESS) |
|||
.eq(InviteRewardRecord::getInvitedUserId, invitedUserId)); |
|||
if (userCount != null && userCount > 0) { |
|||
return true; |
|||
} |
|||
if (StringUtils.isNotBlank(phoneNumber)) { |
|||
Long mobileCount = recordMapper.selectCount(new LambdaQueryWrapper<InviteRewardRecord>() |
|||
.eq(InviteRewardRecord::getStatus, STATUS_SUCCESS) |
|||
.eq(InviteRewardRecord::getInvitedMobile, phoneNumber)); |
|||
if (mobileCount != null && mobileCount > 0) { |
|||
return true; |
|||
} |
|||
} |
|||
if (StringUtils.isNotBlank(deviceId)) { |
|||
Long deviceCount = recordMapper.selectCount(new LambdaQueryWrapper<InviteRewardRecord>() |
|||
.eq(InviteRewardRecord::getStatus, STATUS_SUCCESS) |
|||
.eq(InviteRewardRecord::getInviteDeviceId, deviceId)); |
|||
return deviceCount != null && deviceCount > 0; |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
private boolean hasAnyRewardRecord(String invitedUserId) { |
|||
Long count = recordMapper.selectCount(new LambdaQueryWrapper<InviteRewardRecord>() |
|||
.eq(InviteRewardRecord::getInvitedUserId, invitedUserId)); |
|||
return count != null && count > 0; |
|||
} |
|||
|
|||
private boolean exceedDailyLimit(InviteRewardConfig config) { |
|||
if (config.getDailyLimit() == null || config.getDailyLimit() <= 0) { |
|||
return false; |
|||
} |
|||
Calendar calendar = Calendar.getInstance(); |
|||
calendar.set(Calendar.HOUR_OF_DAY, 0); |
|||
calendar.set(Calendar.MINUTE, 0); |
|||
calendar.set(Calendar.SECOND, 0); |
|||
calendar.set(Calendar.MILLISECOND, 0); |
|||
Long count = recordMapper.selectCount(new LambdaQueryWrapper<InviteRewardRecord>() |
|||
.eq(InviteRewardRecord::getRegionId, config.getRegionId()) |
|||
.eq(InviteRewardRecord::getStatus, STATUS_SUCCESS) |
|||
.ge(InviteRewardRecord::getCreateTime, calendar.getTime())); |
|||
return count != null && count >= config.getDailyLimit(); |
|||
} |
|||
|
|||
private boolean exceedTotalBudget(InviteRewardConfig config) { |
|||
BigDecimal totalBudget = config.getTotalBudget() == null ? BigDecimal.ZERO : config.getTotalBudget(); |
|||
if (totalBudget.compareTo(BigDecimal.ZERO) <= 0) { |
|||
return false; |
|||
} |
|||
BigDecimal usedBudget = config.getUsedBudget() == null ? BigDecimal.ZERO : config.getUsedBudget(); |
|||
BigDecimal rewardAmount = config.getRewardAmount() == null ? BigDecimal.ZERO : config.getRewardAmount(); |
|||
return usedBudget.add(rewardAmount).compareTo(totalBudget) > 0; |
|||
} |
|||
|
|||
private InviteRewardRecord createProcessingRecord(User invitedUser, String phoneNumber, String inviterId, String regionId, |
|||
String deviceId, BigDecimal amount) { |
|||
Date now = new Date(); |
|||
InviteRewardRecord record = new InviteRewardRecord(); |
|||
record.setId(genId()); |
|||
record.setInviterId(inviterId); |
|||
record.setInvitedUserId(invitedUser.getId()); |
|||
record.setInvitedMobile(phoneNumber); |
|||
record.setInviteDeviceId(deviceId); |
|||
record.setRegionId(regionId); |
|||
record.setAmount(amount); |
|||
record.setStatus(STATUS_PROCESSING); |
|||
record.setOutBillNo(record.getId()); |
|||
record.setCreateTime(now); |
|||
record.setUpdateTime(now); |
|||
recordMapper.insert(record); |
|||
return record; |
|||
} |
|||
|
|||
private String buildAuthorizationNo(User user) { |
|||
String userId = user == null ? "" : StringUtils.defaultString(user.getId()); |
|||
return ("IA" + userId).replaceAll("[^A-Za-z0-9]", ""); |
|||
} |
|||
|
|||
private void markUserSkipped(User user, String regionId, String deviceId) { |
|||
user.setInviteRegionId(regionId); |
|||
user.setInviteDeviceId(deviceId); |
|||
user.setInviteRewardStatus(STATUS_SKIPPED); |
|||
userService.update(user); |
|||
} |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
CREATE TABLE IF NOT EXISTS `t_invite_reward_config` ( |
|||
`id` varchar(64) NOT NULL COMMENT '主键', |
|||
`region_id` varchar(64) NOT NULL COMMENT '区域id', |
|||
`reward_amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '每个有效新用户奖励金额', |
|||
`daily_limit` int(11) NOT NULL DEFAULT '0' COMMENT '每日发放上限 0不限', |
|||
`total_budget` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '总预算 0不限', |
|||
`used_budget` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '已发放预算', |
|||
`enabled` int(11) NOT NULL DEFAULT '1' COMMENT '是否启用 1是 0否', |
|||
`remark` varchar(255) DEFAULT NULL COMMENT '备注', |
|||
`create_time` datetime DEFAULT NULL COMMENT '创建时间', |
|||
`update_time` datetime DEFAULT NULL COMMENT '更新时间', |
|||
PRIMARY KEY (`id`), |
|||
UNIQUE KEY `uk_invite_reward_config_region` (`region_id`) |
|||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='拉新返现配置'; |
|||
|
|||
CREATE TABLE IF NOT EXISTS `t_invite_reward_record` ( |
|||
`id` varchar(64) NOT NULL COMMENT '主键', |
|||
`inviter_id` varchar(64) NOT NULL COMMENT '邀请人用户id', |
|||
`invited_user_id` varchar(64) NOT NULL COMMENT '被邀请人用户id', |
|||
`invited_mobile` varchar(32) DEFAULT NULL COMMENT '被邀请人手机号', |
|||
`invite_device_id` varchar(128) DEFAULT NULL COMMENT '邀请设备标识', |
|||
`region_id` varchar(64) NOT NULL COMMENT '发生奖励时的区域id', |
|||
`amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '奖励金额', |
|||
`status` int(11) NOT NULL DEFAULT '0' COMMENT '状态 0处理中 1成功 2失败 3跳过', |
|||
`out_bill_no` varchar(64) DEFAULT NULL COMMENT '微信商家转账商户单号', |
|||
`transfer_bill_no` varchar(128) DEFAULT NULL COMMENT '微信商家转账单号', |
|||
`fail_reason` varchar(512) DEFAULT NULL COMMENT '失败或跳过原因', |
|||
`wx_response` text COMMENT '微信响应', |
|||
`create_time` datetime DEFAULT NULL COMMENT '创建时间', |
|||
`update_time` datetime DEFAULT NULL COMMENT '更新时间', |
|||
PRIMARY KEY (`id`), |
|||
UNIQUE KEY `uk_invite_reward_invited_user` (`invited_user_id`), |
|||
KEY `idx_invite_reward_mobile` (`invited_mobile`), |
|||
KEY `idx_invite_reward_device` (`invite_device_id`), |
|||
KEY `idx_invite_reward_region_status` (`region_id`, `status`, `create_time`) |
|||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='拉新返现奖励记录'; |
|||
|
|||
ALTER TABLE `t_user` |
|||
ADD COLUMN `invite_region_id` varchar(64) DEFAULT NULL COMMENT '拉新来源区域id', |
|||
ADD COLUMN `invite_device_id` varchar(128) DEFAULT NULL COMMENT '拉新设备标识', |
|||
ADD COLUMN `invite_new_user` int(11) DEFAULT '0' COMMENT '是否邀请链路新用户 1是 0否', |
|||
ADD COLUMN `invite_reward_status` int(11) DEFAULT '0' COMMENT '拉新奖励状态 0未处理 1成功 2失败 3跳过', |
|||
ADD COLUMN `invite_reward_amount` decimal(10,2) DEFAULT '0.00' COMMENT '拉新奖励金额', |
|||
ADD COLUMN `invite_reward_time` datetime DEFAULT NULL COMMENT '拉新奖励时间', |
|||
ADD COLUMN `invite_transfer_auth_no` varchar(64) DEFAULT NULL COMMENT '拉新转账免确认授权商户单号', |
|||
ADD COLUMN `invite_transfer_auth_id` varchar(64) DEFAULT NULL COMMENT '拉新转账微信免确认授权单号', |
|||
ADD COLUMN `invite_transfer_auth_state` varchar(32) DEFAULT NULL COMMENT '拉新转账免确认授权状态', |
|||
ADD COLUMN `invite_transfer_auth_time` datetime DEFAULT NULL COMMENT '拉新转账免确认授权时间'; |
|||
Loading…
Reference in new issue