add app父商户授权管理、风控业务增加remark

master
luoyang 5 years ago
parent 65fca63a03
commit 4d6ff46b78

@ -10,7 +10,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>manage</artifactId> <artifactId>manage</artifactId>
<version>1.2.14</version> <version>1.2.15</version>
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@ -78,6 +78,11 @@
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId> <artifactId>spring-boot-starter-websocket</artifactId>

@ -24,7 +24,16 @@ public class AppClientBean {
private String suburb; private String suburb;
private String postcode; private String postcode;
private String state; private String state;
@JSONField(name = "legal_person")
private String legalPerson;
@JSONField(name = "legal_phone")
private String legalPhone;
@JSONField(name = "legal_email")
private String legalEmail;
@JSONField(name = "legal_job_title")
private String legalJobTitle;
@JSONField(name = "contact_job")
private String contactJob;
public JSONObject updateObject() { public JSONObject updateObject() {
JSONObject res = new JSONObject(); JSONObject res = new JSONObject();
@ -55,7 +64,26 @@ public class AppClientBean {
if (state != null) { if (state != null) {
res.put("state", state); res.put("state", state);
} }
if (contactJob != null) {
res.put("contact_job", contactJob);
}
return res;
}
public JSONObject legalObject() {
JSONObject res = new JSONObject();
if (legalPerson != null) {
res.put("legal_person", legalPerson);
}
if (legalPhone != null) {
res.put("legal_phone", legalPhone);
}
if (legalEmail != null) {
res.put("legal_email", legalEmail);
}
if (legalJobTitle != null) {
res.put("legal_job_title", legalJobTitle);
}
return res; return res;
} }
@ -137,4 +165,44 @@ public class AppClientBean {
public void setState(String state) { public void setState(String state) {
this.state = state; this.state = state;
} }
public String getContactJob() {
return contactJob;
}
public String getLegalEmail() {
return legalEmail;
}
public String getLegalJobTitle() {
return legalJobTitle;
}
public String getLegalPerson() {
return legalPerson;
}
public String getLegalPhone() {
return legalPhone;
}
public void setContactJob(String contactJob) {
this.contactJob = contactJob;
}
public void setLegalEmail(String legalEmail) {
this.legalEmail = legalEmail;
}
public void setLegalJobTitle(String legalJobTitle) {
this.legalJobTitle = legalJobTitle;
}
public void setLegalPerson(String legalPerson) {
this.legalPerson = legalPerson;
}
public void setLegalPhone(String legalPhone) {
this.legalPhone = legalPhone;
}
} }

@ -0,0 +1,56 @@
package au.com.royalpay.payment.manage.appclient.beans;
import com.alibaba.fastjson.annotation.JSONField;
public class AppPaymentConfigBean {
@JSONField(name="api_surcharge")
private Boolean apiSurcharge;
@JSONField(name="retail_surcharge")
private Boolean retailSurcharge;
@JSONField(name="qrcode_surcharge")
private Boolean qrcodeSurcharge;
@JSONField(name = "require_remark")
private Boolean requireRemark;
@JSONField(name = "require_custinfo")
private Boolean requireCustinfo;
public Boolean getApiSurcharge() {
return apiSurcharge;
}
public void setApiSurcharge(Boolean apiSurcharge) {
this.apiSurcharge = apiSurcharge;
}
public Boolean getRetailSurcharge() {
return retailSurcharge;
}
public void setRetailSurcharge(Boolean retailSurcharge) {
this.retailSurcharge = retailSurcharge;
}
public Boolean getQrcodeSurcharge() {
return qrcodeSurcharge;
}
public void setQrcodeSurcharge(Boolean qrcodeSurcharge) {
this.qrcodeSurcharge = qrcodeSurcharge;
}
public Boolean getRequireCustinfo() {
return requireCustinfo;
}
public Boolean getRequireRemark() {
return requireRemark;
}
public void setRequireCustinfo(Boolean requireCustinfo) {
this.requireCustinfo = requireCustinfo;
}
public void setRequireRemark(Boolean requireRemark) {
this.requireRemark = requireRemark;
}
}

@ -1,6 +1,7 @@
package au.com.royalpay.payment.manage.appclient.core; package au.com.royalpay.payment.manage.appclient.core;
import au.com.royalpay.payment.manage.appclient.beans.AppClientBean; import au.com.royalpay.payment.manage.appclient.beans.AppClientBean;
import au.com.royalpay.payment.manage.appclient.beans.AppPaymentConfigBean;
import au.com.royalpay.payment.manage.appclient.beans.AppQueryBean; import au.com.royalpay.payment.manage.appclient.beans.AppQueryBean;
import au.com.royalpay.payment.manage.merchants.beans.ClientAuthFilesInfo; import au.com.royalpay.payment.manage.merchants.beans.ClientAuthFilesInfo;
import au.com.royalpay.payment.manage.merchants.beans.ClientUpdateInfo; import au.com.royalpay.payment.manage.merchants.beans.ClientUpdateInfo;
@ -76,6 +77,8 @@ public interface RetailAppService {
JSONObject getClientCurrentRateNew(JSONObject device); JSONObject getClientCurrentRateNew(JSONObject device);
JSONObject getClientCurrentRateNewByMoniker(JSONObject device, String clientMoniker);
JSONObject listDailyTransactions(String dateStr, String timezone, boolean thisDevOnly, JSONObject device,String app_client_ids); JSONObject listDailyTransactions(String dateStr, String timezone, boolean thisDevOnly, JSONObject device,String app_client_ids);
JSONObject getActivities(JSONObject device, String activity_page, int page, int limit); JSONObject getActivities(JSONObject device, String activity_page, int page, int limit);
@ -90,10 +93,16 @@ public interface RetailAppService {
void updateClient(JSONObject device, AppClientBean appClientBean); void updateClient(JSONObject device, AppClientBean appClientBean);
void updateClientByMoniker(JSONObject device, String clientMoniker, AppClientBean appClientBean);
JSONObject getClientInfo(JSONObject device); JSONObject getClientInfo(JSONObject device);
JSONObject getClientInfoByMoniker(JSONObject device, String clientMoniker);
JSONObject getClientInfoRealtime(JSONObject device); JSONObject getClientInfoRealtime(JSONObject device);
JSONObject getQRCodePaySurChargeByMoniker(JSONObject device, String clientMoniker);
JSONObject getClientInfoMe(JSONObject device); JSONObject getClientInfoMe(JSONObject device);
JSONObject updateRetailConfig(JSONObject device, boolean paySurcharge); JSONObject updateRetailConfig(JSONObject device, boolean paySurcharge);
@ -136,7 +145,9 @@ public interface RetailAppService {
JSONObject getQrcode(JSONObject device, QRCodeConfig config,int client_id); JSONObject getQrcode(JSONObject device, QRCodeConfig config,int client_id);
void changeSurchargeEnable(JSONObject device, UpdateSurchargeDTO updateSurchargeDTO); void changeSurchargeEnable(JSONObject device, AppPaymentConfigBean appPaymentConfigBean);
void changePaymentConfigByMoniker(JSONObject device, String clientMoniker, AppPaymentConfigBean appPaymentConfigBean);
JSONObject getInvoiceData(JSONObject device, AppQueryBean appQueryBean) throws Exception; JSONObject getInvoiceData(JSONObject device, AppQueryBean appQueryBean) throws Exception;
@ -146,6 +157,8 @@ public interface RetailAppService {
JSONObject getAuthFiles(JSONObject device); JSONObject getAuthFiles(JSONObject device);
JSONObject getAuthFilesByMoniker(JSONObject device, String clientMoniker);
void uploadAuthFiles(JSONObject device,ClientAuthFilesInfo clientAuthFilesInfo); void uploadAuthFiles(JSONObject device,ClientAuthFilesInfo clientAuthFilesInfo);
void updateMerchantInfo(JSONObject device, ClientUpdateInfo clientUpdateInfo); void updateMerchantInfo(JSONObject device, ClientUpdateInfo clientUpdateInfo);
@ -209,4 +222,6 @@ public interface RetailAppService {
void deleteGreenChannelAuthFiles(JSONObject device, String filesInfo); void deleteGreenChannelAuthFiles(JSONObject device, String filesInfo);
void commitAuthFilesToCompliance(JSONObject device,JSONObject photoInfo); void commitAuthFilesToCompliance(JSONObject device,JSONObject photoInfo);
boolean isSubPartner(JSONObject device, String clientMoniker);
} }

@ -6,6 +6,7 @@ import au.com.royalpay.payment.manage.activities.app_index.core.AppActService;
import au.com.royalpay.payment.manage.analysis.mappers.CustomerAndOrdersStatisticsMapper; import au.com.royalpay.payment.manage.analysis.mappers.CustomerAndOrdersStatisticsMapper;
import au.com.royalpay.payment.manage.analysis.mappers.TransactionAnalysisMapper; import au.com.royalpay.payment.manage.analysis.mappers.TransactionAnalysisMapper;
import au.com.royalpay.payment.manage.appclient.beans.AppClientBean; import au.com.royalpay.payment.manage.appclient.beans.AppClientBean;
import au.com.royalpay.payment.manage.appclient.beans.AppPaymentConfigBean;
import au.com.royalpay.payment.manage.appclient.beans.AppQueryBean; import au.com.royalpay.payment.manage.appclient.beans.AppQueryBean;
import au.com.royalpay.payment.manage.appclient.core.RetailAppService; import au.com.royalpay.payment.manage.appclient.core.RetailAppService;
import au.com.royalpay.payment.manage.cashback.core.CashbackService; import au.com.royalpay.payment.manage.cashback.core.CashbackService;
@ -93,6 +94,7 @@ import org.thymeleaf.spring5.SpringTemplateEngine;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.*; import java.io.*;
import java.lang.reflect.Array;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.text.DateFormat; import java.text.DateFormat;
@ -110,6 +112,7 @@ import java.util.TimeZone;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.servlet.ServletOutputStream; import javax.servlet.ServletOutputStream;
@ -352,7 +355,7 @@ public class RetailAppServiceImp implements RetailAppService {
} }
@Override @Override
public void changeSurchargeEnable(JSONObject device, UpdateSurchargeDTO updateSurchargeDTO) { public void changeSurchargeEnable(JSONObject device, AppPaymentConfigBean appPaymentConfigBean) {
JSONObject client = clientManager.getClientInfo(device.getIntValue("client_id")); JSONObject client = clientManager.getClientInfo(device.getIntValue("client_id"));
JSONObject account = clientAccountMapper.findById(device.getString("account_id")); JSONObject account = clientAccountMapper.findById(device.getString("account_id"));
if (PartnerRole.getRole(account.getIntValue("role")) != PartnerRole.ADMIN) { if (PartnerRole.getRole(account.getIntValue("role")) != PartnerRole.ADMIN) {
@ -361,17 +364,65 @@ public class RetailAppServiceImp implements RetailAppService {
if (client == null) { if (client == null) {
throw new NotFoundException("Client not found, please check"); throw new NotFoundException("Client not found, please check");
} }
if (updateSurchargeDTO.getApiSurcharge() != null) { if (appPaymentConfigBean.getApiSurcharge() != null) {
clientModifySupport.processClientConfigModify( clientModifySupport.processClientConfigModify(
new SwitchPermissionModify(account, client.getString("client_moniker"), "api_surcharge", updateSurchargeDTO.getApiSurcharge())); new SwitchPermissionModify(account, client.getString("client_moniker"), "api_surcharge", appPaymentConfigBean.getApiSurcharge()));
} }
if (updateSurchargeDTO.getQrcodeSurcharge() != null) { if (appPaymentConfigBean.getQrcodeSurcharge() != null) {
clientModifySupport.processClientConfigModify( clientModifySupport.processClientConfigModify(
new SwitchPermissionModify(account, client.getString("client_moniker"), "qrcode_surcharge", updateSurchargeDTO.getQrcodeSurcharge())); new SwitchPermissionModify(account, client.getString("client_moniker"), "qrcode_surcharge", appPaymentConfigBean.getQrcodeSurcharge()));
} }
if (updateSurchargeDTO.getRetailSurcharge() != null) { if (appPaymentConfigBean.getRetailSurcharge() != null) {
clientModifySupport.processClientConfigModify( clientModifySupport.processClientConfigModify(
new SwitchPermissionModify(account, client.getString("client_moniker"), "retail_surcharge", updateSurchargeDTO.getRetailSurcharge())); new SwitchPermissionModify(account, client.getString("client_moniker"), "retail_surcharge", appPaymentConfigBean.getRetailSurcharge()));
}
if (appPaymentConfigBean.getRequireRemark() != null) {
clientModifySupport.processClientConfigModify(
new SwitchPermissionModify(account, client.getString("client_moniker"), "require_remark", appPaymentConfigBean.getRequireRemark()));
}
if (appPaymentConfigBean.getRequireCustinfo() != null) {
clientModifySupport.processClientConfigModify(
new SwitchPermissionModify(account, client.getString("client_moniker"), "require_custinfo", appPaymentConfigBean.getRequireCustinfo()));
}
}
@Override
public void changePaymentConfigByMoniker(JSONObject device, String clientMoniker, AppPaymentConfigBean appPaymentConfigBean) {
JSONObject client = clientManager.getClientInfo(device.getIntValue("client_id"));
JSONObject account = clientAccountMapper.findById(device.getString("account_id"));
if (PartnerRole.getRole(account.getIntValue("role")) != PartnerRole.ADMIN) {
throw new ForbiddenException("You have no permission");
}
if (client == null) {
throw new NotFoundException("Client not found, please check");
}
boolean isSubPartner = isSubPartner(device, clientMoniker);
if (!isSubPartner) {
throw new ForbiddenException("You have no permission");
}
if (appPaymentConfigBean.getApiSurcharge() != null) {
clientModifySupport.processClientConfigModify(
new SwitchPermissionModify(account, clientMoniker, "api_surcharge", appPaymentConfigBean.getApiSurcharge()));
}
if (appPaymentConfigBean.getQrcodeSurcharge() != null) {
clientModifySupport.processClientConfigModify(
new SwitchPermissionModify(account, clientMoniker, "qrcode_surcharge", appPaymentConfigBean.getQrcodeSurcharge()));
}
if (appPaymentConfigBean.getRetailSurcharge() != null) {
clientModifySupport.processClientConfigModify(
new SwitchPermissionModify(account, clientMoniker, "retail_surcharge", appPaymentConfigBean.getRetailSurcharge()));
}
if (appPaymentConfigBean.getRequireRemark() != null) {
clientModifySupport.processClientConfigModify(
new SwitchPermissionModify(account, clientMoniker, "require_remark", appPaymentConfigBean.getRequireRemark()));
}
if (appPaymentConfigBean.getRequireCustinfo() != null) {
clientModifySupport.processClientConfigModify(
new SwitchPermissionModify(account, clientMoniker, "require_custinfo", appPaymentConfigBean.getRequireCustinfo()));
} }
} }
@ -429,6 +480,18 @@ public class RetailAppServiceImp implements RetailAppService {
return clientManager.getAuthFiles(account, client.getString("client_moniker")); return clientManager.getAuthFiles(account, client.getString("client_moniker"));
} }
@Override
public JSONObject getAuthFilesByMoniker(JSONObject device, String clientMoniker) {
if (!isSubPartner(device, clientMoniker)) {
throw new ForbiddenException("You have no permission");
}
JSONObject client = clientManager.getClientInfoByMoniker(clientMoniker);
if (client == null) {
throw new NotFoundException("Client not found, please check");
}
return clientManager.getAllAuthFiles(null, clientMoniker);
}
@Override @Override
public void uploadAuthFiles(JSONObject device, ClientAuthFilesInfo clientAuthFilesInfo) { public void uploadAuthFiles(JSONObject device, ClientAuthFilesInfo clientAuthFilesInfo) {
JSONObject client = clientManager.getClientInfo(device.getIntValue("client_id")); JSONObject client = clientManager.getClientInfo(device.getIntValue("client_id"));
@ -593,7 +656,24 @@ public class RetailAppServiceImp implements RetailAppService {
throw new ForbiddenException("You have no permission"); throw new ForbiddenException("You have no permission");
} }
clientManager.updateAppClient(account, device.getIntValue("client_id"), appClientBean); clientManager.updateAppClient(account, device.getIntValue("client_id"), appClientBean);
}
@Override
public void updateClientByMoniker(JSONObject device, String clientMoniker, AppClientBean appClientBean) {
String clientType = device.getString("client_type");
deviceSupport.findRegister(clientType);
JSONObject account = clientAccountMapper.findById(device.getString("account_id"));
if (device.getIntValue("client_id") != account.getIntValue("client_id") || PartnerRole.getRole(account.getIntValue("role")) == PartnerRole.CASHIER) {
throw new ForbiddenException("You have no permission");
}
if (!isSubPartner(device, clientMoniker)) {
throw new ForbiddenException("You have no permission");
}
JSONObject client = clientManager.getClientInfoByMoniker(clientMoniker);
if (client == null) {
throw new NotFoundException("Client not found, please check");
}
clientManager.updateAppClient(account, client.getIntValue("client_id"), appClientBean);
} }
@Override @Override
@ -602,30 +682,25 @@ public class RetailAppServiceImp implements RetailAppService {
deviceSupport.findRegister(clientType); deviceSupport.findRegister(clientType);
JSONObject clientWithConfig = clientMapper.findClient(device.getIntValue("client_id")); JSONObject clientWithConfig = clientMapper.findClient(device.getIntValue("client_id"));
clientWithConfig.putAll(clientConfigService.find(device.getIntValue("client_id"))); clientWithConfig.putAll(clientConfigService.find(device.getIntValue("client_id")));
JSONObject res = SignInAccountServiceImpl.clientInfoWithNoSecretInfo(clientWithConfig); JSONObject res = getClientBaseInfo(clientWithConfig);
res.put("is_skip_clearing", res.getBoolean("skip_clearing"));
if (clientType.equals("iphone")) { if (clientType.equals("iphone")) {
res.put("skip_clearing", !res.getBoolean("skip_clearing")); res.put("skip_clearing", !res.getBoolean("skip_clearing"));
} }
if ("4".equals(clientWithConfig.getString("source"))) { return res;
res.put("refuse_remark", clientWithConfig.getString("refuse_remark"));
res.put("base_info_lack", false);
res.put("compliance_info_lack", false);
if (StringUtils.isEmpty(clientWithConfig.getString("business_structure")) || StringUtils.isEmpty(clientWithConfig.getString("logo_url"))
|| StringUtils.isEmpty(clientWithConfig.getString("description"))
|| ("Company".equals(clientWithConfig.getString("business_structure")) && StringUtils.isEmpty(clientWithConfig.getString("acn")))
|| (!"Company".equals(clientWithConfig.getString("business_structure")) && StringUtils.isEmpty(clientWithConfig.getString("abn")))
|| (StringUtils.isEmpty(clientWithConfig.getString("company_website")) && StringUtils.isEmpty(clientWithConfig.getString("company_photo"))
&& StringUtils.isEmpty(clientWithConfig.getString("store_photo")))) {
res.put("base_info_lack", true);
}
JSONObject file = clientManager.getAuthFiles(null, clientWithConfig.getString("client_moniker"));
for (String s : fileName) {
if (file.getString(s) == null) {
res.put("compliance_info_lack", true);
} }
@Override
public JSONObject getClientInfoByMoniker(JSONObject device, String clientMoniker) {
String clientType = device.getString("client_type");
deviceSupport.findRegister(clientType);
if (!isSubPartner(device, clientMoniker)) {
throw new ForbiddenException("You have no permission");
} }
JSONObject client = clientManager.getClientInfoByMoniker(clientMoniker);
client.putAll(clientConfigService.find(client.getIntValue("client_id")));
JSONObject res = getClientBaseInfo(client);
if (clientType.equals("iphone")) {
res.put("skip_clearing", !res.getBoolean("skip_clearing"));
} }
return res; return res;
} }
@ -637,6 +712,22 @@ public class RetailAppServiceImp implements RetailAppService {
return clientWithConfig; return clientWithConfig;
} }
@Override
public JSONObject getQRCodePaySurChargeByMoniker(JSONObject device, String clientMoniker) {
if (!isSubPartner(device, clientMoniker)) {
throw new ForbiddenException("You have no permission");
}
JSONObject client = clientManager.getClientInfoByMoniker(clientMoniker);
client.putAll(clientConfigService.find(client.getIntValue("client_id")));
JSONObject result = new JSONObject();
result.put("qrcode_surcharge", client.getBooleanValue("qrcode_surcharge"));
result.put("retail_surcharge", client.getBooleanValue("retail_surcharge"));
result.put("api_surcharge", client.getBooleanValue("api_surcharge"));
result.put("require_custinfo", client.getBooleanValue("require_custinfo"));
result.put("require_remark", client.getBooleanValue("require_remark"));
return result;
}
@Override @Override
public JSONObject getClientInfoMe(JSONObject device) { public JSONObject getClientInfoMe(JSONObject device) {
JSONObject result = new JSONObject(); JSONObject result = new JSONObject();
@ -1490,50 +1581,21 @@ public class RetailAppServiceImp implements RetailAppService {
String clientType = device.getString("client_type"); String clientType = device.getString("client_type");
deviceSupport.findRegister(clientType); deviceSupport.findRegister(clientType);
int clientId = device.getIntValue("client_id"); int clientId = device.getIntValue("client_id");
Date now = new Date(); return getClientRateByClientId(clientId);
JSONObject res = merchantInfoProvider.clientCurrentRate(clientId, now, "Wechat");
JSONObject client = clientManager.getClientInfo(clientId);
Assert.notNull(client, "Client is null");
JSONObject clientConfig = clientConfigService.find(clientId);
ArrayList<JSONObject> channels = new ArrayList<>();
if (clientConfig.getBigDecimal("customer_surcharge_rate") != null) {
res.put("customer_surcharge_rate", clientConfig.getBigDecimal("customer_surcharge_rate"));
} }
res.put("max_customer_surcharge_rate", PlatformEnvironment.getEnv().getMaxCustomerSurchargeRate());
JSONObject wechat = getChannel(clientId, now, "Wechat"); @Override
if (wechat.containsKey("channel")) { public JSONObject getClientCurrentRateNewByMoniker(JSONObject device, String clientMoniker) {
channels.add(wechat); String clientType = device.getString("client_type");
} deviceSupport.findRegister(clientType);
JSONObject alipay = getChannel(clientId, now, "Alipay"); if (!isSubPartner(device, clientMoniker)) {
if (alipay.containsKey("channel")) { throw new ForbiddenException("You have no permission");
channels.add(alipay);
}
JSONObject bestpay = getChannel(clientId, now, "Bestpay");
if (bestpay.containsKey("channel")) {
channels.add(bestpay);
}
JSONObject jd = getChannel(clientId, now, "jd");
if (jd.containsKey("channel")) {
channels.add(jd);
} }
JSONObject AlipayOnline = getChannel(clientId, now, "AlipayOnline"); JSONObject client = clientManager.getClientInfoByMoniker(clientMoniker);
if (AlipayOnline.containsKey("channel")) { if (client == null) {
channels.add(AlipayOnline); throw new NotFoundException("Client not found, please check");
} }
// JSONObject Hf = getChannel(clientId, now, "hf"); return getClientRateByClientId(client.getIntValue("client_id"));
// if (Hf.containsKey("channel")) {
// channels.add(Hf);
// }
// JSONObject Yeepay = getChannel(clientId, now, "Yeepay");
// if (Yeepay.containsKey("channel")) {
// channels.add(Yeepay);
// }
JSONObject CBBankPay = getChannel(clientId, now, "CB_BankPay");
if (CBBankPay.containsKey("channel")) {
channels.add(CBBankPay);
}
res.put("channels", channels);
return res;
} }
private JSONObject getChannel(int clientId, Date date, String channel) { private JSONObject getChannel(int clientId, Date date, String channel) {
@ -2250,12 +2312,11 @@ public class RetailAppServiceImp implements RetailAppService {
deviceSupport.findRegister(clientType); deviceSupport.findRegister(clientType);
JSONObject client = clientMapper.findClient(device.getIntValue("client_id")); JSONObject client = clientMapper.findClient(device.getIntValue("client_id"));
JSONObject account = clientAccountMapper.findById(device.getString("account_id")); JSONObject account = clientAccountMapper.findById(device.getString("account_id"));
JSONObject authFileStatus = signInAccountService.checkAuthFileStatus(client);
if (PartnerRole.getRole(account.getIntValue("role")) == PartnerRole.CASHIER) { if (PartnerRole.getRole(account.getIntValue("role")) == PartnerRole.CASHIER) {
JSONObject cashierResult = new JSONObject(); authFileStatus.put("client_less_file", false);
cashierResult.put("client_less_file", false);
return cashierResult;
} }
return signInAccountService.checkAuthFileStatus(client); return authFileStatus;
} }
@Override @Override
@ -2387,6 +2448,19 @@ public class RetailAppServiceImp implements RetailAppService {
clientManager.commitAuthFilesToCompliance(client.getString("client_moniker"), account,"App"); clientManager.commitAuthFilesToCompliance(client.getString("client_moniker"), account,"App");
} }
@Override
public boolean isSubPartner(JSONObject device,String clientMoniker) {
JSONObject client = clientManager.getClientInfoByMoniker(clientMoniker);
if (client == null) {
throw new NotFoundException("Client not found, please check");
}
if (device.getIntValue("client_id") == client.getIntValue("client_id")) {
return true;
}
JSONObject deviceClient = clientManager.getClientInfo(device.getIntValue("client_id"));
JSONArray listSubClients = clientManager.getAllClientIds(device.getIntValue("client_id"));
return (listSubClients.contains(client.getString("client_id")) && deviceClient.getBooleanValue("sub_manage"));
}
private void exportCBBankAggregateFile(JSONObject client, HttpServletResponse httpResponse) { private void exportCBBankAggregateFile(JSONObject client, HttpServletResponse httpResponse) {
httpResponse.setContentType("application/pdf"); httpResponse.setContentType("application/pdf");
@ -2444,4 +2518,84 @@ public class RetailAppServiceImp implements RetailAppService {
List<JSONObject> list = clientBankAccountMapper.clientBankAccounts(client_id); List<JSONObject> list = clientBankAccountMapper.clientBankAccounts(client_id);
return list.isEmpty() ? new JSONObject() : list.get(0); return list.isEmpty() ? new JSONObject() : list.get(0);
} }
private JSONObject getClientBaseInfo(JSONObject clientWithConfig) {
JSONObject res = SignInAccountServiceImpl.clientInfoWithNoSecretInfo(clientWithConfig);
res.put("is_skip_clearing", res.getBoolean("skip_clearing"));
if ("4".equals(clientWithConfig.getString("source"))) {
res.put("refuse_remark", clientWithConfig.getString("refuse_remark"));
res.put("base_info_lack", false);
res.put("compliance_info_lack", false);
if (StringUtils.isEmpty(clientWithConfig.getString("business_structure")) || StringUtils.isEmpty(clientWithConfig.getString("logo_url"))
|| StringUtils.isEmpty(clientWithConfig.getString("description"))
|| ("Company".equals(clientWithConfig.getString("business_structure")) && StringUtils.isEmpty(clientWithConfig.getString("acn")))
|| (!"Company".equals(clientWithConfig.getString("business_structure")) && StringUtils.isEmpty(clientWithConfig.getString("abn")))
|| (StringUtils.isEmpty(clientWithConfig.getString("company_website")) && StringUtils.isEmpty(clientWithConfig.getString("company_photo"))
&& StringUtils.isEmpty(clientWithConfig.getString("store_photo")))) {
res.put("base_info_lack", true);
}
JSONObject file = clientManager.getAuthFiles(null, clientWithConfig.getString("client_moniker"));
for (String s : fileName) {
if (file.getString(s) == null) {
res.put("compliance_info_lack", true);
}
}
}
JSONObject clientLegal = sysClientLegalPersonMapper.findRepresentativeInfo(res.getIntValue("client_id"));
if (clientLegal != null) {
res.put("legal_person", clientLegal.getString("representative_person"));
res.put("legal_job_title", clientLegal.getString("job_title"));
res.put("legal_phone", clientLegal.getString("phone"));
res.put("legal_email", clientLegal.getString("email"));
}
return res;
}
private JSONObject getClientRateByClientId(int clientId) {
Date now = new Date();
JSONObject res = merchantInfoProvider.clientCurrentRate(clientId, now, "Wechat");
JSONObject client = clientManager.getClientInfo(clientId);
Assert.notNull(client, "Client is null");
JSONObject clientConfig = clientConfigService.find(clientId);
ArrayList<JSONObject> channels = new ArrayList<>();
if (clientConfig.getBigDecimal("customer_surcharge_rate") != null) {
res.put("customer_surcharge_rate", clientConfig.getBigDecimal("customer_surcharge_rate"));
}
res.put("max_customer_surcharge_rate", PlatformEnvironment.getEnv().getMaxCustomerSurchargeRate());
JSONObject wechat = getChannel(clientId, now, "Wechat");
if (wechat.containsKey("channel")) {
channels.add(wechat);
}
JSONObject alipay = getChannel(clientId, now, "Alipay");
if (alipay.containsKey("channel")) {
channels.add(alipay);
}
JSONObject bestpay = getChannel(clientId, now, "Bestpay");
if (bestpay.containsKey("channel")) {
channels.add(bestpay);
}
JSONObject jd = getChannel(clientId, now, "jd");
if (jd.containsKey("channel")) {
channels.add(jd);
}
JSONObject AlipayOnline = getChannel(clientId, now, "AlipayOnline");
if (AlipayOnline.containsKey("channel")) {
channels.add(AlipayOnline);
}
// JSONObject Hf = getChannel(clientId, now, "hf");
// if (Hf.containsKey("channel")) {
// channels.add(Hf);
// }
// JSONObject Yeepay = getChannel(clientId, now, "Yeepay");
// if (Yeepay.containsKey("channel")) {
// channels.add(Yeepay);
// }
JSONObject CBBankPay = getChannel(clientId, now, "CB_BankPay");
if (CBBankPay.containsKey("channel")) {
channels.add(CBBankPay);
}
res.put("channels", channels);
return res;
}
} }

@ -4,6 +4,7 @@ import au.com.royalpay.payment.core.exceptions.ParamInvalidException;
import au.com.royalpay.payment.manage.activities.app_index.core.AppActService; import au.com.royalpay.payment.manage.activities.app_index.core.AppActService;
import au.com.royalpay.payment.manage.activities.monsettledelay.core.ActMonDelaySettleService; import au.com.royalpay.payment.manage.activities.monsettledelay.core.ActMonDelaySettleService;
import au.com.royalpay.payment.manage.appclient.beans.AppClientBean; import au.com.royalpay.payment.manage.appclient.beans.AppClientBean;
import au.com.royalpay.payment.manage.appclient.beans.AppPaymentConfigBean;
import au.com.royalpay.payment.manage.appclient.beans.AppQueryBean; import au.com.royalpay.payment.manage.appclient.beans.AppQueryBean;
import au.com.royalpay.payment.manage.appclient.core.RetailAppService; import au.com.royalpay.payment.manage.appclient.core.RetailAppService;
import au.com.royalpay.payment.manage.bill.bean.NewBillBean; import au.com.royalpay.payment.manage.bill.bean.NewBillBean;
@ -223,6 +224,11 @@ public class RetailAppController {
return retailAppService.getClientCurrentRateNew(device); return retailAppService.getClientCurrentRateNew(device);
} }
@GetMapping("/current_rate_new/{clientMoniker}")
public JSONObject getClientCurrentRateNew(@ModelAttribute("RETAIL_DEVICE") JSONObject device, @PathVariable String clientMoniker) {
return retailAppService.getClientCurrentRateNewByMoniker(device, clientMoniker);
}
@PutMapping("/sign_out") @PutMapping("/sign_out")
public void signOut(@ModelAttribute(CommonConsts.RETAIL_DEVICE) JSONObject device) { public void signOut(@ModelAttribute(CommonConsts.RETAIL_DEVICE) JSONObject device) {
retailAppService.sign_out(device); retailAppService.sign_out(device);
@ -264,6 +270,11 @@ public class RetailAppController {
return retailAppService.getClientInfo(device); return retailAppService.getClientInfo(device);
} }
@GetMapping("/client/{clientMoniker}")
public JSONObject getClientInfoByMoniker(@ModelAttribute(CommonConsts.RETAIL_DEVICE) JSONObject device, @PathVariable String clientMoniker) {
return retailAppService.getClientInfoByMoniker(device, clientMoniker);
}
@PutMapping("/client/apply") @PutMapping("/client/apply")
@ResponseBody @ResponseBody
public void updatePartnerInfo(@ModelAttribute(RETAIL_DEVICE) JSONObject device, @RequestBody ClientUpdateInfo info) { public void updatePartnerInfo(@ModelAttribute(RETAIL_DEVICE) JSONObject device, @RequestBody ClientUpdateInfo info) {
@ -316,6 +327,11 @@ public class RetailAppController {
retailAppService.updateClient(device, appClientBean); retailAppService.updateClient(device, appClientBean);
} }
@PutMapping("/client/{clientMoniker}")
public void updateClientByMoniker(@ModelAttribute(CommonConsts.RETAIL_DEVICE) JSONObject device, @PathVariable String clientMoniker, @RequestBody AppClientBean appClientBean) {
retailAppService.updateClientByMoniker(device, clientMoniker, appClientBean);
}
@GetMapping("/client/file") @GetMapping("/client/file")
public JSONObject getAuthFiles(@ModelAttribute(CommonConsts.RETAIL_DEVICE) JSONObject device) { public JSONObject getAuthFiles(@ModelAttribute(CommonConsts.RETAIL_DEVICE) JSONObject device) {
return retailAppService.getAuthFiles(device); return retailAppService.getAuthFiles(device);
@ -326,6 +342,11 @@ public class RetailAppController {
retailAppService.uploadAuthFiles(device, filesInfo); retailAppService.uploadAuthFiles(device, filesInfo);
} }
@PutMapping("/client/file/{clientMoniker}")
public JSONObject getAuthFilesByMoniker(@ModelAttribute(CommonConsts.RETAIL_DEVICE) JSONObject device, @PathVariable String clientMoniker) {
return retailAppService.getAuthFilesByMoniker(device, clientMoniker);
}
@GetMapping("/daily_transactions/date/{dateStr}") @GetMapping("/daily_transactions/date/{dateStr}")
public JSONObject listDailyTransactions(@PathVariable String dateStr, @RequestParam(defaultValue = "Australia/Melbourne") String timezone, @RequestParam(required = false) String app_client_ids, public JSONObject listDailyTransactions(@PathVariable String dateStr, @RequestParam(defaultValue = "Australia/Melbourne") String timezone, @RequestParam(required = false) String app_client_ids,
@RequestParam(defaultValue = "false") boolean thisdevice, @ModelAttribute(CommonConsts.RETAIL_DEVICE) JSONObject device) { @RequestParam(defaultValue = "false") boolean thisdevice, @ModelAttribute(CommonConsts.RETAIL_DEVICE) JSONObject device) {
@ -515,8 +536,14 @@ public class RetailAppController {
@PutMapping("/surcharge") @PutMapping("/surcharge")
@ResponseBody @ResponseBody
public void changeQRCodePaySurCharge(@ModelAttribute(RETAIL_DEVICE) JSONObject device, @RequestBody UpdateSurchargeDTO updateSurchargeDTO) { public void changeQRCodePaySurCharge(@ModelAttribute(RETAIL_DEVICE) JSONObject device, @RequestBody AppPaymentConfigBean appPaymentConfigBean) {
retailAppService.changeSurchargeEnable(device, updateSurchargeDTO); retailAppService.changeSurchargeEnable(device, appPaymentConfigBean);
}
@PutMapping("/surcharge/{clientMoniker}")
@ResponseBody
public void changeQRCodePaySurChargeByMoniker(@ModelAttribute(RETAIL_DEVICE) JSONObject device, @PathVariable String clientMoniker, @RequestBody AppPaymentConfigBean appPaymentConfigBean) {
retailAppService.changePaymentConfigByMoniker(device, clientMoniker,appPaymentConfigBean);
} }
@GetMapping("/invoice") @GetMapping("/invoice")
@ -541,6 +568,11 @@ public class RetailAppController {
return result; return result;
} }
@GetMapping("/surcharge/{clientMoniker}")
public JSONObject changeQRCodePaySurChargeByMoniker(@ModelAttribute(RETAIL_DEVICE) JSONObject device, @PathVariable String clientMoniker) {
return retailAppService.getQRCodePaySurChargeByMoniker(device, clientMoniker);
}
@PostMapping("/attachment/files") @PostMapping("/attachment/files")
public JSONObject uploadImage(@ModelAttribute(RETAIL_DEVICE) JSONObject device, @RequestParam MultipartFile file) throws Exception { public JSONObject uploadImage(@ModelAttribute(RETAIL_DEVICE) JSONObject device, @RequestParam MultipartFile file) throws Exception {
return attachmentClient.uploadFile(file, false); return attachmentClient.uploadFile(file, false);

@ -4127,6 +4127,11 @@ public class ClientManagerImpl implements ClientManager, ManagerTodoNoticeProvid
updateObj.put("client_id", client_id); updateObj.put("client_id", client_id);
clientMapper.update(updateObj); clientMapper.update(updateObj);
} }
JSONObject clientLegal = appClientBean.legalObject();
if (clientLegal.size() > 0) {
clientLegal.put("client_id", client_id);
sysClientLegalPersonMapper.update(clientLegal);
}
if (appClientBean.getCustomerSurchargeRate() != null) { if (appClientBean.getCustomerSurchargeRate() != null) {
if (appClientBean.getCustomerSurchargeRate() <= 0) { if (appClientBean.getCustomerSurchargeRate() <= 0) {
throw new ForbiddenException("customerSurchargeRate is 0"); throw new ForbiddenException("customerSurchargeRate is 0");
@ -4157,9 +4162,9 @@ public class ClientManagerImpl implements ClientManager, ManagerTodoNoticeProvid
client.put("skip_clearing", skip_clearing); client.put("skip_clearing", skip_clearing);
if (client.getString("rpay_enterprise_id") != null) { // if (client.getString("rpay_enterprise_id") != null) {
rpayApi.switchMerchantSettle(client); // rpayApi.switchMerchantSettle(client);
} // }
} }
@Override @Override

@ -71,12 +71,6 @@ public class PartnerApplyController {
return clientApply.listPartnerApply(manager,apply); return clientApply.listPartnerApply(manager,apply);
} }
@RequestMapping(value = "/{client_id}",method = RequestMethod.GET)
@RequireManager(role = {ManagerRole.ADMIN, ManagerRole.BD_USER, ManagerRole.OPERATOR, ManagerRole.SERVANT})
public JSONObject getComplianceCompanyDetail(@PathVariable String client_apply_id) {
return clientApply.getPartnerApplicationDetail(client_apply_id);
}
@RequestMapping(value = "/{client_apply_id}",method = RequestMethod.GET) @RequestMapping(value = "/{client_apply_id}",method = RequestMethod.GET)
@RequireManager(role = {ManagerRole.ADMIN, ManagerRole.BD_USER, ManagerRole.OPERATOR, ManagerRole.SERVANT}) @RequireManager(role = {ManagerRole.ADMIN, ManagerRole.BD_USER, ManagerRole.OPERATOR, ManagerRole.SERVANT})
public JSONObject getApplicationDetail(@PathVariable String client_apply_id) { public JSONObject getApplicationDetail(@PathVariable String client_apply_id) {

@ -142,4 +142,6 @@ public interface RiskBusinessService {
JSONObject updateIsSendClient(String riskId); JSONObject updateIsSendClient(String riskId);
void completeOrderAmount(); void completeOrderAmount();
void UpdateRiskEventRemark(String riskId, String remark);
} }

@ -1331,6 +1331,13 @@ public class RiskBusinessServiceImpl implements RiskBusinessService, ManagerTodo
} }
} }
@Override
public void UpdateRiskEventRemark(String riskId, String remark) {
JSONObject event = riskEventMapper.findById(riskId);
event.put("remark", remark);
riskEventMapper.update(event);
}
private List<String> getShopTemplate() { private List<String> getShopTemplate() {
return Arrays.asList("1.与调查交易金额对应的购物小票/发票存根照片 要求:照片应清晰,必须显示商户名称、商户地址、购物时间、物品名称、购物金额等;\n" + 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;", "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;",

@ -202,5 +202,11 @@ public class RiskBusinessController {
riskBusinessService.completeOrderAmount(); riskBusinessService.completeOrderAmount();
return "SUCCESS"; return "SUCCESS";
} }
@PutMapping(value = "{risk_id}/remark")
@ResponseBody
public void UpdateRiskEventRemark(@PathVariable("risk_id") String riskId, @RequestBody JSONObject remark) {
riskBusinessService.UpdateRiskEventRemark(riskId, remark.getString("remark"));
}
} }

@ -464,7 +464,7 @@ public class SignInAccountServiceImpl implements SignInAccountService, Applicati
"company_phone", "suburb", "postcode", "state", "contact_person", "contact_phone", "contact_email", "short_name", "logo_url", "enable_refund", "company_phone", "suburb", "postcode", "state", "contact_person", "contact_phone", "contact_email", "short_name", "logo_url", "enable_refund",
"enable_refund_auth", "retail_surcharge", "require_custinfo", "require_remark", "logo_thumbnail", "creator", "create_time", "approver", "enable_refund_auth", "retail_surcharge", "require_custinfo", "require_remark", "logo_thumbnail", "creator", "create_time", "approver",
"approve_result", "approve_time", "open_status", "timezone", "has_children", "source", "customer_surcharge_rate", "enable_alipay", "enable_wechat", "approve_result", "approve_time", "open_status", "timezone", "has_children", "source", "customer_surcharge_rate", "enable_alipay", "enable_wechat",
"enable_bestpay", "manual_settle", "skip_clearing", "mail_confirm", "surcharge_mode", "company_photo", "store_photo", "company_website"}; "enable_bestpay", "manual_settle", "skip_clearing", "mail_confirm", "surcharge_mode", "company_photo", "store_photo", "company_website", "contact_job" ,"sub_manage"};
for (String col : columns) { for (String col : columns) {
simpleClient.put(col, client.get(col)); simpleClient.put(col, client.get(col));
} }
@ -644,7 +644,6 @@ public class SignInAccountServiceImpl implements SignInAccountService, Applicati
JSONObject result = new JSONObject(); JSONObject result = new JSONObject();
result.put("client_less_file", false); result.put("client_less_file", false);
result.put("put_fail_pdf", "https://file.royalpay.com.au/open/2019/08/28/1566959635986_P1GuvCkuWINPhUJUqUQnz8E0u6Lgpx.pdf"); result.put("put_fail_pdf", "https://file.royalpay.com.au/open/2019/08/28/1566959635986_P1GuvCkuWINPhUJUqUQnz8E0u6Lgpx.pdf");
if ((client.getIntValue("approve_result") == 2 || client.getIntValue("open_status") == 10 || client.getIntValue("approve_result") == 1 || client.getIntValue("open_status") == 5) && client.getIntValue("source")!=4) {
List<JSONObject> clientFiles = clientFilesMapper.findAllClientFile(client.getIntValue("client_id")); List<JSONObject> clientFiles = clientFilesMapper.findAllClientFile(client.getIntValue("client_id"));
boolean clientFilesIsLess = false; boolean clientFilesIsLess = false;
for (int i = 0; i < FILE_KEYS.length; i++) { for (int i = 0; i < FILE_KEYS.length; i++) {
@ -705,12 +704,13 @@ public class SignInAccountServiceImpl implements SignInAccountService, Applicati
result.put(key,fileJson); result.put(key,fileJson);
} }
} }
result.put("client_less_file", clientFilesIsLess); }
if ((client.getIntValue("approve_result") == 2 || client.getIntValue("open_status") == 10 || client.getIntValue("approve_result") == 1 || client.getIntValue("open_status") == 5) && client.getIntValue("source")!=4 ) {
if (clientFilesIsLess) { if (clientFilesIsLess) {
result.put("client_less_file", clientFilesIsLess);
whenClientLessFile(client, result); whenClientLessFile(client, result);
} }
} }
}
return result; return result;
} }

