From f8377665733e7708b523bec1acba4ce17f4b1bd8 Mon Sep 17 00:00:00 2001 From: yixian Date: Wed, 5 Aug 2020 12:10:04 +1000 Subject: [PATCH] risk control: chargeback --- pom.xml | 4 +- .../manage/risk/web/RiskController.java | 16 +- .../core/RiskBusinessService.java | 6 +- .../core/impl/RiskBusinessServiceImpl.java | 414 +++++++++++------- .../web/RiskBusinessController.java | 58 ++- src/main/ui/static/analysis/risk_business.js | 293 +++++++------ .../templates/dialog_holiday_config.html | 3 + .../analysis/templates/new_riskEvent.html | 111 +++-- .../analysis/templates/riskEvent_detail.html | 15 +- 9 files changed, 551 insertions(+), 369 deletions(-) diff --git a/pom.xml b/pom.xml index 330d44d93..437cfb8df 100644 --- a/pom.xml +++ b/pom.xml @@ -5,11 +5,11 @@ au.com.royalpay.payment payment-parent - 2.2.9 + 2.2.10 4.0.0 manage - 2.3.48 + 2.3.49 UTF-8 2.4.0 diff --git a/src/main/java/au/com/royalpay/payment/manage/risk/web/RiskController.java b/src/main/java/au/com/royalpay/payment/manage/risk/web/RiskController.java index 654d65328..517becf17 100644 --- a/src/main/java/au/com/royalpay/payment/manage/risk/web/RiskController.java +++ b/src/main/java/au/com/royalpay/payment/manage/risk/web/RiskController.java @@ -5,9 +5,7 @@ import au.com.royalpay.payment.manage.risk.bean.*; import au.com.royalpay.payment.manage.risk.core.RiskMerchantService; import au.com.royalpay.payment.tools.CommonConsts; import au.com.royalpay.payment.tools.permission.enums.ManagerRole; - import com.alibaba.fastjson.JSONObject; - import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; @@ -16,7 +14,7 @@ import javax.annotation.Resource; * Created by wangning on 08/12/2016. */ @RestController -@ManagerMapping(value = "/sys/risk",role = {ManagerRole.ADMIN,ManagerRole.OPERATOR}) +@ManagerMapping(value = "/sys/risk", role = {ManagerRole.ADMIN, ManagerRole.OPERATOR}) public class RiskController { @Resource @@ -47,17 +45,15 @@ public class RiskController { riskMerchantService.dealRiskRecordDirectly(manager, record_id); } - @RequestMapping(value = "/records/{record_id}/deal",method = RequestMethod.PUT) - public void dealRecord(@ModelAttribute(CommonConsts.MANAGER_STATUS) JSONObject manager, @PathVariable String record_id, @RequestBody DealRiskRecord dealRiskRecord){ + @PutMapping("/records/{record_id}/deal") + public void dealRecord(@ModelAttribute(CommonConsts.MANAGER_STATUS) JSONObject manager, @PathVariable String record_id, @RequestBody DealRiskRecord dealRiskRecord) { dealRiskRecord.setRecordId(record_id); - riskMerchantService.dealRiskRecord(manager,dealRiskRecord); + riskMerchantService.dealRiskRecord(manager, dealRiskRecord); } @GetMapping("/orders") public JSONObject getRiskOrders(QueryRiskOrder queryRiskOrder) { return riskMerchantService.getRiskOrders(queryRiskOrder); - - } @PostMapping("/white/{clientMoniker}") @@ -71,13 +67,13 @@ public class RiskController { } @PostMapping("/records/uploadFiles") - public void addRecordLog(@ModelAttribute(CommonConsts.MANAGER_STATUS) JSONObject manager,@RequestBody AddRiskDetailLog addRiskDetailLog) { + public void addRecordLog(@ModelAttribute(CommonConsts.MANAGER_STATUS) JSONObject manager, @RequestBody AddRiskDetailLog addRiskDetailLog) { // riskMerchantService.addDetailLog(addRiskDetailLog,manager); } @GetMapping("/details") public JSONObject listDetails(QueryRiskDetail queryRiskDetail) { - return riskMerchantService.getRiskDetails(queryRiskDetail); + return riskMerchantService.getRiskDetails(queryRiskDetail); } @PostMapping("/details/remark") diff --git a/src/main/java/au/com/royalpay/payment/manage/riskbusiness/core/RiskBusinessService.java b/src/main/java/au/com/royalpay/payment/manage/riskbusiness/core/RiskBusinessService.java index 920111634..fffbedaec 100644 --- a/src/main/java/au/com/royalpay/payment/manage/riskbusiness/core/RiskBusinessService.java +++ b/src/main/java/au/com/royalpay/payment/manage/riskbusiness/core/RiskBusinessService.java @@ -1,7 +1,7 @@ package au.com.royalpay.payment.manage.riskbusiness.core; +import au.com.royalpay.payment.core.beans.ChargebackStatus; import com.alibaba.fastjson.JSONObject; -import com.github.miemiedev.mybatis.paginator.domain.PageBounds; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @@ -143,5 +143,7 @@ public interface RiskBusinessService { void completeOrderAmount(); - void UpdateRiskEventRemark(String riskId, String remark); + void updateRiskEventRemark(String riskId, String remark); + + void markChargebackStatus(String riskId, JSONObject manager, ChargebackStatus status); } diff --git a/src/main/java/au/com/royalpay/payment/manage/riskbusiness/core/impl/RiskBusinessServiceImpl.java b/src/main/java/au/com/royalpay/payment/manage/riskbusiness/core/impl/RiskBusinessServiceImpl.java index b911619f4..48e3186b6 100644 --- a/src/main/java/au/com/royalpay/payment/manage/riskbusiness/core/impl/RiskBusinessServiceImpl.java +++ b/src/main/java/au/com/royalpay/payment/manage/riskbusiness/core/impl/RiskBusinessServiceImpl.java @@ -1,8 +1,9 @@ package au.com.royalpay.payment.manage.riskbusiness.core.impl; +import au.com.royalpay.payment.core.CardSecureService; +import au.com.royalpay.payment.core.beans.ChargebackStatus; import au.com.royalpay.payment.core.exceptions.EmailException; import au.com.royalpay.payment.core.exceptions.InvalidShortIdException; -import au.com.royalpay.payment.core.exceptions.OrderNotExistsException; import au.com.royalpay.payment.core.exceptions.OrderNotMatchException; import au.com.royalpay.payment.manage.mappers.log.AppMessageLogMapper; import au.com.royalpay.payment.manage.mappers.payment.OrderMapper; @@ -20,11 +21,11 @@ import au.com.royalpay.payment.manage.riskbusiness.core.RiskMaterialService; import au.com.royalpay.payment.manage.riskbusiness.core.RiskProcessLogService; import au.com.royalpay.payment.manage.riskbusiness.core.RiskUploadService; import au.com.royalpay.payment.manage.riskbusiness.enums.RiskEmailStatusEnum; +import au.com.royalpay.payment.manage.riskbusiness.enums.RiskOrderTypeEnum; import au.com.royalpay.payment.manage.riskbusiness.enums.RiskResultTypeEnum; import au.com.royalpay.payment.manage.signin.beans.TodoNotice; import au.com.royalpay.payment.manage.signin.core.ManagerTodoNoticeProvider; import au.com.royalpay.payment.manage.tradelog.core.TradeLogService; -import au.com.royalpay.payment.manage.riskbusiness.enums.RiskOrderTypeEnum; import au.com.royalpay.payment.tools.connections.mpsupport.MpWechatApi; import au.com.royalpay.payment.tools.connections.mpsupport.MpWechatApiProvider; import au.com.royalpay.payment.tools.connections.mpsupport.beans.TemplateMessage; @@ -34,23 +35,28 @@ import au.com.royalpay.payment.tools.device.message.AppMsgSender; import au.com.royalpay.payment.tools.env.PlatformEnvironment; import au.com.royalpay.payment.tools.env.SysConfigManager; import au.com.royalpay.payment.tools.exceptions.BadRequestException; +import au.com.royalpay.payment.tools.exceptions.NotFoundException; +import au.com.royalpay.payment.tools.exceptions.ServerErrorException; import au.com.royalpay.payment.tools.exceptions.event.WechatExceptionEvent; import au.com.royalpay.payment.tools.locale.LocaleSupport; +import au.com.royalpay.payment.tools.mappers.CommonRiskEventMapper; +import au.com.royalpay.payment.tools.mappers.RiskEventDAO; import au.com.royalpay.payment.tools.permission.enums.ManagerRole; -import au.com.royalpay.payment.tools.exceptions.ServerErrorException; +import au.com.royalpay.payment.tools.risk.RiskEvent; import au.com.royalpay.payment.tools.threadpool.RoyalThreadPoolExecutor; import au.com.royalpay.payment.tools.utils.PageListUtils; -import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.github.miemiedev.mybatis.paginator.domain.Order; import com.github.miemiedev.mybatis.paginator.domain.PageBounds; import com.github.miemiedev.mybatis.paginator.domain.PageList; -import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.DateFormatUtils; import org.apache.commons.lang3.time.DateUtils; -import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -62,17 +68,19 @@ import org.springframework.transaction.annotation.Transactional; import org.thymeleaf.context.Context; import org.thymeleaf.spring5.SpringTemplateEngine; +import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; -import java.io.*; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.net.URL; import java.text.ParseException; import java.util.*; -import javax.annotation.Resource; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; -import java.util.concurrent.TimeUnit; /** * @Author lvjian @@ -80,12 +88,20 @@ import java.util.concurrent.TimeUnit; */ @Service public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodoNoticeProvider, ApplicationEventPublisherAware { + public static final int RISK_ORDER_TYPE_WX = 1; + public static final int RISK_ORDER_TYPE_ALIPAY = 2; + public static final int RISK_ORDER_TYPE_RP = 3; + public static final int RISK_ORDER_TYPE_WARN = 4; + public static final int RISK_ORDER_TYPE_COMMON_MID = 5; + public static final int RISK_ORDER_TYPE_CHARGEBACK = 6; private Logger logger = LoggerFactory.getLogger(RiskBusinessServiceImpl.class); @Resource private RiskEventMapper riskEventMapper; @Resource + private CommonRiskEventMapper commonRiskEventMapper; + @Resource private ClientMapper clientMapper; @Resource private ClientBDMapper clientBDMapper; @@ -123,11 +139,13 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo @Resource private AppMessageLogMapper appMessageLogMapper; - private Map senderMap = new HashMap<>(); + private final Map senderMap = new HashMap<>(); @Resource private APNSMessageHelper apnsMessageHelper; @Resource + private CardSecureService cardSecureService; + @Resource private ManagerMapper managerMapper; @Resource private ClientAccountMapper clientAccountMapper; @@ -144,7 +162,7 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo Arrays.stream(senders).forEach(appMsgSender -> senderMap.put(appMsgSender.devType(), appMsgSender)); } - private ThreadPoolExecutor sendingAppleMsgPool = new ThreadPoolExecutor(10, 30, 5, TimeUnit.SECONDS, new LinkedBlockingQueue()); + private final ThreadPoolExecutor sendingAppleMsgPool = new ThreadPoolExecutor(10, 30, 5, TimeUnit.SECONDS, new LinkedBlockingQueue()); @Override public List getRiskEvents(JSONObject params) { @@ -158,7 +176,7 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo if (manager != null && ManagerRole.BD_USER.hasRole(manager.getIntValue("role"))) { params.put("bd_id", manager.getString("manager_id")); List orderTypes = Arrays.asList(RiskOrderTypeEnum.WECHAT_ORDER.getOrderType(), - RiskOrderTypeEnum.ALIPAY_ORDER.getOrderType(),RiskOrderTypeEnum.ROYALPAY_ORDER.getOrderType()); + RiskOrderTypeEnum.ALIPAY_ORDER.getOrderType(), RiskOrderTypeEnum.ROYALPAY_ORDER.getOrderType()); params.put("order_types", orderTypes); List resultTypes = Arrays.asList(RiskResultTypeEnum.SEND_EMAIL_TO_BD.getResultType(), @@ -176,8 +194,10 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo * 微信调单、支付宝调单、royalpay调单 * 未处理、已发送、待审核、打回 */ - Date currentDate = new Date(), replyDate; - Integer resultType, orderType; + Date currentDate = new Date(); + Date replyDate; + Integer resultType; + Integer orderType; boolean isPassTimeout = false; for (JSONObject riskEvent : riskEvents) { try { @@ -206,15 +226,15 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo @Override public List analysisByIndustry(JSONObject params, JSONObject manager) { List industryAnalysis = riskEventMapper.analysisByIndustry(params); - for(JSONObject industry:industryAnalysis){ - if(StringUtils.isBlank(industry.getString("industry"))){ - industry.put("industry","未知行业"); + for (JSONObject industry : industryAnalysis) { + if (StringUtils.isBlank(industry.getString("industry"))) { + industry.put("industry", "未知行业"); } } return industryAnalysis; } - private List industryArray(){ + private List industryArray() { List industryArray = new ArrayList<>(); industryArray.add("鞋包服饰"); industryArray.add("机票行业"); @@ -278,13 +298,13 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo * 通用号调单与其他类型的调单,订单格式不一样,所以区分 */ if (riskEvent.getIntValue("order_type") == 3) { - for (int i = 0; i < orderIdArray.length; i++) { - orderInfo = tradeLogService.getOrderDetail(new JSONObject(), riskEvent.getString("client_moniker"), orderIdArray[i], null); + for (String orderId : orderIdArray) { + orderInfo = tradeLogService.getOrderDetail(new JSONObject(), riskEvent.getString("client_moniker"), orderId, null); tradeLogs.add(orderInfo); } } else { - for (int i = 0; i < orderIdArray.length; i++) { - orderInfo = orderMapper.findOrderById(orderIdArray[i],client.getIntValue("client_id")); + for (String orderId : orderIdArray) { + orderInfo = orderMapper.findOrderById(orderId, client.getIntValue("client_id")); tradeLogs.add(orderInfo); } } @@ -298,7 +318,7 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo String clientMoniker = params.getString("client_moniker"); if (clientMoniker != null) { client = clientMapper.findClientByMonikerAll(clientMoniker); - if(client == null){ + if (client == null) { throw new InvalidShortIdException(); } } @@ -322,7 +342,7 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo PageList transactionList = transactionMapper.findByClientIdAndSystemTransactionId(client.getIntValue("client_id"), orderIdArray[i], new PageBounds(Order.formString("transaction_time.desc"))); // 判断该笔订单是否存在,是否属于该商户 // 由于查询订单时已经关联商户了,所以只会抛出订单不匹配的异常 - if (transactionList == null || transactionList.size() <= 0) + if (transactionList == null || transactionList.isEmpty()) throw new OrderNotMatchException(); /* else { @@ -349,6 +369,7 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo * 添加风控事件 * 更新risk_orders表 * 记录风控日志 + * * @param params * @param manager */ @@ -364,38 +385,51 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo RiskResultTypeEnum.NOT_HANDLED.getRemark(), RiskResultTypeEnum.NOT_HANDLED.getResultType(), RiskResultTypeEnum.NOT_HANDLED.getResultType()); + int orderType = params.getIntValue("order_type"); + if (orderType == RISK_ORDER_TYPE_CHARGEBACK) { + logger.info("Chargeback event"); + RiskEvent evt = getRiskEvent(params.getString("risk_id")); + if (evt == null) { + return; + } + cardSecureService.submitChargeBackReport(evt); + } + } + + private RiskEvent getRiskEvent(String riskId) { + return Optional.ofNullable(commonRiskEventMapper.findById(riskId)).map(RiskEventDAO::convert).orElse(null); } - private void setRiskOrders(JSONObject params){ - if(StringUtils.isNotBlank(params.getString("real_order_ids"))){ + private void setRiskOrders(JSONObject params) { + if (StringUtils.isNotBlank(params.getString("real_order_ids"))) { String riskId = params.getString("risk_id"); String orderIds = params.getString("real_order_ids"); - for(String orderId : orderIds.split(",")){ + for (String orderId : orderIds.split(",")) { JSONObject order = transactionMapper.findByOrderId(orderId); JSONObject riskOrder = new JSONObject(); - riskOrder.put("risk_id",riskId); - riskOrder.put("order_id",orderId); - riskOrder.put("clearing_amount",order.getString("clearing_amount")); + riskOrder.put("risk_id", riskId); + riskOrder.put("order_id", orderId); + riskOrder.put("clearing_amount", order.getString("clearing_amount")); riskOrdersMapper.save(riskOrder); } } } @Override - public void updateRiskEvent(JSONObject params,JSONObject manager) { - if(StringUtils.isNotBlank(params.getString("channel_result"))){ + public void updateRiskEvent(JSONObject params, JSONObject manager) { + if (StringUtils.isNotBlank(params.getString("channel_result"))) { JSONObject risk = riskEventMapper.findById(params.getString("risk_id")); //填写渠道处理结果时候,支付宝、微信和内部调单在材料审核通过前无法填写渠道处理结果 - if(params.getIntValue("result_type") < RiskResultTypeEnum.MATERIAL_AUDIT_PASS.getResultType() && params.getIntValue("order_type") < RiskOrderTypeEnum.WARNING_ORDER.getOrderType()){ + if (params.getIntValue("result_type") < RiskResultTypeEnum.MATERIAL_AUDIT_PASS.getResultType() && params.getIntValue("order_type") < RiskOrderTypeEnum.WARNING_ORDER.getOrderType()) { throw new BadRequestException("请先提交渠道方材料进行审核!"); } //渠道处理结果不一样,才会记录操作并更改事件单状态为渠道方处理结果 - if(risk.getString("channel_result")==null || !risk.getString("channel_result").equals(params.getString("channel_result"))){ - params.put("result_type",RiskResultTypeEnum.ALREADY_HANDLED.getResultType()); + if (risk.getString("channel_result") == null || !risk.getString("channel_result").equals(params.getString("channel_result"))) { + params.put("result_type", RiskResultTypeEnum.ALREADY_HANDLED.getResultType()); riskProcessLogService.addRiskProcessLog(params.getString("risk_id"), manager.getString("manager_id"), manager.getString("display_name"), - RiskResultTypeEnum.ALREADY_HANDLED.getRemark() + ":"+ params.getString("channel_result"), + RiskResultTypeEnum.ALREADY_HANDLED.getRemark() + ":" + params.getString("channel_result"), params.getIntValue("result_type"), RiskResultTypeEnum.ALREADY_HANDLED.getResultType()); } @@ -411,12 +445,12 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo public void sendWxMess(JSONObject params, String channel, int result_type) { String type = "禁用"; if (params.get("temporary_close_merchant") != null) { - if (params.getBoolean("temporary_close_merchant")) { + if (params.getBooleanValue("temporary_close_merchant")) { type = "临时禁用"; } } if (params.get("temporary_close_channel") != null) { - if (params.getBoolean("temporary_close_channel")) { + if (params.getBooleanValue("temporary_close_channel")) { type = "临时禁用"; } } @@ -437,14 +471,14 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo for (String bdId : bdIdArray) { JSONObject bd = managerMapper.findById(bdId); if (bd != null) { - if (bd.get("wx_openid")!=null){ + if (bd.get("wx_openid") != null) { if (StringUtils.isNotBlank(bd.getString("wx_openid"))) { try { MpWechatApi paymentApi = mpWechatApiProvider.getNewPaymentApi(); - TemplateMessage msg = initSendBDCloseChannelAndPartnerTemplate(bd.getString("wx_openid"), paymentApi.getTemplateId("risk-forbidden-channelAndPartner"),client); + TemplateMessage msg = initSendBDCloseChannelAndPartnerTemplate(bd.getString("wx_openid"), paymentApi.getTemplateId("risk-forbidden-channelAndPartner"), client); paymentApi.sendTemplateMessage(msg); } catch (WechatException e) { - logger.error("Wechat Message Error,风控关闭通道" + e.getMessage()); + logger.error("Wechat Message Error,风控关闭通道{}", e.getMessage()); publisher.publishEvent(new WechatExceptionEvent(this, e, "风控关闭通道")); } } @@ -455,14 +489,14 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo List accounts = clientAccountMapper.listRiskNoticeAccounts(client.getIntValue("client_id")); if (accounts != null) { for (JSONObject account : accounts) { - if (account.get("wechat_openid")!=null){ + if (account.get("wechat_openid") != null) { if (StringUtils.isNotBlank(account.getString("wechat_openid"))) { try { MpWechatApi paymentApi = mpWechatApiProvider.getNewPaymentApi(); - TemplateMessage msg = initSendPartnerCloseChannelAndPartnerTemplate(account.getString("wechat_openid"), paymentApi.getTemplateId("risk-forbidden-channelAndPartner"),client); + TemplateMessage msg = initSendPartnerCloseChannelAndPartnerTemplate(account.getString("wechat_openid"), paymentApi.getTemplateId("risk-forbidden-channelAndPartner"), client); paymentApi.sendTemplateMessage(msg); } catch (WechatException e) { - logger.error("Wechat Message Error,风控关闭通道" + e.getMessage()); + logger.error("Wechat Message Error,风控关闭通道,{}", e.getMessage()); publisher.publishEvent(new WechatExceptionEvent(this, e, "风控关闭通道")); } } @@ -473,26 +507,27 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo } - private TemplateMessage initSendBDCloseChannelAndPartnerTemplate(String wxopenid, String templateId,JSONObject client) { - TemplateMessage msg = new TemplateMessage(wxopenid, templateId,null); + private TemplateMessage initSendBDCloseChannelAndPartnerTemplate(String wxopenid, String templateId, JSONObject client) { + TemplateMessage msg = new TemplateMessage(wxopenid, templateId, null); //1:关闭渠道;2:关闭商户 if (client.getIntValue("result_type") == 1) { - msg.put("first", "您的商户("+client.getString("client_moniker")+")" + client.getString("channel") + "支付渠道已"+client.getString("close_type"), "#000000"); - }else { - msg.put("first", "您的商户("+client.getString("client_moniker")+")已"+client.getString("close_type"), "#000000"); + msg.put("first", "您的商户(" + client.getString("client_moniker") + ")" + client.getString("channel") + "支付渠道已" + client.getString("close_type"), "#000000"); + } else { + msg.put("first", "您的商户(" + client.getString("client_moniker") + ")已" + client.getString("close_type"), "#000000"); } msg.put("keyword1", client.getString("channel") + "风控调单", "#0000ff"); msg.put("keyword2", DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss"), "#000000"); msg.put("remark", "请联系商户尽快配合风控部门上传相应资料,以免影响支付", "#0000FF"); return msg; } - private TemplateMessage initSendPartnerCloseChannelAndPartnerTemplate(String wxopenid, String templateId,JSONObject client) { - TemplateMessage msg = new TemplateMessage(wxopenid, templateId,null); + + private TemplateMessage initSendPartnerCloseChannelAndPartnerTemplate(String wxopenid, String templateId, JSONObject client) { + TemplateMessage msg = new TemplateMessage(wxopenid, templateId, null); //1:关闭渠道;2:关闭商户 if (client.getIntValue("result_type") == 1) { - msg.put("first", "您的商户" + client.getString("channel") + "支付渠道已"+client.getString("close_type"), "#000000"); - }else { - msg.put("first", "您的商户("+client.getString("client_moniker")+")已"+client.getString("close_type"), "#000000"); + msg.put("first", "您的商户" + client.getString("channel") + "支付渠道已" + client.getString("close_type"), "#000000"); + } else { + msg.put("first", "您的商户(" + client.getString("client_moniker") + ")已" + client.getString("close_type"), "#000000"); } msg.put("keyword1", client.getString("channel") + "风控调单", "#0000ff"); msg.put("keyword2", DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss"), "#000000"); @@ -507,6 +542,7 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo /** * 下载审核材料 + * * @param riskId * @param response */ @@ -524,11 +560,11 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo response.setContentType("application/octet-stream"); response.setHeader("Content-Disposition", "attachment;filename=" + downloadFilename); ZipOutputStream zos = new ZipOutputStream(response.getOutputStream()); - for(int i = 1; i <= 6; i++){ - if(riskMaterial.containsKey("file" + i)){ - List fileList= (List) riskMaterial.get("file"+i); - for(String fileUrl : fileList){ - zos.putNextEntry(new ZipEntry("file" + i+fileUrl.substring(fileUrl.lastIndexOf("/")))); + for (int i = 1; i <= 6; i++) { + if (riskMaterial.containsKey("file" + i)) { + List fileList = (List) riskMaterial.get("file" + i); + for (String fileUrl : fileList) { + zos.putNextEntry(new ZipEntry("file" + i + fileUrl.substring(fileUrl.lastIndexOf("/")))); InputStream inputStream = new URL(fileUrl).openConnection().getInputStream(); byte[] buffer = new byte[1024]; int result = 0; @@ -551,6 +587,7 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo * 更新事件信息 * 记录风控日志 * App推送消息 + * * @param riskId * @throws IOException */ @@ -558,22 +595,22 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo public void sendUploadEmail(String riskId) throws IOException { JSONObject event = getRiskEventDetail(riskId); Context ctx = getMailContext(event); - final List emailsTos = (List)ctx.getVariable("emailsTos"); - final List emailsCcs = ctx.getVariable("emailsCcs")==null?new ArrayList<>():(List)ctx.getVariable("emailsCcs"); + final List emailsTos = (List) ctx.getVariable("emailsTos"); + final List emailsCcs = ctx.getVariable("emailsCcs") == null ? new ArrayList<>() : (List) ctx.getVariable("emailsCcs"); final List emailsBccs = ctx.getVariable("emailsBccs") == null ? new ArrayList<>() : (List) ctx.getVariable("emailsBccs"); - final String title = (String)ctx.getVariable("title"); + final String title = (String) ctx.getVariable("title"); final String content = thymeleaf.process("mail/risk_upload_mail.html", ctx); - final String uploadUrl = (String)ctx.getVariable("uploadUrl"); + final String uploadUrl = (String) ctx.getVariable("uploadUrl"); royalThreadPoolExecutor.execute(() -> { try { String emailId = mailService.sendRiskEmail(title, emailsTos.isEmpty() ? "" : StringUtils.join(emailsTos, ","), emailsCcs.isEmpty() ? "" : StringUtils.join(emailsCcs, ","), emailsBccs.isEmpty() ? "" : StringUtils.join(emailsBccs, ","), - content, null,event.getIntValue("order_type")); + content, null, event.getIntValue("order_type")); event.put("email_status", RiskEmailStatusEnum.ALREADY_SEND.getEmailStatus()); event.put("result_type", RiskResultTypeEnum.SEND_EMAIL_TO_BD.getResultType()); - event.put("submit_url",uploadUrl); + event.put("submit_url", uploadUrl); Integer orderType = event.getInteger("order_type"); if (orderType.equals(RiskOrderTypeEnum.WARNING_ORDER.getOrderType())) { event.put("result_type", RiskResultTypeEnum.ALREADY_HANDLED.getResultType()); @@ -585,7 +622,7 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo RiskResultTypeEnum.SEND_EMAIL_TO_BD.getRemark(), RiskResultTypeEnum.NOT_HANDLED.getResultType(), RiskResultTypeEnum.SEND_EMAIL_TO_BD.getResultType()); - if(event.getIntValue("is_send_client") == 1){ + if (event.getIntValue("is_send_client") == 1) { sendAppRiskMessage(event); } } catch (Exception e) { @@ -597,11 +634,12 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo /** * App推送消息 * 推送类型risk + * * @param event */ - private void sendAppRiskMessage(JSONObject event){ + private void sendAppRiskMessage(JSONObject event) { JSONObject client = clientMapper.findClientByMoniker(event.getString("client_moniker")); - logger.debug("sendRiskAppMessage-" + client.getString("client_moniker") + "-" + "risk_id:"+event.getString("risk_id")); + logger.debug("sendRiskAppMessage-{}-risk_id:{}", client.getString("client_moniker"), event.getString("risk_id")); List tokens = clientDeviceTokenMapper.listTokensByClient_id(client.getIntValue("client_id")); for (JSONObject devToken : tokens) { Runnable task = () -> { @@ -634,7 +672,7 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo log.put("status", 2); appMessageLogMapper.update(log); } catch (Exception e) { - logger.error("出错了:" + e.getMessage()); + logger.error("出错了:{}", e.getMessage()); appMessageLogMapper.updateStatus(log.getString("send_id"), 1, e.getMessage()); throw new ServerErrorException("Send App " + devToken.getString("client_type") + " Failed" + ",token" + token, e); } @@ -645,6 +683,7 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo /** * 记录消息推送日志 + * * @param dev_id * @param client_id * @param messageType @@ -670,6 +709,7 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo * 判断当前事件处理状态是否合理 * 更新事件 * 记录风控日志 + * * @param riskId * @param refuseDescription 拒绝说明 * @throws IOException @@ -683,29 +723,29 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo throw new BadRequestException("Refused Already!"); } else if (RiskResultTypeEnum.MATERIAL_AUDIT_PASS.getResultType().intValue() == resultType) { throw new BadRequestException("Passed Already!"); - } else if (RiskResultTypeEnum.WAIT_FOR_AUDIT.getResultType().intValue() != resultType){ + } else if (RiskResultTypeEnum.WAIT_FOR_AUDIT.getResultType().intValue() != resultType) { throw new BadRequestException(); } riskMaterialService.updateRiskMaterial(riskId, refuseDescription); Context ctx = getMailContext(event); - ctx.setVariable("refuse",true); + ctx.setVariable("refuse", true); List findAllMaterials = riskMaterialMapper.findAllMaterials(riskId); if (findAllMaterials == null || findAllMaterials.size() == 0) { ctx.setVariable("refuse_description", ""); - }else { + } else { ctx.setVariable("refuse_description", findAllMaterials.get(0).getString("refuse_description")); } - final List emailsTos = (List)ctx.getVariable("emailsTos"); - final List emailsCcs = ctx.getVariable("emailsCcs")==null?new ArrayList<>():(List)ctx.getVariable("emailsCcs"); - final String uploadUrl = (String)ctx.getVariable("uploadUrl"); + final List emailsTos = (List) ctx.getVariable("emailsTos"); + final List emailsCcs = ctx.getVariable("emailsCcs") == null ? new ArrayList<>() : (List) ctx.getVariable("emailsCcs"); + final String uploadUrl = (String) ctx.getVariable("uploadUrl"); final String content = thymeleaf.process("mail/risk_upload_mail.html", ctx); royalThreadPoolExecutor.execute(() -> { try { String emailId = mailService.sendRiskEmail("You need to resubmit risk materials", emailsTos.isEmpty() ? "" : StringUtils.join(emailsTos, ","), - emailsCcs.isEmpty() ? "" : StringUtils.join(emailsCcs, ","), "",content, null,event.getIntValue("order_type")); - event.put("email_status",RiskEmailStatusEnum.BACK_AND_SEND.getEmailStatus()); - event.put("result_type",RiskResultTypeEnum.MATERIAL_NOT_PASS.getResultType()); - event.put("submit_url",uploadUrl); + emailsCcs.isEmpty() ? "" : StringUtils.join(emailsCcs, ","), "", content, null, event.getIntValue("order_type")); + event.put("email_status", RiskEmailStatusEnum.BACK_AND_SEND.getEmailStatus()); + event.put("result_type", RiskResultTypeEnum.MATERIAL_NOT_PASS.getResultType()); + event.put("submit_url", uploadUrl); riskEventMapper.update(event); riskProcessLogService.addRiskProcessLog(riskId, event.getString("fillin_id"), @@ -721,6 +761,7 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo /** * 发送催促邮件 + * * @param riskId * @throws IOException */ @@ -728,14 +769,14 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo public void sendUrgeEmail(String riskId) throws IOException { JSONObject event = getRiskEventDetail(riskId); Context ctx = getMailContext(event); - final List emailsTos = (List)ctx.getVariable("emailsTos"); - final List emailsCcs = ctx.getVariable("emailsCcs")==null?new ArrayList<>():(List)ctx.getVariable("emailsCcs"); + final List emailsTos = (List) ctx.getVariable("emailsTos"); + final List emailsCcs = ctx.getVariable("emailsCcs") == null ? new ArrayList<>() : (List) ctx.getVariable("emailsCcs"); final String content = thymeleaf.process("mail/risk_urge_mail.html", ctx); royalThreadPoolExecutor.execute(() -> { try { String emailId = mailService.sendRiskEmail("Please submit risk materials as soon as possible", emailsTos.isEmpty() ? "" : StringUtils.join(emailsTos, ","), - emailsCcs.isEmpty() ? "" : StringUtils.join(emailsCcs, ","),"", content, null,event.getIntValue("order_type")); - event.put("email_status",3); + emailsCcs.isEmpty() ? "" : StringUtils.join(emailsCcs, ","), "", content, null, event.getIntValue("order_type")); + event.put("email_status", 3); riskEventMapper.update(event); } catch (Exception e) { throw new EmailException("Email Sending Failed", e); @@ -750,20 +791,14 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo throw new InvalidShortIdException(); } String codeKey = RandomStringUtils.random(20, true, true); - while(stringRedisTemplate.boundValueOps(getRiskUploadKey(codeKey)).get()!=null ){ + while (stringRedisTemplate.boundValueOps(getRiskUploadKey(codeKey)).get() != null) { codeKey = RandomStringUtils.random(20, true, true); } String codeKeyValue = RandomStringUtils.random(10, true, true); - /* - String expireDay = "7"; - if(event.getIntValue("order_type")>2){ - expireDay = "3"; - } - )*/ // 原来设定的过期时间是7天 String expireDay = "3650"; stringRedisTemplate.boundValueOps(getRiskUploadKey(codeKey)).set(codeKeyValue, Long.parseLong(expireDay), TimeUnit.DAYS); - String uploadUrl = PlatformEnvironment.getEnv().concatUrl("/risk/upload/") + event.getString("risk_id") + "/" + codeKey; + String uploadUrl = PlatformEnvironment.getEnv().concatUrl("/risk/upload/") + event.getString("risk_id") + "/" + codeKey; int orderType = event.getIntValue("order_type"); if (orderType == 1 || orderType == 2) uploadUrl = PlatformEnvironment.getEnv().concatUrl("/manage.html#/analysis/monitoring/") + event.getString("risk_id") + "/bd/detail?codeKey=" + codeKey; @@ -782,10 +817,10 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo } List clientEmails = new ArrayList<>(); clientEmails.add(client.getString("contact_email")); - client.put("is_resend",new Date().compareTo(event.getDate("reply_email_date"))); + client.put("is_resend", new Date().compareTo(event.getDate("reply_email_date"))); String bdNamesStr = bdNames.isEmpty() ? "" : StringUtils.join(bdNames, ","); - String reply_date = DateFormatUtils.format(DateUtils.addDays(event.getDate("reply_email_date"),-1),"yyyy年MM月dd日"); - String reply_date_english = DateFormatUtils.format(DateUtils.addDays(event.getDate("reply_email_date"),-1),"dd/MM/yyyy"); + String reply_date = DateFormatUtils.format(DateUtils.addDays(event.getDate("reply_email_date"), -1), "yyyy年MM月dd日"); + String reply_date_english = DateFormatUtils.format(DateUtils.addDays(event.getDate("reply_email_date"), -1), "dd/MM/yyyy"); GregorianCalendar gregorianCalendar = new GregorianCalendar(); String hello = gregorianCalendar.get(GregorianCalendar.AM_PM) == 0 ? "上午好" : "下午好"; Context ctx = new Context(); @@ -799,9 +834,9 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo ctx.setVariable("royalpay_order_type", event.getIntValue("royalpay_order_type")); ctx.setVariable("warning_order_type", event.getIntValue("warning_order_type")); ctx.setVariable("description", event.getString("description")); - ctx.setVariable("is_send_client",event.getIntValue("is_send_client")); - ctx.setVariable("mail_template",event.get("mail_template")==null?0:event.getIntValue("mail_template")); - ctx.setVariable("send_clean_days",event.getIntValue("send_clean_days")); + ctx.setVariable("is_send_client", event.getIntValue("is_send_client")); + ctx.setVariable("mail_template", event.get("mail_template") == null ? 0 : event.getIntValue("mail_template")); + ctx.setVariable("send_clean_days", event.getIntValue("send_clean_days")); String realOrderIdsStr = event.getString("real_order_ids"); String[] realOrderIds = {}; if (StringUtils.isNotBlank(realOrderIdsStr)) { @@ -857,24 +892,24 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo } } - switch (event.getIntValue("order_type")){ + switch (event.getIntValue("order_type")) { case 1: case 2: - for(String orderId : realOrderIds){ - JSONObject order = orderMapper.findOrderById(orderId,client.getIntValue("client_id")); - if(order==null){ + for (String orderId : realOrderIds) { + JSONObject order = orderMapper.findOrderById(orderId, client.getIntValue("client_id")); + if (order == null) { throw new BadRequestException("Order: " + orderId + " not exists"); } orders.add(order); } ctx.setVariable("orders", orders); - ctx.setVariable("title","Your merchants needs to submit risk materials"); + ctx.setVariable("title", "Your merchants needs to submit risk materials"); break; case 3: - for(String orderId : realOrderIds){ + for (String orderId : realOrderIds) { JSONObject order = tradeLogService.getOrderDetail(new JSONObject(), clientMoniker, orderId, null); - if(order==null){ - throw new BadRequestException("Order: "+orderId+" not exists"); + if (order == null) { + throw new BadRequestException("Order: " + orderId + " not exists"); } order.put("order_description", StringUtils.defaultString(order.getString("order_description"))); order.put("gateway", getGateWay(order.getIntValue("gateway"))); @@ -883,45 +918,41 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo order.put("total_amount", order.getString("currency") + " " + order.getString("total_amount")); order.put("display_amount", order.getString("currency") + " " + order.getString("display_amount")); order.put("customer_payment_amount", order.getString("currency") + " " + order.getString("customer_payment_amount")); - order.put("clearing_amount", "AUD "+ order.getString("clearing_amount")); + order.put("clearing_amount", "AUD " + order.getString("clearing_amount")); orders.add(order); } ctx.setVariable("orders", orders); -// List attachList = new ArrayList<>(); -// JSONObject file = new JSONObject(); -// file.put("name", client.getString("short_name")+ "被查单号相关信息.xlsx"); -// file.put("content", Base64.encodeBase64String(generateRiskOrders(event))); -// attachList.add(file); -// ctx.setVariable("files",attachList); case 4: - ctx.setVariable("title","RoyalPay风控调查 — " + client.getString("short_name")); + ctx.setVariable("title", "RoyalPay风控调查 — " + client.getString("short_name")); break; } return ctx; } - private String getRiskUploadKey(String codeKey){ + + private String getRiskUploadKey(String codeKey) { return UPLOAD_MAIL_PREFIX + codeKey; } /** * 获取风控事件上传的材料 + * * @param param * @return */ @Override public JSONObject getRiskMaterial(JSONObject param) { List riskMaterialList = riskMaterialMapper.findAllMaterials(param.getString("risk_id")); - if (riskMaterialList != null && riskMaterialList.size() > 0){ + if (riskMaterialList != null && riskMaterialList.size() > 0) { JSONObject fileNew = riskMaterialList.get(0); List files = riskFileMapper.findAllFiles(fileNew.getString("material_id")); - for(JSONObject file : files){ + for (JSONObject file : files) { int fileType = file.getIntValue("file_type"); - if(!fileNew.containsKey("file" + fileType)){ - List fileList = new ArrayList<>(); - fileList.add(file.getString("file_url")); - fileNew.put("file" + fileType, fileList); - }else{ - List fileList = (List) fileNew.get("file"+fileType); + if (!fileNew.containsKey("file" + fileType)) { + List fileList = new ArrayList<>(); + fileList.add(file.getString("file_url")); + fileNew.put("file" + fileType, fileList); + } else { + List fileList = (List) fileNew.get("file" + fileType); fileList.add(file.getString("file_url")); fileNew.put("file" + fileType, fileList); } @@ -973,7 +1004,7 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo String[] orderIds = event.getString("order_ids").split(","); JSONObject client = clientMapper.findClientByMonikerAll(event.getString("client_moniker")); Workbook wb = new XSSFWorkbook(); - for(String orderId : orderIds){ + for (String orderId : orderIds) { JSONObject orderDetail = tradeLogService.getOrderDetail(new JSONObject(), event.getString("client_moniker"), orderId, null); Sheet sheet = wb.createSheet(orderId); sheet.setDefaultColumnWidth((short) 40); @@ -993,45 +1024,46 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo Row row13 = sheet.createRow(13); Row row14 = sheet.createRow(14); Row row15 = sheet.createRow(15); - row0.createCell(0,Cell.CELL_TYPE_STRING).setCellValue("Partner"); - row0.createCell(1,Cell.CELL_TYPE_STRING).setCellValue(orderDetail.getJSONObject("client").getString("short_name")+"("+orderDetail.getJSONObject("client").getString("client_moniker")+")"); - row1.createCell(0,Cell.CELL_TYPE_STRING).setCellValue("Order ID"); - row1.createCell(1,Cell.CELL_TYPE_STRING).setCellValue(orderDetail.getString("order_id")); - row2.createCell(0,Cell.CELL_TYPE_STRING).setCellValue("Platform Transaction ID"); - row2.createCell(1,Cell.CELL_TYPE_STRING).setCellValue(orderDetail.getString("system_transaction_id")); - row3.createCell(0,Cell.CELL_TYPE_STRING).setCellValue("Order Description"); - row3.createCell(1,Cell.CELL_TYPE_STRING).setCellValue(StringUtils.defaultString(orderDetail.getString("order_description"))); - row4.createCell(0,Cell.CELL_TYPE_STRING).setCellValue("Customer ID"); - row4.createCell(1,Cell.CELL_TYPE_STRING).setCellValue(orderDetail.getString("customer_id")); - row5.createCell(0,Cell.CELL_TYPE_STRING).setCellValue("IP"); - row5.createCell(1,Cell.CELL_TYPE_STRING).setCellValue(orderDetail.getString("customer_ip")); - row6.createCell(0,Cell.CELL_TYPE_STRING).setCellValue("Total Amount"); - row6.createCell(1,Cell.CELL_TYPE_STRING).setCellValue(orderDetail.getString("currency")+" "+orderDetail.getString("total_amount")); - row7.createCell(0,Cell.CELL_TYPE_STRING).setCellValue("Input Amount"); - row7.createCell(1,Cell.CELL_TYPE_STRING).setCellValue(orderDetail.getString("currency")+" "+orderDetail.getString("display_amount")); - row8.createCell(0,Cell.CELL_TYPE_STRING).setCellValue("Pay Amount"); - row8.createCell(1,Cell.CELL_TYPE_STRING).setCellValue(orderDetail.getString("currency")+" "+orderDetail.getString("customer_payment_amount")); - row9.createCell(0,Cell.CELL_TYPE_STRING).setCellValue("Exchange Rate"); - row9.createCell(1,Cell.CELL_TYPE_STRING).setCellValue(orderDetail.getString("exchange_rate")); - row10.createCell(0,Cell.CELL_TYPE_STRING).setCellValue("Clearing Amount"); - row10.createCell(1,Cell.CELL_TYPE_STRING).setCellValue("AUD "+ orderDetail.getString("clearing_amount")); - row11.createCell(0,Cell.CELL_TYPE_STRING).setCellValue("Gateway"); - row11.createCell(1,Cell.CELL_TYPE_STRING).setCellValue(getGateWay(orderDetail.getIntValue("gateway"))); - row12.createCell(0,Cell.CELL_TYPE_STRING).setCellValue("Create Time"); - row12.createCell(1,Cell.CELL_TYPE_STRING).setCellValue(orderDetail.getString("create_time")); - row13.createCell(0,Cell.CELL_TYPE_STRING).setCellValue("Status"); - row13.createCell(1,Cell.CELL_TYPE_STRING).setCellValue(getStatus(orderDetail.getIntValue("status"))); - row14.createCell(0,Cell.CELL_TYPE_STRING).setCellValue("Pay Time"); - row14.createCell(1,Cell.CELL_TYPE_STRING).setCellValue(orderDetail.getString("transaction_time")); - row15.createCell(0,Cell.CELL_TYPE_STRING).setCellValue("Order Detail"); - row15.createCell(1,Cell.CELL_TYPE_STRING).setCellValue(StringUtils.defaultString(orderDetail.getString("order_detail"))); + row0.createCell(0, Cell.CELL_TYPE_STRING).setCellValue("Partner"); + row0.createCell(1, Cell.CELL_TYPE_STRING).setCellValue(orderDetail.getJSONObject("client").getString("short_name") + "(" + orderDetail.getJSONObject("client").getString("client_moniker") + ")"); + row1.createCell(0, Cell.CELL_TYPE_STRING).setCellValue("Order ID"); + row1.createCell(1, Cell.CELL_TYPE_STRING).setCellValue(orderDetail.getString("order_id")); + row2.createCell(0, Cell.CELL_TYPE_STRING).setCellValue("Platform Transaction ID"); + row2.createCell(1, Cell.CELL_TYPE_STRING).setCellValue(orderDetail.getString("system_transaction_id")); + row3.createCell(0, Cell.CELL_TYPE_STRING).setCellValue("Order Description"); + row3.createCell(1, Cell.CELL_TYPE_STRING).setCellValue(StringUtils.defaultString(orderDetail.getString("order_description"))); + row4.createCell(0, Cell.CELL_TYPE_STRING).setCellValue("Customer ID"); + row4.createCell(1, Cell.CELL_TYPE_STRING).setCellValue(orderDetail.getString("customer_id")); + row5.createCell(0, Cell.CELL_TYPE_STRING).setCellValue("IP"); + row5.createCell(1, Cell.CELL_TYPE_STRING).setCellValue(orderDetail.getString("customer_ip")); + row6.createCell(0, Cell.CELL_TYPE_STRING).setCellValue("Total Amount"); + row6.createCell(1, Cell.CELL_TYPE_STRING).setCellValue(orderDetail.getString("currency") + " " + orderDetail.getString("total_amount")); + row7.createCell(0, Cell.CELL_TYPE_STRING).setCellValue("Input Amount"); + row7.createCell(1, Cell.CELL_TYPE_STRING).setCellValue(orderDetail.getString("currency") + " " + orderDetail.getString("display_amount")); + row8.createCell(0, Cell.CELL_TYPE_STRING).setCellValue("Pay Amount"); + row8.createCell(1, Cell.CELL_TYPE_STRING).setCellValue(orderDetail.getString("currency") + " " + orderDetail.getString("customer_payment_amount")); + row9.createCell(0, Cell.CELL_TYPE_STRING).setCellValue("Exchange Rate"); + row9.createCell(1, Cell.CELL_TYPE_STRING).setCellValue(orderDetail.getString("exchange_rate")); + row10.createCell(0, Cell.CELL_TYPE_STRING).setCellValue("Clearing Amount"); + row10.createCell(1, Cell.CELL_TYPE_STRING).setCellValue("AUD " + orderDetail.getString("clearing_amount")); + row11.createCell(0, Cell.CELL_TYPE_STRING).setCellValue("Gateway"); + row11.createCell(1, Cell.CELL_TYPE_STRING).setCellValue(getGateWay(orderDetail.getIntValue("gateway"))); + row12.createCell(0, Cell.CELL_TYPE_STRING).setCellValue("Create Time"); + row12.createCell(1, Cell.CELL_TYPE_STRING).setCellValue(orderDetail.getString("create_time")); + row13.createCell(0, Cell.CELL_TYPE_STRING).setCellValue("Status"); + row13.createCell(1, Cell.CELL_TYPE_STRING).setCellValue(getStatus(orderDetail.getIntValue("status"))); + row14.createCell(0, Cell.CELL_TYPE_STRING).setCellValue("Pay Time"); + row14.createCell(1, Cell.CELL_TYPE_STRING).setCellValue(orderDetail.getString("transaction_time")); + row15.createCell(0, Cell.CELL_TYPE_STRING).setCellValue("Order Detail"); + row15.createCell(1, Cell.CELL_TYPE_STRING).setCellValue(StringUtils.defaultString(orderDetail.getString("order_detail"))); } ByteArrayOutputStream bos = new ByteArrayOutputStream(); wb.write(bos); bos.flush(); return bos.toByteArray(); } - private String getGateWay(int gateWay){ + + private String getGateWay(int gateWay) { switch (gateWay) { case 0: return "线下扫码"; @@ -1067,7 +1099,7 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo return ""; } - private String getStatus(int status){ + private String getStatus(int status) { switch (status) { case 0: return "Creating"; @@ -1091,6 +1123,7 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo /** * 检查是否有需要的调单事件待处理,由前端弹框提醒 + * * @param manager * @param notices */ @@ -1138,6 +1171,7 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo /** * 某个商户是否存在调单事件 + * * @param clientId * @return */ @@ -1145,7 +1179,7 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo public JSONObject getNoticeInfo(int clientId) { JSONObject client = clientMapper.findClient(clientId); - if(client == null){ + if (client == null) { throw new InvalidShortIdException(); } JSONObject params = new JSONObject(); @@ -1168,6 +1202,7 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo * 调单事件需要上传的材料说明 * 微信调单与内部调单是固定的 * 支付宝调单由风控人员录入 + * * @param riskId * @return */ @@ -1230,6 +1265,9 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo case 3: materialsRemark = getStudyTemplate(); break; + case 4: + materialsRemark = getChargebackTemplate(); + break; default: break; } @@ -1257,18 +1295,18 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo @Override public void banRiskEvent(String riskId) { JSONObject params = new JSONObject(); - params.put("risk_id",riskId); - params.put("is_valid",0); + params.put("risk_id", riskId); + params.put("is_valid", 0); riskEventMapper.update(params); } @Override public void commitWaitRiskStatus(String riskId, String codeKey, JSONObject manager) { JSONObject event = riskEventMapper.findById(riskId); - if (event.getIntValue("result_type") != RiskResultTypeEnum.SEND_EMAIL_TO_BD.getResultType() &&event.getIntValue("result_type") != RiskResultTypeEnum.MATERIAL_NOT_PASS.getResultType()) { + if (event.getIntValue("result_type") != RiskResultTypeEnum.SEND_EMAIL_TO_BD.getResultType() && event.getIntValue("result_type") != RiskResultTypeEnum.MATERIAL_NOT_PASS.getResultType()) { throw new BadRequestException("风控调单不存在或资料已补全"); } - if(event.getIntValue("result_type")!= RiskResultTypeEnum.MATERIAL_AUDIT_PASS.getResultType()){ + if (event.getIntValue("result_type") != RiskResultTypeEnum.MATERIAL_AUDIT_PASS.getResultType()) { event.put("result_type", RiskResultTypeEnum.WAIT_FOR_AUDIT.getResultType()); riskEventMapper.update(event); } @@ -1315,29 +1353,50 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo @Override public void completeOrderAmount() { List riskEventList = getRiskEvents(null); - for(JSONObject riskEvent:riskEventList){ - if(riskEvent.getIntValue("order_type") == 4 || StringUtils.isBlank(riskEvent.getString("real_order_ids"))){ + for (JSONObject riskEvent : riskEventList) { + if (riskEvent.getIntValue("order_type") == 4 || StringUtils.isBlank(riskEvent.getString("real_order_ids"))) { continue; } - String[] order_ids=riskEvent.getString("real_order_ids").split(","); - for(String orderId : order_ids){ + String[] orderIds = riskEvent.getString("real_order_ids").split(","); + for (String orderId : orderIds) { JSONObject transaction = transactionMapper.findByOrderId(orderId); JSONObject riskOrder = new JSONObject(); - riskOrder.put("risk_id",riskEvent.getString("risk_id")); - riskOrder.put("order_id",transaction.getString("order_id")); - riskOrder.put("clearing_amount",transaction.getString("clearing_amount")); + riskOrder.put("risk_id", riskEvent.getString("risk_id")); + riskOrder.put("order_id", transaction.getString("order_id")); + riskOrder.put("clearing_amount", transaction.getString("clearing_amount")); riskOrdersMapper.save(riskOrder); } } } @Override - public void UpdateRiskEventRemark(String riskId, String remark) { + public void updateRiskEventRemark(String riskId, String remark) { JSONObject event = riskEventMapper.findById(riskId); event.put("remark", remark); riskEventMapper.update(event); } + @Override + public void markChargebackStatus(String riskId, JSONObject manager, ChargebackStatus status) { + RiskEvent risk = getRiskEvent(riskId); + if (risk == null) { + throw new NotFoundException("Risk Event not found"); + } + if (risk.getOrderType() != RISK_ORDER_TYPE_CHARGEBACK) { + throw new BadRequestException("Not Chargeback event"); + } + if (status == null || status == ChargebackStatus.PROCESSING) { + throw new BadRequestException("Invalid status"); + } + riskProcessLogService.addRiskProcessLog(riskId, + manager.getString("manager_id"), + manager.getString("display_name"), + RiskResultTypeEnum.ALREADY_HANDLED.getRemark(), + RiskResultTypeEnum.ALREADY_HANDLED.getResultType(), + RiskResultTypeEnum.ALREADY_HANDLED.getResultType()); + cardSecureService.changeChargebackReportStatus(riskId, status); + } + private List getShopTemplate() { return Arrays.asList("1.与调查交易金额对应的购物小票/发票存根照片 要求:照片应清晰,必须显示商户名称、商户地址、购物时间、物品名称、购物金额等;\n" + "Photos of shopping receipts/ invoice stubs Requirement corresponding with the investigated orders Requirement: The photos should be clear and must show Merchant name, Business address, Transaction time, Product information, Quantity purchased, etc;", @@ -1375,4 +1434,19 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo "Other materials including onboarding materials as task description required;Explanation of business model for your merchant including actual selling-goods or services, per customer transaction, business hours, etc;" ); } + + private List getChargebackTemplate() { + return Arrays.asList("1.A legible copy of the signed authority and/or receipts from the Cardholder. \n" + + "持卡人签名授权书和/或收据的清晰副本。", + "2.Copy of any tax invoice/s and/or signed disclosure of cancellation policy. \n" + + "任何税务发票和/或签署的披露取消政策的副本。", + "3.Any transaction Information relating to the sale of any digital goods purchased online.\n" + + "与在线购买的任何数字商品的销售有关的任何交易信息。", + "4.Description of the goods/services supplied and/or proof of delivery/collection of goods.\n" + + "提供的商品/服务的说明和/或交付/收货的证明。(消费者买了什么,商家卖了什么,消费者收没收到货,如何收到的)", + "5.Copy of Mail Order/telephone order transaction receipt.\n" + + "邮件订单/电话订单交易收据的副本。", + "6.Cardholder identification or any form of communication with the Cardholder, including email correspondence. \n" + + "持卡人身份或与持卡人的任何形式的通信,包括电子邮件通信。"); + } } diff --git a/src/main/java/au/com/royalpay/payment/manage/riskbusiness/web/RiskBusinessController.java b/src/main/java/au/com/royalpay/payment/manage/riskbusiness/web/RiskBusinessController.java index 224e3bfda..9791a534b 100644 --- a/src/main/java/au/com/royalpay/payment/manage/riskbusiness/web/RiskBusinessController.java +++ b/src/main/java/au/com/royalpay/payment/manage/riskbusiness/web/RiskBusinessController.java @@ -1,5 +1,8 @@ package au.com.royalpay.payment.manage.riskbusiness.web; +import au.com.royalpay.payment.core.CardSecureService; +import au.com.royalpay.payment.core.beans.ChargebackStatus; +import au.com.royalpay.payment.core.beans.ChargebackTransaction; import au.com.royalpay.payment.manage.merchants.beans.PartnerQuery; import au.com.royalpay.payment.manage.merchants.core.ClientManager; import au.com.royalpay.payment.manage.permission.manager.ManagerMapping; @@ -14,13 +17,14 @@ import com.alibaba.fastjson.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; +import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import java.io.IOException; - import java.util.List; /** * 风控业务 + * * @Author lvjian * @Date 2018/10/10 1:12 */ @@ -33,7 +37,8 @@ public class RiskBusinessController { @Autowired private RiskMaterialService riskMaterialService; - + @Resource + private CardSecureService cardSecureService; @Autowired private ClientManager clientManager; @@ -41,7 +46,6 @@ public class RiskBusinessController { private RiskProcessLogService riskProcessLogService; - @GetMapping(value = "events") public JSONObject getRiskEvents(RiskEventQuery riskEventQuery, @ModelAttribute(CommonConsts.MANAGER_STATUS) JSONObject manager) { JSONObject params = riskEventQuery.toJSON(); @@ -72,13 +76,18 @@ public class RiskBusinessController { @GetMapping(value = "events/{risk_id}/detail") public JSONObject getRiskEventDetailWithoutTradelogs(@PathVariable("risk_id") String riskId, - @ModelAttribute(CommonConsts.MANAGER_STATUS) JSONObject manager) { - JSONObject riskEvent = riskBusinessService.getRiskEventDetail(riskId); - return riskEvent; + @ModelAttribute(CommonConsts.MANAGER_STATUS) JSONObject manager) { + return riskBusinessService.getRiskEventDetail(riskId); + } + + + @GetMapping("/chargeback_orders") + public List searchForChargeback(@RequestParam("channel_order_id") String channelOrderId) { + return cardSecureService.queryOrdersForChargeBack(channelOrderId); } @PostMapping(value = "events") - public void RegisterRiskEvent(@RequestBody JSONObject params, + public void registerRiskEvent(@RequestBody JSONObject params, @ModelAttribute(CommonConsts.MANAGER_STATUS) JSONObject manager) { params.put("fillin_id", manager.getString("manager_id")); params.put("fillin_person", manager.getString("display_name")); @@ -86,8 +95,8 @@ public class RiskBusinessController { } @PutMapping(value = "events") - public void UpdateRiskEvent(@RequestBody JSONObject params,@ModelAttribute(CommonConsts.MANAGER_STATUS) JSONObject manager) { - riskBusinessService.updateRiskEvent(params,manager); + public void updateRiskEvent(@RequestBody JSONObject params, @ModelAttribute(CommonConsts.MANAGER_STATUS) JSONObject manager) { + riskBusinessService.updateRiskEvent(params, manager); } @PutMapping(value = "events/pass") @@ -97,17 +106,17 @@ public class RiskBusinessController { } @GetMapping(value = "/{risk_id}/download/materialsAsZIP") - public void downloadComplianceZip(@PathVariable("risk_id") String riskId, HttpServletResponse response) throws Exception { + public void downloadComplianceZip(@PathVariable("risk_id") String riskId, HttpServletResponse response) { riskBusinessService.downloadAuditMaterialZiP(riskId, response); } - @RequestMapping(value = "/{risk_id}/upload_mail",method = RequestMethod.PUT) + @RequestMapping(value = "/{risk_id}/upload_mail", method = RequestMethod.PUT) public void uploadEmail(@PathVariable String risk_id) throws IOException { riskBusinessService.sendUploadEmail(risk_id); } - @RequestMapping(value = "/{risk_id}/refuse",method = RequestMethod.PUT) + @RequestMapping(value = "/{risk_id}/refuse", method = RequestMethod.PUT) public void refuseEmail(@PathVariable("risk_id") String riskId, @RequestBody JSONObject otherParams) throws IOException { riskBusinessService.sendRefuseEmail(riskId, otherParams.getString("refuse_description")); } @@ -137,7 +146,7 @@ public class RiskBusinessController { //1:禁用渠道;2:禁用商户 riskBusinessService.sendWxMess(params, channel, 1); } - riskBusinessService.updateRiskEvent(params,manager); + riskBusinessService.updateRiskEvent(params, manager); } @PutMapping(value = "/partner/{isValid}") @@ -152,26 +161,31 @@ public class RiskBusinessController { if (temporaryCloseMerchant != 1) { params.put("result_type", RiskResultTypeEnum.ALREADY_HANDLED.getResultType()); } - } - else { + } else { clientManager.revertClient(clientMoniker, manager); params.put("result_type", RiskResultTypeEnum.ALREADY_HANDLED.getResultType()); } - riskBusinessService.updateRiskEvent(params,manager); + riskBusinessService.updateRiskEvent(params, manager); } - @RequestMapping(value = "/{risk_id}/urge",method = RequestMethod.PUT) + @PutMapping("/{risk_id}/urge") public void urgeEmail(@PathVariable String risk_id) throws IOException { riskBusinessService.sendUrgeEmail(risk_id); } - @RequestMapping(value = "/ban/{risk_id}",method = RequestMethod.DELETE) - public void banEvent(@PathVariable String risk_id){ + @PutMapping("/chargebacks/{riskId}/mark_status") + public void markChargebackStatus(@PathVariable String riskId, @ModelAttribute(CommonConsts.MANAGER_STATUS) JSONObject manager, + @RequestBody JSONObject status) { + riskBusinessService.markChargebackStatus(riskId, manager, status.getObject("status", ChargebackStatus.class)); + } + + @DeleteMapping("/ban/{risk_id}") + public void banEvent(@PathVariable String risk_id) { riskBusinessService.banRiskEvent(risk_id); } @PutMapping("/commitWaitRiskStatus/{risk_id}/{codeKey}") - public void commitWaitRiskStatus(@PathVariable String risk_id,@PathVariable String codeKey,@ModelAttribute(CommonConsts.MANAGER_STATUS) JSONObject manager) { + public void commitWaitRiskStatus(@PathVariable String risk_id, @PathVariable String codeKey, @ModelAttribute(CommonConsts.MANAGER_STATUS) JSONObject manager) { riskBusinessService.commitWaitRiskStatus(risk_id, codeKey, manager); } @@ -205,8 +219,8 @@ public class RiskBusinessController { @PutMapping(value = "{risk_id}/remark") @ResponseBody - public void UpdateRiskEventRemark(@PathVariable("risk_id") String riskId, @RequestBody JSONObject remark) { - riskBusinessService.UpdateRiskEventRemark(riskId, remark.getString("remark")); + public void updateRiskEventRemark(@PathVariable("risk_id") String riskId, @RequestBody JSONObject remark) { + riskBusinessService.updateRiskEventRemark(riskId, remark.getString("remark")); } } diff --git a/src/main/ui/static/analysis/risk_business.js b/src/main/ui/static/analysis/risk_business.js index c09ed9973..31c4f86d6 100644 --- a/src/main/ui/static/analysis/risk_business.js +++ b/src/main/ui/static/analysis/risk_business.js @@ -5,38 +5,40 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], function (angular, $) { 'use strict'; - var mailTemplate = { + let mailTemplate = { "1": "代购、综合商城类", "2": "飞机票、旅行社类", - "3": "教育类、中介类" + "3": "教育类、中介类", + "4": "Chargeback" }; - var orderTypesMap = { + let orderTypesMap = { "1": "微信调单", "2": "支付宝调单", "3": "RoyalPay调单", "4": "警告", - "5": "通用号调单" + "5": "通用号调单", + "6": "卡支付ChargeBack" }; - var orderTypesMapForBD = { + let orderTypesMapForBD = { "1": "微信调单", "2": "支付宝调单", "3": "RoyalPay调单" }; - var royalpayOrderTypesMap = { + let royalpayOrderTypesMap = { "0": "正常调单", "1": "单纯大金额频繁刷单" }; - var warningOrderTypesMap = { + let warningOrderTypesMap = { "0": "单人多次大金额交易", "1": "退款频繁" }; - var resultTypesMap = { + let resultTypesMap = { "0": "未处理", "1": "已发送邮件", "2": "已提交材料,等待审核", @@ -45,7 +47,7 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], "5": "已处理" }; - var resultTypeSearchMap = { + let resultTypeSearchMap = { "0": "未处理", "1": "资料完善中", "2": "等待风控", @@ -54,7 +56,7 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], "5": "已处理" }; - var resultTypeSearchMapForBD = { + let resultTypeSearchMapForBD = { "1": "材料待上传", "2": "材料已提交", "3": "风控初审完成,渠道方审核中", @@ -62,14 +64,14 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], "5": "已处理" }; - var emailStatusMap = { + let emailStatusMap = { "0": "未发送", "1": "已发送", "2": "打回并已发送", "3": "已发送催促邮件" }; - var amountSectionMap = { + let amountSectionMap = { "0-500": "0-500", "500-1000": "500-1000", "1000-1500": "1000-1500", @@ -81,7 +83,7 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], "4000-4500": "4000-4500" }; - var channelResultArray = [ + let channelResultArray = [ "关闭支付", "恢复支付", "单日10", @@ -95,7 +97,7 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], "其他" ]; - var app = angular.module('riskBusinessApp', ['ui.router']); + let app = angular.module('riskBusinessApp', ['ui.router']); app.config(['$stateProvider', function ($stateProvider) { $stateProvider.state('analysis_monitoring.risk_business', { url: '/risk_business', @@ -137,7 +139,7 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], }]); - app.controller('riskBusinessCtrl', ['$scope', '$state', '$http', '$uibModal', '$filter', 'commonDialog', 'industryMap','chartParser', + app.controller('riskBusinessCtrl', ['$scope', '$state', '$http', '$uibModal', '$filter', 'commonDialog', 'industryMap', 'chartParser', function ($scope, $state, $http, $uibModal, $filter, commonDialog, industryMap, chartParser) { $scope.orderTypes = orderTypesMap; $scope.orderTypesForBD = orderTypesMapForBD; @@ -148,26 +150,26 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], $scope.amountSection = amountSectionMap; $scope.pagination = {}; $scope.params = {}; - var industries = new Array(); + let industries = []; angular.forEach($scope.industries, function (industry) { industries.push(industry.label); }); industries.push('未知行业'); - $scope.$watch('params',function (newVal) { - console.log('params changed--->',newVal) + $scope.$watch('params', function (newVal) { + console.log('params changed--->', newVal) }); console.log('riskBusinessCtrl loaded') // console.log(industries); // 加载风险注册事件 $scope.loadRiskEvents = function (page) { - var params = angular.copy($scope.params); + let params = angular.copy($scope.params); params.page = page || $scope.pagination.page || 1; params.replyEmailDateBegin = $filter('date')(params.replyEmailDateBegin, 'yyyy-MM-dd'); params.replyEmailDateEnd = $filter('date')(params.replyEmailDateEnd, 'yyyy-MM-dd'); params.receiveEmailDateBegin = $filter('date')(params.receiveEmailDateBegin, 'yyyy-MM-dd'); params.receiveEmailDateEnd = $filter('date')(params.receiveEmailDateEnd, 'yyyy-MM-dd'); if (params.section != null) { - var sectionArray = params.section.split('-'); + let sectionArray = params.section.split('-'); params.startAmount = sectionArray[0]; params.endAmount = sectionArray[1]; } @@ -177,16 +179,16 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], }); $http.get('/risk/business/events/analysis/industry', {params: params}).then(function (resp) { $scope.riskEventsByIndustry = resp.data; - $scope.risk_industry_chart = chartParser.parse(industryAmount(industries),$scope.riskEventsByIndustry); + $scope.risk_industry_chart = chartParser.parse(industryAmount(industries), $scope.riskEventsByIndustry); }); $http.get('/risk/business/events/analysis/amount', {params: params}).then(function (resp) { $scope.riskEventsByAmount = resp.data; - $scope.risk_amount_chart = chartParser.parse(intervalsAmount(),$scope.riskEventsByAmount); + $scope.risk_amount_chart = chartParser.parse(intervalsAmount(), $scope.riskEventsByAmount); }); }; - var industryAmount = function (industries) { + let industryAmount = function (industries) { return { chart: { tooltip: { @@ -205,15 +207,15 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], name: '风控事件单量', type: 'pie', radius: '80%', center: ['50%', '58%'], - label:{ //饼图图形上的文本标签 - normal:{ - show:true, - position:'outer', //标签的位置 - textStyle : { - fontWeight : 300 , - fontSize : 16 //文字的字体大小 + label: { //饼图图形上的文本标签 + normal: { + show: true, + position: 'outer', //标签的位置 + textStyle: { + fontWeight: 300, + fontSize: 16 //文字的字体大小 }, - formatter:'{d}%' + formatter: '{d}%' } }, itemStyle: { @@ -229,7 +231,7 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], } }; - var intervalsAmount = function () { + let intervalsAmount = function () { return { chart: { tooltip: { @@ -240,8 +242,8 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], legend: { orient: 'vertical', left: 'right', - data: [ '0-500','500-1000','1000-1500','1500-2000','2000-2500','2500-3000','3000-3500','3500-4000','4000-4500','4500-5000','5000-5500','5500-6000', - '6000-6500','6500-7000','7000-7500','7500-8000','8000-8500','8500-9000','>9000'] + data: ['0-500', '500-1000', '1000-1500', '1500-2000', '2000-2500', '2500-3000', '3000-3500', '3500-4000', '4000-4500', '4500-5000', '5000-5500', '5500-6000', + '6000-6500', '6500-7000', '7000-7500', '7500-8000', '8000-8500', '8500-9000', '>9000'] } }, series: [{ @@ -249,15 +251,15 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], name: '风控事件单量', type: 'pie', radius: '80%', center: ['50%', '58%'], - label:{ //饼图图形上的文本标签 - normal:{ - show:true, - position:'outer', //标签的位置 - textStyle : { - fontWeight : 300 , - fontSize : 16 //文字的字体大小 + label: { //饼图图形上的文本标签 + normal: { + show: true, + position: 'outer', //标签的位置 + textStyle: { + fontWeight: 300, + fontSize: 16 //文字的字体大小 }, - formatter:'{d}%' + formatter: '{d}%' } }, itemStyle: { @@ -286,7 +288,7 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], $scope.mailTemplate = mailTemplate; $scope.resultTypes = resultTypesMap; $scope.channelResults = channelResultArray; - if (riskEvent.data.is_send_client == 1) + if (riskEvent.data.is_send_client === 1) riskEvent.data.is_send_client = true; $scope.riskEvent = riskEvent.data; @@ -299,7 +301,7 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], }; $scope.checkTemplate = function () { - var url = ""; + let url = ""; switch ($scope.riskEvent.mail_template) { case "1": url = "https://file.royalpay.com.au/open/2019/03/27/1553658222567_BzfAtsEgsBdMQLl3jGOAlfcYmFUL1F.png"; @@ -323,13 +325,13 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], // 编辑表格的数据保存对象,重新从源数据复制,从而取消保存操作时不会更新视图 $scope.riskEventEdit = angular.copy(riskEvent.data); - //var index = $scope.riskEvent.order_ids.lastIndexOf(","); + //let index = $scope.riskEvent.order_ids.lastIndexOf(","); //$scope.riskEvent.order_ids = $scope.riskEvent.order_ids.substring(0, index) + "," + $scope.riskEvent.order_ids.substring(index + 1); // 获取数据库中对应的渠道字段 - var orderChannel = 'enable_'; - if ($scope.riskEvent.order_type == 1) { + let orderChannel = 'enable_'; + if ($scope.riskEvent.order_type === 1) { orderChannel += 'wechat'; - } else if ($scope.riskEvent.order_type == 2) { + } else if ($scope.riskEvent.order_type === 2) { orderChannel += 'alipay'; } else { orderChannel = null; @@ -342,7 +344,11 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], }).then(function () { $http.delete('/risk/business/ban/' + $scope.riskEvent.risk_id).then(function () { $state.go('analysis_monitoring.risk_business'); - commonDialog.alert({title: 'Delete', content: 'Risk Event Already Disabled', type: 'error'}); + commonDialog.alert({ + title: 'Delete', + content: 'Risk Event Already Disabled', + type: 'error' + }); }, function (resp) { commonDialog.alert({title: 'Error', content: resp.data.message, type: 'error'}); }) @@ -350,10 +356,10 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], }; $scope.commitWaitRiskStatus = function () { - var index = $scope.riskEvent.submit_url.lastIndexOf('='); + let index = $scope.riskEvent.submit_url.lastIndexOf('='); if (index < 0) index = $scope.riskEvent.submit_url.lastIndexOf('/'); - var codeKey = $scope.riskEvent.submit_url.substring(index + 1); + let codeKey = $scope.riskEvent.submit_url.substring(index + 1); commonDialog.confirm({ title: '确认已提交资料', content: '确认已收到商户资料,更新风控状态为等待风控审核?' @@ -368,6 +374,18 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], }) }; + $scope.commitChargebackStatus = function (std) { + commonDialog.confirm({title: '确认操作', content: '当前操作不可逆,并会将风控项标记为办结状态,确认操作?'}).then(function () { + $http.put('/risk/business/chargebacks/' + $scope.riskEvent.risk_id + '/status', {status: std}).then(function () { + $state.reload(); + commonDialog.alert({title: 'Success', content: '修改成功', type: success}) + }, function (res) { + commonDialog.alert({title: 'Error', content: resp.data.message, type: 'error'}) + }) + }) + + } + /** * order_ids在指定位置换行 * @param str @@ -375,15 +393,15 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], * @returns {*} */ $scope.splitStr = function (str, position) { - if (str == null || str == '') + if (str == null || str === '') return; - var strArr = str.split(","); - var resultStr = ''; - for (var i = 0; i < strArr.length; i++) { + let strArr = str.split(","); + let resultStr = ''; + for (let i = 0; i < strArr.length; i++) { resultStr += strArr[i]; - if (i == (strArr.length - 1)) + if (i === (strArr.length - 1)) break; - if ((i + 1) % position == 0) + if ((i + 1) % position === 0) resultStr += ", "; else resultStr += ","; @@ -405,7 +423,7 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], // 控制编辑表格的显示 $scope.editFlag = false; - $scope.changeEditFlag = function(editFlag) { + $scope.changeEditFlag = function (editFlag) { $scope.editFlag = !editFlag; // 如果是在编辑状态,需要将日期转换为date类型(前端控件需要) // 如果是在非编辑状态,需要将日期转换为yyyy-MM-dd格式 @@ -418,7 +436,7 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], } }; - $scope.save = function(form) { + $scope.save = function (form) { if (form.$invalid) { angular.forEach(form, function (item, key) { if (key.indexOf('$') < 0) { @@ -454,10 +472,10 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], * @param temporaryFlag 是否临时关停 */ $scope.updateChannel = function (orderType, channelFlag, temporaryFlag) { - var channel; - if (orderType == "1") + let channel; + if (orderType === "1") channel = 'wechat'; - else if (orderType == "2") + else if (orderType === "2") channel = 'alipay'; $scope.riskEvent.temporary_close_channel = temporaryFlag; commonDialog.confirm({ @@ -477,7 +495,7 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], }; // 关停商户 - $scope.updateClient = function(isValid, temporaryFlag) { + $scope.updateClient = function (isValid, temporaryFlag) { $scope.riskEvent.temporary_close_merchant = temporaryFlag; commonDialog.confirm({ title: 'Warning', @@ -497,8 +515,8 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], title: 'Warning', content: 'Please confirm sending mail.' }).then(function () { - var url = '/risk/business/' + $scope.riskEvent.risk_id + '/upload_mail'; - if ($scope.riskEvent.result_type == 1) + let url = '/risk/business/' + $scope.riskEvent.risk_id + '/upload_mail'; + if ($scope.riskEvent.result_type === 1) url = '/risk/business/' + $scope.riskEvent.risk_id + '/urge'; $http.put(url).then(function () { $state.reload(); @@ -514,14 +532,14 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], }; // 以下为BD上传材料相关 - $scope.material={}; - $scope.uploadFile = function(files, index) { + $scope.material = {}; + $scope.uploadFile = function (files, index) { if (files && files.length) { - var urls = new Array(); - var value = 0; + let urls = []; + let value = 0; $scope.allMaterialInfo.material[index].fileProgressValue = 0; - for (var i = 0; i < files.length; i++) { - var file = files[i]; + for (let i = 0; i < files.length; i++) { + let file = files[i]; Upload.upload({ url: '/attachment/riskFiles', data: {file: file} @@ -531,8 +549,8 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], delete $scope.allMaterialInfo.material[index].fileProgressValue; alert('Upload Failed'); }, function (evt) { - value += parseInt(100 * evt.loaded / evt.total ); - $scope.allMaterialInfo.material[index].fileProgressValue = value/(files.length*2); + value += parseInt(100 * evt.loaded / evt.total); + $scope.allMaterialInfo.material[index].fileProgressValue = value / (files.length * 2); }) } $scope.allMaterialInfo.material[index].uploadFile = urls; @@ -540,11 +558,11 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], }; $scope.submit = function (form) { - var codeKey = $scope.riskEvent.submit_url.substring($scope.riskEvent.submit_url.lastIndexOf('=') + 1) || $scope.riskEvent.submit_url.substring($scope.riskEvent.submit_url.lastIndexOf('/') + 1); - $scope.material.update_time=$filter('date')(new Date(), 'yyyy-MM-dd HH:mm:ss'); + let codeKey = $scope.riskEvent.submit_url.substring($scope.riskEvent.submit_url.lastIndexOf('=') + 1) || $scope.riskEvent.submit_url.substring($scope.riskEvent.submit_url.lastIndexOf('/') + 1); + $scope.material.update_time = $filter('date')(new Date(), 'yyyy-MM-dd HH:mm:ss'); $scope.material.risk_id = $scope.riskEvent.risk_id; - for (var i = 0; i < $scope.allMaterialInfo.material.length; i++) { - var key = 'file' + (i + 1) + "_url"; + for (let i = 0; i < $scope.allMaterialInfo.material.length; i++) { + let key = 'file' + (i + 1) + "_url"; $scope.material[key] = $scope.allMaterialInfo.material[i].uploadFile; } $scope.material.description = $scope.allMaterialInfo.description; @@ -565,12 +583,12 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], }; // 加载提交材料 - $scope.loadRiskMaterial = function() { + $scope.loadRiskMaterial = function () { // $http.get('/risk/business/' + $scope.riskEvent.risk_id + '/material').then(function(resp) { // $scope.material = resp.data; // }); - $http.get('/risk/business/' + $scope.riskEvent.risk_id + '/all_material_info').then(function(resp) { + $http.get('/risk/business/' + $scope.riskEvent.risk_id + '/all_material_info').then(function (resp) { $scope.allMaterialInfo = resp.data; if ($scope.uploadShowFlag) { $scope.allMaterialInfo.description = ''; @@ -579,7 +597,7 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], }; // BD是否可上传 - $scope.uploadShowFlag = ($scope.riskEvent.result_type == 1 || $scope.riskEvent.result_type == 3 || $scope.riskEvent.result_type == 4) ? true : false; + $scope.uploadShowFlag = ($scope.riskEvent.result_type === 1 || $scope.riskEvent.result_type === 3 || $scope.riskEvent.result_type === 4); //if (!$scope.uploadShowFlag) $scope.loadRiskMaterial(); } @@ -595,7 +613,7 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], // 加载提交材料 // $scope.fileObject = {}; - $scope.loadRiskMaterial = function() { + $scope.loadRiskMaterial = function () { // $http.get('/risk/business/' + $scope.riskEvent.risk_id + '/material').then(function(resp) { // $scope.riskMaterial = resp.data; // $scope.file1 = resp.data.file1; @@ -605,31 +623,31 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], // $scope.file5 = resp.data.file5; // $scope.file6 = resp.data.file6; // - // // for (var i = 1; i <= 10; i++) { - // // var key = "file" + i; + // // for (let i = 1; i <= 10; i++) { + // // let key = "file" + i; // // if (riskMaterial[key + '_url'] != null) // // $scope.fileObject[key] = riskMaterial[key + '_url']; // // } // // $scope.fileLength = Object.keys($scope.fileObject).length; // }) - $http.get('/risk/business/' + $scope.riskEvent.risk_id + '/all_material_info').then(function(resp) { + $http.get('/risk/business/' + $scope.riskEvent.risk_id + '/all_material_info').then(function (resp) { $scope.riskMaterial = resp.data; }); }; $scope.loadRiskMaterial(); //审核通过也要可以传图片 - $scope.uploadShowFlag = ($scope.riskEvent.result_type == 1 || $scope.riskEvent.result_type == 3 || $scope.riskEvent.result_type == 4) ? true : false; + $scope.uploadShowFlag = ($scope.riskEvent.result_type === 1 || $scope.riskEvent.result_type === 3 || $scope.riskEvent.result_type === 4); // 材料上传 - $scope.uploadFile = function(files, index) { + $scope.uploadFile = function (files, index) { if (files && files.length) { - var urls = new Array(); - var value = 0; + let urls = []; + let value = 0; $scope.riskMaterial.material[index].fileProgressValue = 0; - for (var i = 0; i < files.length; i++) { - var file = files[i]; + for (let i = 0; i < files.length; i++) { + let file = files[i]; Upload.upload({ url: '/attachment/riskFiles', data: {file: file} @@ -640,8 +658,8 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], delete $scope.riskMaterial.material[index].fileProgressValue; alert('Upload Failed'); }, function (evt) { - value += parseInt(100 * evt.loaded / evt.total ); - $scope.riskMaterial.material[index].fileProgressValue = value/(files.length*2); + value += parseInt(100 * evt.loaded / evt.total); + $scope.riskMaterial.material[index].fileProgressValue = value / (files.length * 2); }) } $scope.riskMaterial.material[index].file = urls; @@ -649,14 +667,14 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], }; $scope.submit = function (form) { - var index = $scope.riskEvent.submit_url.lastIndexOf('='); + let index = $scope.riskEvent.submit_url.lastIndexOf('='); if (index < 0) index = $scope.riskEvent.submit_url.lastIndexOf('/'); - var codeKey = $scope.riskEvent.submit_url.substring(index + 1); - $scope.material.update_time=$filter('date')(new Date(), 'yyyy-MM-dd HH:mm:ss'); + let codeKey = $scope.riskEvent.submit_url.substring(index + 1); + $scope.material.update_time = $filter('date')(new Date(), 'yyyy-MM-dd HH:mm:ss'); $scope.material.risk_id = $scope.riskEvent.risk_id; - for (var i = 0; i < $scope.riskMaterial.material.length; i++) { - var key = 'file' + (i + 1) + "_url"; + for (let i = 0; i < $scope.riskMaterial.material.length; i++) { + let key = 'file' + (i + 1) + "_url"; $scope.material[key] = $scope.riskMaterial.material[i].file; } $scope.material.description = $scope.riskMaterial.description; @@ -677,11 +695,11 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], }; // 材料审核 - $scope.auditMaterial = function(auditType) { + $scope.auditMaterial = function (auditType) { - var url = '/risk/business/events/pass'; - var warningMessageHTML = '是否确定通过该材料?'; - if (auditType == 3) { + let url = '/risk/business/events/pass'; + let warningMessageHTML = '是否确定通过该材料?'; + if (auditType === 3) { commonDialog.confirm({ title: 'Warning', contentHtml: $sce.trustAsHtml(warningMessageHTML) @@ -698,8 +716,7 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], }); }); }); - } - else if (auditType == 4) { + } else if (auditType === 4) { url = '/risk/business/' + $scope.riskEvent.risk_id + '/refuse'; //warningMessageHTML = '是否确定拒绝该材料?' commonDialog.inputText({title: 'Input Refuse Description', size: 'lg'}).then(function (text) { @@ -732,8 +749,21 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], $scope.materials = [{key: 0, value: ""}]; $scope.canMinus = false; $scope.riskEvent = {}; + $scope.searchForChargeback = function () { + $scope.riskEvent.order_ids = null; + let channelOrderId = $scope.riskEvent.channel_order_id; + $http.get('/risk/business/chargeback_orders', {channel_order_id: channelOrderId}).then(function (res) { + $scope.channel_orders = res.data; + }, function (res) { + commonDialog.alert({type: 'error', title: 'Error', content: res.data.message}) + }); + }; + $scope.chooseOrderForChargeback = function (chargebackOrder) { + $scope.riskEvent.order_ids = chargebackOrder.channel_order_id; + $scope.riskEvent.client_moniker = chargebackOrder.client_moniker; + } $scope.checkTemplate = function () { - var url = ""; + let url = ""; switch ($scope.riskEvent.mail_template) { case "1": url = "https://file.royalpay.com.au/open/2019/03/27/1553658222567_BzfAtsEgsBdMQLl3jGOAlfcYmFUL1F.png"; @@ -757,35 +787,35 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], $scope.canMinus = true; }; - $scope.decrease = function($index) { + $scope.decrease = function ($index) { // 如果input大于1,删除 if ($scope.materials.length > 1) { $scope.materials.splice($index, 1); } // 如果回复数为1,不允许删除 - if ($scope.materials.length == 1) { + if ($scope.materials.length === 1) { $scope.canMinus = false; } }; - var array=new Array(); - var validIndex = 0; - $scope.combineMaterials = function() { - for (var i = 0; i < $scope.materials.length; i++) { - var value = $scope.materials[i].value; - if (value == '') + let array = []; + let validIndex = 0; + $scope.combineMaterials = function () { + for (let i = 0; i < $scope.materials.length; i++) { + let value = $scope.materials[i].value; + if (value === '') continue; - var cr = {}; + let cr = {}; cr['question' + (validIndex + 1)] = $scope.materials[i].value; array[validIndex] = cr; validIndex++; } - if (array.length == 0) + if (array.length === 0) return null; return JSON.stringify(array); }; $scope.is_send_client = false; - $scope.changeIsSendClient = function(flag) { + $scope.changeIsSendClient = function (flag) { $scope.is_send_client = flag $scope.send_clean_days = flag }; @@ -803,7 +833,7 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], if ($scope.riskEvent.receive_email_date == null) $scope.riskEvent.receive_email_date = new Date(); // 默认设置邮件回复截止日期为收到邮件的七天后,如果是内部调单,设置三天后 - var replyDeadline = angular.copy($scope.riskEvent.receive_email_date); + let replyDeadline = angular.copy($scope.riskEvent.receive_email_date); if ($scope.riskEvent.order_type > 2) replyDeadline.setDate(replyDeadline.getDate() + 3); else @@ -818,8 +848,8 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], $scope.riskEvent.send_clean_days = $scope.send_clean_days; - var saveRiskBtn = document.getElementById('save-risk-btn'); - var saveRiskBtnInnerHtmlBak = saveRiskBtn.innerHTML; + let saveRiskBtn = document.getElementById('save-risk-btn'); + let saveRiskBtnInnerHtmlBak = saveRiskBtn.innerHTML; saveRiskBtn.disabled = true; saveRiskBtn.innerHTML = " Processing"; $http.post('/risk/business/events', $scope.riskEvent).then(function (resp) { @@ -830,7 +860,7 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], content: 'Register new riskEvent successfully', type: 'success' }); - $state.go('^',{}, {reload: true}); + $state.go('^', {}, {reload: true}); }, function (resp) { saveRiskBtn.innerHTML = saveRiskBtnInnerHtmlBak; saveRiskBtn.disabled = false; @@ -842,16 +872,15 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], }); }; $scope.partnerParam = {}; - $scope.loadParnters = function() { + $scope.loadParnters = function () { $scope.partnerParam.sub_merchant_id = $scope.riskEvent.sub_merchant_id; $http.get('/risk/business/partners', {params: $scope.partnerParam}).then(function (resp) { $scope.partners = resp.data; if ($scope.partners != null && $scope.partners.length > 0) { // 由于通用号调单可能无法提供商户client_moniker,所以后台无法查询到订单 - if ($scope.partners.length == 1 || $scope.riskEvent.order_type != 5) + if ($scope.partners.length === 1 || $scope.riskEvent.order_type !== 5) $scope.riskEvent.client_moniker = $scope.partners[0].client_moniker; - } - else { + } else { commonDialog.confirm({ title: 'Warning', content: '该微信子商户号下暂时没有商户或者商户被禁用,是否继续?' @@ -863,7 +892,7 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], $scope.riskEvent.sub_merchant_id = ''; }); } - if ($scope.partners != null && $scope.partners.length > 1 && $scope.riskEvent.order_type != 5) + if ($scope.partners != null && $scope.partners.length > 1 && $scope.riskEvent.order_type !== 5) commonDialog.confirm({ title: 'Warning', content: '该微信子商户号下有多个商户,是否将调单类型改为通用号调单?' @@ -877,8 +906,8 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], ]); // 调单类型过滤器 - app.filter('orderType', function() { - return function(type) { + app.filter('orderType', function () { + return function (type) { return orderTypesMap[type]; } }); @@ -895,22 +924,22 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'], } }); - app.filter('warningOrderType', function() { - return function(type) { + app.filter('warningOrderType', function () { + return function (type) { return warningOrderTypesMap[type]; } }); // 处理结果过滤器 - app.filter('resultType', function() { - return function(type, resultTypesMap) { + app.filter('resultType', function () { + return function (type, resultTypesMap) { return resultTypesMap[type]; } }); // 邮件发送状态过滤器 - app.filter('emailStatus', function() { - return function(status) { + app.filter('emailStatus', function () { + return function (status) { return emailStatusMap[status]; } }); diff --git a/src/main/ui/static/analysis/templates/dialog_holiday_config.html b/src/main/ui/static/analysis/templates/dialog_holiday_config.html index baae0d44a..991e03d13 100644 --- a/src/main/ui/static/analysis/templates/dialog_holiday_config.html +++ b/src/main/ui/static/analysis/templates/dialog_holiday_config.html @@ -25,6 +25,9 @@
  • AU NSW
  • +
  • AU QLD +
  • diff --git a/src/main/ui/static/analysis/templates/new_riskEvent.html b/src/main/ui/static/analysis/templates/new_riskEvent.html index 629c2ed5c..7ac541a95 100644 --- a/src/main/ui/static/analysis/templates/new_riskEvent.html +++ b/src/main/ui/static/analysis/templates/new_riskEvent.html @@ -1,4 +1,3 @@ -

    New RiskEvent

    @@ -56,7 +55,8 @@ -
    +
    -
    +
    @@ -134,7 +135,7 @@
    @@ -187,11 +188,59 @@
    +
    + +
    +
    + +
    + +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + +
    MerchantOrder IDChannelChannel Order IDAmountTransaction TimeOperation
    + +
    +
    +
    @@ -223,21 +272,23 @@
    - -
    - -
    -
    - - -
    + +
    + +
    +
    + + +
    重新启用渠道 + ng-click="updateChannel(riskEvent.order_type, true, false)"> + 重新启用渠道
    @@ -262,6 +263,18 @@ ng-if="'10000000000'|withRole">重新启用商户
    +
    + +
    +
    + +