@ -279,7 +279,9 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'],
app.controller('riskEventDetailCtrl', ['$scope', '$state', '$http', '$uibModal', '$filter', 'Upload', 'commonDialog', 'riskEvent', 'orderService', app.controller('riskEventDetailCtrl', ['$scope', '$state', '$http', '$uibModal', '$filter', 'Upload', 'commonDialog', 'riskEvent', 'orderService',
function ($scope, $state, $http, $uibModal, $filter, Upload, commonDialog, riskEvent, orderService) { function ($scope, $state, $http, $uibModal, $filter, Upload, commonDialog, riskEvent, orderService) {
$scope.ctrl = {
editRemark: false
};
$scope.orderTypes = orderTypesMap; $scope.orderTypes = orderTypesMap;
$scope.mailTemplate = mailTemplate; $scope.mailTemplate = mailTemplate;
$scope.resultTypes = resultTypesMap; $scope.resultTypes = resultTypesMap;
@ -288,6 +290,14 @@ define(['angular', 'jquery', 'uiRouter', './monitoring/analysis-monitoring'],
riskEvent.data.is_send_client = true; riskEvent.data.is_send_client = true;
$scope.riskEvent = riskEvent.data; $scope.riskEvent = riskEvent.data;
$scope.saveRemark = function () {
$http.put('/risk/business/' + $scope.riskEvent.risk_id + '/remark', {remark: $scope.riskEvent.remark}).then(function (resp) {
$scope.ctrl.editRemark = false;
}, function (resp) {
commonDialog.alert({title: 'Error', content: resp.data.message, type: 'error'});
})
};
$scope.checkTemplate = function () { $scope.checkTemplate = function () {
var url = ""; var url = "";
switch ($scope.riskEvent.mail_template) { switch ($scope.riskEvent.mail_template) {

@ -272,6 +272,27 @@
</p> </p>
</div> </div>
</div> </div>
<div class="form-group" ng-if="('10000000000'|withRole)">
<label class="control-label col-sm-2">Remark</label>
<div class="col-sm-10">
<textarea ng-if="!ctrl.editRemark" style="margin-left: 1%;width: 40%;height: 55px"
ng-model="riskEvent.remark"
readonly></textarea>
<textarea ng-if="ctrl.editRemark" style="margin-left: 1%;width: 40%;height: 55px"
ng-model="riskEvent.remark"
maxlength="200"></textarea>
<a role="button" ng-click="ctrl.editRemark=true" style="vertical-align:top"><i
class="fa fa-edit" ng-if="!ctrl.editRemark"></i></a>
<button class="btn btn-success" ng-click="saveRemark()" ng-if="ctrl.editRemark"
style="vertical-align:top">
<i class="fa fa-check"></i>
</button>
<button class="btn btn-danger" ng-click="ctrl.editRemark=false" ng-if="ctrl.editRemark"
style="vertical-align:top">
<i class="fa fa-remove"></i>
</button>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -622,6 +643,28 @@
<!--</select>--> <!--</select>-->
</div> </div>
</div> </div>
<div class="form-group">
<label class="control-label col-sm-2"
for="remark-input">Remark
</label>
<div class="col-sm-8">
<textarea class="form-control"
ng-model="riskEventEdit.remark"
type="text"
name="remark"
id="remark-input"/>
<!--<select class="form-control"-->
<!--name="channel_result"-->
<!--ng-model="riskEventEdit.channel_result"-->
<!--id="channel-result-input"-->
<!--ng-options="item for item in channelResults">-->
<!--<option value="">Please Choose</option>-->
<!--</select>-->
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

Loading…
Cancel
Save