diff --git a/pom.xml b/pom.xml index 43203c321..5c1f37ed2 100644 --- a/pom.xml +++ b/pom.xml @@ -5,12 +5,12 @@ au.com.royalpay.payment payment-parent - 1.0.11 + 1.0.13 4.0.0 manage - 1.1.30 + 1.1.31 UTF-8 diff --git a/src/main/java/au/com/royalpay/payment/manage/appclient/core/RetailAppService.java b/src/main/java/au/com/royalpay/payment/manage/appclient/core/RetailAppService.java index cca43a4c0..310c063d1 100644 --- a/src/main/java/au/com/royalpay/payment/manage/appclient/core/RetailAppService.java +++ b/src/main/java/au/com/royalpay/payment/manage/appclient/core/RetailAppService.java @@ -10,7 +10,9 @@ import au.com.royalpay.payment.tools.merchants.beans.QRCodeConfig; import au.com.royalpay.payment.tools.merchants.beans.UpdateSurchargeDTO; import com.alibaba.fastjson.JSONObject; +import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; import java.math.BigDecimal; import java.util.List; import java.util.Map; @@ -194,5 +196,17 @@ public interface RetailAppService { JSONObject toggleCBBankCustomerSurcharge(JSONObject device, boolean customerSurcharge); + JSONObject getClientAuthFileStatus(JSONObject device); + void getCBBankAggregateFile(JSONObject device, HttpServletResponse httpResponse); + + JSONObject getSourceAggregateFile(JSONObject device); + + JSONObject getClientAggregateFile(JSONObject device, MultipartFile file) throws IOException; + + List uploadGreenChannelAuthFiles(JSONObject device, ClientAuthFilesInfo filesInfo); + + void deleteGreenChannelAuthFiles(JSONObject device, String filesInfo); + + void commitAuthFilesToCompliance(JSONObject device); } diff --git a/src/main/java/au/com/royalpay/payment/manage/appclient/core/impls/RetailAppServiceImp.java b/src/main/java/au/com/royalpay/payment/manage/appclient/core/impls/RetailAppServiceImp.java index 271de92c0..ba640eac3 100644 --- a/src/main/java/au/com/royalpay/payment/manage/appclient/core/impls/RetailAppServiceImp.java +++ b/src/main/java/au/com/royalpay/payment/manage/appclient/core/impls/RetailAppServiceImp.java @@ -42,6 +42,7 @@ import au.com.royalpay.payment.manage.tradelog.beans.TradeLogQuery; import au.com.royalpay.payment.manage.tradelog.core.TradeLogService; import au.com.royalpay.payment.manage.tradelog.refund.RefundService; import au.com.royalpay.payment.tools.cms.RoyalPayCMSSupport; +import au.com.royalpay.payment.tools.connections.attachment.core.AttachmentClient; import au.com.royalpay.payment.tools.device.DeviceSupport; import au.com.royalpay.payment.tools.device.message.AppMessage; import au.com.royalpay.payment.tools.device.message.AppMsgSender; @@ -67,6 +68,7 @@ import com.github.miemiedev.mybatis.paginator.domain.PageBounds; import com.github.miemiedev.mybatis.paginator.domain.PageList; import com.github.qcloudsms.httpclient.HTTPException; +import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomUtils; @@ -82,12 +84,12 @@ import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; +import org.springframework.web.multipart.MultipartFile; import org.thymeleaf.context.Context; import org.thymeleaf.spring4.SpringTemplateEngine; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; +import java.awt.image.BufferedImage; +import java.io.*; import java.math.BigDecimal; import java.math.RoundingMode; import java.text.DateFormat; @@ -201,8 +203,13 @@ public class RetailAppServiceImp implements RetailAppService { @Resource private SpringTemplateEngine thymeleaf; @Resource + private AttachmentClient attachmentClient; + @Resource + private ClientBankAccountMapper clientBankAccountMapper; + @Resource private StringRedisTemplate stringRedisTemplate; private final String CBBANK_AGGREGATE_FILE = "https://file.royalpay.com.au/open/2019/08/05/1564972204689_uwZvpTBjtLUMcN8c540xcZvux1Rd3O.pdf"; + private final String IMG_AGGREGATE_FILE = "https://file.royalpay.com.au/open/2019/08/22/1566440384256_R7Jc3cl5JPZsmVznKffzStwVMELwsl.pdf"; @Resource private SmsSender smsSender; @@ -2219,10 +2226,136 @@ public class RetailAppServiceImp implements RetailAppService { client.put("bank_account_number", bankAccount.getString("account_no")); client.put("bank_account_name", bankAccount.getString("account_name")); } - exportAggregateFile(client, httpResponse); + exportCBBankAggregateFile(client, httpResponse); + } + + @Override + public JSONObject getClientAuthFileStatus(JSONObject device) { + String clientType = device.getString("client_type"); + deviceSupport.findRegister(clientType); + JSONObject client = clientMapper.findClient(device.getIntValue("client_id")); + JSONObject account = clientAccountMapper.findById(device.getString("account_id")); + if (PartnerRole.getRole(account.getIntValue("role")) == PartnerRole.CASHIER) { + JSONObject cashierResult = new JSONObject(); + cashierResult.put("client_less_file", false); + return cashierResult; + } + return signInAccountService.checkAuthFileStatus(client); } - private void exportAggregateFile(JSONObject client, HttpServletResponse httpResponse) { + @Override + public JSONObject getSourceAggregateFile(JSONObject device) { + String clientType = device.getString("client_type"); + deviceSupport.findRegister(clientType); + JSONObject client = clientManager.getClientInfo(device.getIntValue("client_id")); + String address = client.getString("address").trim(); + if (address.contains(",")) { + client.put("address", address.substring(0, address.lastIndexOf(",")).trim()); + client.put("address_sub", address.substring(address.lastIndexOf(",") + 1).trim()); + } + if (client.getString("acn") != null && !client.getString("acn").equals("")) { + client.put("acn_type", "ACN: (" + client.getString("acn") + ")"); + client.put("company_name_acn", client.getString("company_name") + " (ACN " + client.getString("acn") + ")"); + } else { + client.put("acn_type", "ABN: (" + client.getString("abn") + ")"); + client.put("company_name_acn", client.getString("company_name") + " (ABN " + client.getString("abn") + ")"); + } + + JSONObject weChatRate = merchantInfoProvider.clientCurrentRate(client.getIntValue("client_id"), new Date(), "Wechat"); + if (weChatRate == null) { + throw new BadRequestException("The Partner's Rate is not config!"); + } + client.put("wechat_rate", weChatRate.getBigDecimal("rate_value").setScale(2, BigDecimal.ROUND_DOWN)); + client.put("clean", "T+" + weChatRate.getString("clean_days")); + client.put("clean_days", weChatRate.getString("clean_days")); + + try { + JSONObject alipayRate = merchantInfoProvider.clientCurrentRate(client.getIntValue("client_id"), new Date(), "Alipay"); + if (alipayRate != null) { + client.put("alipay_rate", alipayRate.getBigDecimal("rate_value").setScale(2, BigDecimal.ROUND_DOWN)); + } + + JSONObject bestPayRate = merchantInfoProvider.clientCurrentRate(client.getIntValue("client_id"), new Date(), "Bestpay"); + if (bestPayRate != null) { + client.put("bestpay_rate", bestPayRate.getBigDecimal("rate_value").setScale(2, BigDecimal.ROUND_DOWN)); + } + + JSONObject jdRate = merchantInfoProvider.clientCurrentRate(client.getIntValue("client_id"), new Date(), "jd"); + if (jdRate != null) { + client.put("jd_rate", jdRate.getBigDecimal("rate_value").setScale(2, BigDecimal.ROUND_DOWN)); + } + + JSONObject alipayOnlineRate = merchantInfoProvider.clientCurrentRate(client.getIntValue("client_id"), new Date(), "AlipayOnline"); + if (alipayOnlineRate != null) { + client.put("alipay_online_rate", alipayOnlineRate.getBigDecimal("rate_value").setScale(2, BigDecimal.ROUND_DOWN)); + } + + JSONObject cbBankPayRate = merchantInfoProvider.clientCurrentRate(client.getIntValue("client_id"), new Date(), "CB_BankPay"); + if (cbBankPayRate != null) { + client.put("cbbank_rate", cbBankPayRate.getBigDecimal("rate_value").setScale(2, BigDecimal.ROUND_DOWN)); + } + } catch (Exception ignored) { + throw new BadRequestException("Merchant Rate Not Configure"); + } + + JSONObject bankAccount = getBankAccountByClientId(client.getIntValue("client_id")); + if (bankAccount == null || bankAccount.size() <= 0) { + throw new BadRequestException("The Partner's Account is not config!"); + } + client.put("bank", bankAccount.getString("bank")); + client.put("bsb_no", bankAccount.getString("bsb_no")); + client.put("account_no", bankAccount.getString("account_no")); + client.put("account_name", bankAccount.getString("account_name")); + String start_date = DateFormatUtils.format(new Date(), "dd/MM/yyyy"); + client.put("start_date", start_date); + Date endDate = TimeZoneUtils.nextYearByCurrDay(); + String end_date = DateFormatUtils.format(endDate, "dd/MM/yyyy"); + client.put("end_date", end_date); + return exportClientAggregateFile(client); + } + + @Override + public JSONObject getClientAggregateFile(JSONObject device,MultipartFile file) throws IOException{ + String clientType = device.getString("client_type"); + deviceSupport.findRegister(clientType); + + if (StringUtils.isBlank(file.getOriginalFilename())) { + throw new BadRequestException("Please Enter Full Name"); + } + JSONObject account = clientAccountMapper.findById(device.getString("account_id")); + return clientManager.getClientAggregateFile(account, file); + } + + @Override + public List uploadGreenChannelAuthFiles(JSONObject device, ClientAuthFilesInfo clientAuthFilesInfo) { + String clientType = device.getString("client_type"); + deviceSupport.findRegister(clientType); + JSONObject client = clientManager.getClientInfo(device.getIntValue("client_id")); + JSONObject account = clientAccountMapper.findById(device.getString("account_id")); + clientAuthFilesInfo.setAuthStatus(0); + clientAuthFilesInfo.setFile_agreement_info(null); + clientAuthFilesInfo.setFile_apply_info(null); + return clientManager.uploadAuthFilesForWaitCompliance(account, client.getString("client_moniker"), clientAuthFilesInfo); + } + + @Override + public void deleteGreenChannelAuthFiles(JSONObject device, String fileId) { + String clientType = device.getString("client_type"); + deviceSupport.findRegister(clientType); + clientManager.deleteAuthFiles(fileId); + } + + @Override + public void commitAuthFilesToCompliance(JSONObject device) { + String clientType = device.getString("client_type"); + deviceSupport.findRegister(clientType); + JSONObject client = clientManager.getClientInfo(device.getIntValue("client_id")); + JSONObject account = clientAccountMapper.findById(device.getString("account_id")); + clientManager.commitAuthFilesToCompliance(client.getString("client_moniker"), account,"App"); + } + + + private void exportCBBankAggregateFile(JSONObject client, HttpServletResponse httpResponse) { httpResponse.setContentType("application/pdf"); httpResponse.setHeader("content-disposition", "attachment;filename=" + client.getString("client_moniker") + "_AGREEMENT_" + new Date() + ".pdf"); ServletOutputStream sos = null; @@ -2240,6 +2373,24 @@ public class RetailAppServiceImp implements RetailAppService { } } + private JSONObject exportClientAggregateFile(JSONObject client) { + InputStream stream = null; + JSONObject result = new JSONObject(); + try { + PdfUtils pdu = new PdfUtils(); + pdu.setTemplatePdfPath(IMG_AGGREGATE_FILE); + pdu.setPdfTemplate(client); + File file = new File(client.getString("client_moniker") + "_agreement.pdf"); + ByteArrayOutputStream bos = pdu.templetPdfBos(file); + stream = new ByteArrayInputStream(bos.toByteArray()); + JSONObject fileInfo = attachmentClient.uploadFile(stream, client.getString("client_moniker") + "_" + System.currentTimeMillis() + "_agreement_source.pdf", false); + result.put("file_value", fileInfo.getString("url")); + } catch (Exception e) { + throw new BadRequestException("获取合同文件失败"); + } + return result; + } + private void deleteAccountEmailKey(String codeKey) { stringRedisTemplate.delete(getUpdateAccountEmailKey(codeKey)); } @@ -2256,4 +2407,8 @@ public class RetailAppServiceImp implements RetailAppService { return BIND_ACCOUNT_PHONE_PREFIX + codeKey; } + private JSONObject getBankAccountByClientId(int client_id) { + List list = clientBankAccountMapper.clientBankAccounts(client_id); + return list.isEmpty() ? new JSONObject() : list.get(0); + } } diff --git a/src/main/java/au/com/royalpay/payment/manage/appclient/web/RetailAppController.java b/src/main/java/au/com/royalpay/payment/manage/appclient/web/RetailAppController.java index 4f9da3115..ca677b3b1 100644 --- a/src/main/java/au/com/royalpay/payment/manage/appclient/web/RetailAppController.java +++ b/src/main/java/au/com/royalpay/payment/manage/appclient/web/RetailAppController.java @@ -42,6 +42,7 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.ModelAndView; +import java.io.IOException; import java.math.BigDecimal; import java.text.ParseException; import java.util.Arrays; @@ -725,8 +726,62 @@ public class RetailAppController { } @RequestMapping(value = "/cbbank_pay/aggregate_file", method = RequestMethod.GET) - public void getCBBankAggragateFile(@ModelAttribute(CommonConsts.RETAIL_DEVICE) JSONObject device,HttpServletResponse httpResponse) { + public void getCBBankAggregateFile(@ModelAttribute(CommonConsts.RETAIL_DEVICE) JSONObject device,HttpServletResponse httpResponse) { retailAppService.getCBBankAggregateFile(device, httpResponse); } + @RequestMapping(value = "/client/auth_file/status", method = RequestMethod.GET) + public JSONObject getClientAuthFileStatus(@ModelAttribute(CommonConsts.RETAIL_DEVICE) JSONObject device) { + return retailAppService.getClientAuthFileStatus(device); + } + + /** + * 原合同文件 + * @param device + */ + @RequestMapping(value = "/client/aggregate_file", method = RequestMethod.GET) + public JSONObject getSourceAggregateFile(@ModelAttribute(CommonConsts.RETAIL_DEVICE) JSONObject device) { + return retailAppService.getSourceAggregateFile(device); + } + + /** + * 合成后的合同文件 + * @param device + * @param file 签名的url图片 + */ + @RequestMapping(value = "/client/aggregate_file", method = RequestMethod.POST) + public JSONObject getClientAggregateFile(@ModelAttribute(CommonConsts.RETAIL_DEVICE) JSONObject device,@RequestParam MultipartFile file) throws IOException { + return retailAppService.getClientAggregateFile(device, file); + } + + /** + * 提交待审核文件 + * @param device + * @param filesInfo 合规文件url + */ + @RequestMapping(value = "/client/auth_file", method = RequestMethod.PUT) + public List uploadGreenChannelAuthFiles(@ModelAttribute(CommonConsts.RETAIL_DEVICE) JSONObject device,@RequestBody ClientAuthFilesInfo filesInfo){ + return retailAppService.uploadGreenChannelAuthFiles(device, filesInfo); + } + + /** + * 删除待审核文件 + * @param device + * @param fileId 合规文件Id + */ + @RequestMapping(value = "/client/auth_file/{fileId}/delete", method = RequestMethod.PUT) + public void deleteGreenChannelAuthFiles(@ModelAttribute(CommonConsts.RETAIL_DEVICE) JSONObject device,@PathVariable String fileId){ + retailAppService.deleteGreenChannelAuthFiles(device, fileId); + } + + + /** + * 提交审核 + * @param device + */ + @RequestMapping(value = "/client/auth_file/commit_to_compliance", method = RequestMethod.POST) + public void commitToComplianceAuthFiles(@ModelAttribute(CommonConsts.RETAIL_DEVICE) JSONObject device){ + retailAppService.commitAuthFilesToCompliance(device); + } + } diff --git a/src/main/java/au/com/royalpay/payment/manage/complianceAudit/bean/ClientComplianceQuery.java b/src/main/java/au/com/royalpay/payment/manage/complianceAudit/bean/ClientComplianceQuery.java new file mode 100644 index 000000000..602c02ec2 --- /dev/null +++ b/src/main/java/au/com/royalpay/payment/manage/complianceAudit/bean/ClientComplianceQuery.java @@ -0,0 +1,59 @@ +package au.com.royalpay.payment.manage.complianceAudit.bean; + +import com.alibaba.fastjson.JSONObject; +import org.apache.commons.lang3.StringUtils; + + +/** + * Created by yixian on 2016-07-01. + */ +public class ClientComplianceQuery { + + private int limit = 10; + private int page = 1; + private String status; + private String client_moniker; + + public JSONObject toJson(){ + JSONObject jason = new JSONObject(); + if(StringUtils.isNotEmpty(status)){ + jason.put("status",status); + } + if(StringUtils.isNotEmpty(client_moniker)){ + jason.put("client_moniker",client_moniker); + } + return jason; + } + + public int getLimit() { + return limit; + } + + public void setLimit(int limit) { + this.limit = limit; + } + + public int getPage() { + return page; + } + + public void setPage(int page) { + this.page = page; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getClient_moniker() { + return client_moniker; + } + + public void setClient_moniker(String client_moniker) { + this.client_moniker = client_moniker; + } +} diff --git a/src/main/java/au/com/royalpay/payment/manage/complianceAudit/core/ClientComplianceApply.java b/src/main/java/au/com/royalpay/payment/manage/complianceAudit/core/ClientComplianceApply.java new file mode 100644 index 000000000..e11b14a4c --- /dev/null +++ b/src/main/java/au/com/royalpay/payment/manage/complianceAudit/core/ClientComplianceApply.java @@ -0,0 +1,21 @@ +package au.com.royalpay.payment.manage.complianceAudit.core; + + +import au.com.royalpay.payment.manage.complianceAudit.bean.ClientComplianceQuery; +import com.alibaba.fastjson.JSONObject; + + +/** + * Created by yishuqian on 18/10/2016. + */ +public interface ClientComplianceApply { + + JSONObject listClientComplianceApply(JSONObject manager, ClientComplianceQuery apply); + + JSONObject complianceAuthFile(JSONObject client); + + void passComplianceFile(JSONObject manager,int clientId,JSONObject passInfo); + + void refuseComplianceFile(JSONObject manager,int clientId,JSONObject refuseInfo); + +} diff --git a/src/main/java/au/com/royalpay/payment/manage/complianceAudit/core/impl/ClientComplianceApplyImpl.java b/src/main/java/au/com/royalpay/payment/manage/complianceAudit/core/impl/ClientComplianceApplyImpl.java new file mode 100644 index 000000000..8f7431e19 --- /dev/null +++ b/src/main/java/au/com/royalpay/payment/manage/complianceAudit/core/impl/ClientComplianceApplyImpl.java @@ -0,0 +1,109 @@ +package au.com.royalpay.payment.manage.complianceAudit.core.impl; + + +import au.com.royalpay.payment.core.exceptions.InvalidShortIdException; +import au.com.royalpay.payment.manage.complianceAudit.bean.ClientComplianceQuery; +import au.com.royalpay.payment.manage.complianceAudit.core.ClientComplianceApply; +import au.com.royalpay.payment.manage.mappers.system.ClientComplianceCompanyMapper; +import au.com.royalpay.payment.manage.mappers.system.ClientFilesMapper; +import au.com.royalpay.payment.tools.exceptions.BadRequestException; +import au.com.royalpay.payment.tools.permission.enums.ManagerRole; +import au.com.royalpay.payment.tools.utils.PageListUtils; +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.omg.CORBA.SystemException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Created by yishuqian on 18/10/2016. + */ +@Service +public class ClientComplianceApplyImpl implements ClientComplianceApply +{ + @Resource + private ClientComplianceCompanyMapper clientComplianceCompanyMapper; + @Resource + private ClientFilesMapper clientFilesMapper; + + @Override + public JSONObject listClientComplianceApply(JSONObject manager, ClientComplianceQuery applyQuery) { + JSONObject params = applyQuery.toJson(); + if (ManagerRole.BD_USER.hasRole(manager.getIntValue("role"))) { + params.put("bd_user", manager.getString("manager_id")); + } + PageList partners = clientComplianceCompanyMapper.listClientCompliances(params, new PageBounds(applyQuery.getPage(), applyQuery.getLimit(), Order.formString("submit_time.desc"))); + return PageListUtils.buildPageListResult(partners); + } + + @Override + public JSONObject complianceAuthFile(JSONObject client) + { + String[] fileKeys = {"client_bank_file", "client_company_file", "client_id_file", "client_agree_file", "client_apply_file"}; + if (client == null) { + throw new InvalidShortIdException(); + } + List clientFiles = clientFilesMapper.findAllClientFile(client.getIntValue("client_id")); + JSONObject fileJson = new JSONObject(); + if (clientFiles != null && clientFiles.size() > 0) { + for (String fileKey : fileKeys) { + List clientFileUrl = clientFiles.stream() + .filter(json -> (fileKey.equals(json.getString("file_name")))) + .sorted((log1, log2) -> log2.getDate("last_update_date").compareTo(log1.getDate("last_update_date"))) + .map(json -> { + JSONObject params = new JSONObject(); + params.put("file_id", json.getString("file_id")); + params.put("status", json.getString("status")); + params.put("file_value", json.getString("file_value")); + return params; + }) + .collect(Collectors.toList()); + if (clientFileUrl != null && clientFileUrl.size() > 0) { + fileJson.put(fileKey, clientFileUrl); + } + } + } + return fileJson; + + }; + + @Override + public void passComplianceFile(JSONObject manager, int clientId, JSONObject passInfo) { + JSONObject complianceDetail = clientComplianceCompanyMapper.findFileByClientId(clientId); + if (complianceDetail == null) { + throw new BadRequestException("无此记录"); + } + if (complianceDetail.getIntValue("status") == 1) { + throw new BadRequestException("审核已通过,请避免重复操作"); + + } + complianceDetail.put("operator_id", manager.getString("manager_id")); + complianceDetail.put("status",1); + complianceDetail.put("description",' '); + clientComplianceCompanyMapper.update(complianceDetail); + clientFilesMapper.passCompliance(clientId); + } + + @Override + public void refuseComplianceFile(JSONObject manager, int clientId, JSONObject refuseInfo) { + JSONObject complianceDetail = clientComplianceCompanyMapper.findFileByClientId(clientId); + if (complianceDetail == null) { + throw new BadRequestException("无此记录"); + } + if (complianceDetail.getIntValue("status") == 2) { + throw new BadRequestException("已打回,请避免重复操作"); + + } + complianceDetail.put("description",refuseInfo.getString("description")); + complianceDetail.put("operator_id", manager.getString("manager_id")); + complianceDetail.put("status",2); + clientComplianceCompanyMapper.update(complianceDetail); + clientFilesMapper.refuseCompliance(clientId); + } +} diff --git a/src/main/java/au/com/royalpay/payment/manage/complianceAudit/web/ComplianceAuditController.java b/src/main/java/au/com/royalpay/payment/manage/complianceAudit/web/ComplianceAuditController.java new file mode 100644 index 000000000..4d2f1abb0 --- /dev/null +++ b/src/main/java/au/com/royalpay/payment/manage/complianceAudit/web/ComplianceAuditController.java @@ -0,0 +1,53 @@ +package au.com.royalpay.payment.manage.complianceAudit.web; + +import au.com.royalpay.payment.manage.complianceAudit.bean.ClientComplianceQuery; +import au.com.royalpay.payment.manage.complianceAudit.core.ClientComplianceApply; +import au.com.royalpay.payment.manage.mappers.system.ClientMapper; +import au.com.royalpay.payment.manage.merchants.core.ClientManager; +import au.com.royalpay.payment.manage.permission.manager.ManagerMapping; +import au.com.royalpay.payment.manage.permission.manager.RequireManager; +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; + +/** + * Created by yishuqian on 18/10/2016. + */ +@RestController +@RequestMapping("/compliance/audit") +public class ComplianceAuditController +{ + @Resource + private ClientComplianceApply clientComplianceApply; + @Resource + private ClientManager clientManager; + @Resource + private ClientMapper clientMapper; + + @RequestMapping(value = "/listClientCompliances",method = RequestMethod.GET) + @RequireManager(role = {ManagerRole.ADMIN, ManagerRole.BD_USER, ManagerRole.OPERATOR, ManagerRole.SERVANT}) + public JSONObject clientComplianceList(@ModelAttribute(CommonConsts.MANAGER_STATUS) JSONObject manager, ClientComplianceQuery apply) { + return clientComplianceApply.listClientComplianceApply(manager,apply); + } + + @ManagerMapping(value = "/{clientId}/pass/complianceFile", method = RequestMethod.PUT, role = {ManagerRole.OPERATOR, ManagerRole.BD_USER}) + public void passComplianceAudit(@PathVariable int clientId, @RequestBody JSONObject passInfo, @ModelAttribute(CommonConsts.MANAGER_STATUS) JSONObject manager) { + clientComplianceApply.passComplianceFile(manager,clientId,passInfo); + } + + @ManagerMapping(value = "/{clientId}/refuse/complianceFile", method = RequestMethod.PUT, role = {ManagerRole.OPERATOR, ManagerRole.BD_USER}) + public void refuseComplianceAudit(@PathVariable int clientId, @RequestBody JSONObject refuseInfo, @ModelAttribute(CommonConsts.MANAGER_STATUS) JSONObject manager) { + clientComplianceApply.refuseComplianceFile(manager,clientId,refuseInfo); + } + + @RequestMapping(value = "/compliance/clientViewFiles/{clientMoniker}",method = RequestMethod.GET) + @RequireManager(role = {ManagerRole.ADMIN, ManagerRole.BD_USER, ManagerRole.OPERATOR, ManagerRole.SERVANT}) + public JSONObject searchCompliances(@PathVariable String clientMoniker) { + JSONObject client = clientMapper.findClientByMoniker(clientMoniker); + return clientManager.getComplianceFilesForBD(client); + } + +} diff --git a/src/main/java/au/com/royalpay/payment/manage/mappers/system/ClientComplianceCompanyMapper.java b/src/main/java/au/com/royalpay/payment/manage/mappers/system/ClientComplianceCompanyMapper.java new file mode 100644 index 000000000..3a1a84cac --- /dev/null +++ b/src/main/java/au/com/royalpay/payment/manage/mappers/system/ClientComplianceCompanyMapper.java @@ -0,0 +1,30 @@ +package au.com.royalpay.payment.manage.mappers.system; + +import cn.yixblog.support.mybatis.autosql.annotations.AdvanceSelect; +import cn.yixblog.support.mybatis.autosql.annotations.AutoMapper; +import cn.yixblog.support.mybatis.autosql.annotations.AutoSql; +import cn.yixblog.support.mybatis.autosql.annotations.SqlType; +import com.alibaba.fastjson.JSONObject; +import com.github.miemiedev.mybatis.paginator.domain.PageBounds; +import com.github.miemiedev.mybatis.paginator.domain.PageList; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * Created by yishuqian on 06/03/2017. + */ +@AutoMapper(tablename = "client_authfile_compliance", pkName = "compliance_id") +public interface ClientComplianceCompanyMapper { + @AutoSql(type = SqlType.INSERT) + void save(JSONObject partner); + + @AutoSql(type = SqlType.UPDATE) + void update(JSONObject partner); + + @AutoSql(type = SqlType.SELECT) + JSONObject findFileByClientId(@Param("client_id") int client_id); + + PageList listClientCompliances(JSONObject params, PageBounds pageBounds); + +} diff --git a/src/main/java/au/com/royalpay/payment/manage/mappers/system/ClientFilesMapper.java b/src/main/java/au/com/royalpay/payment/manage/mappers/system/ClientFilesMapper.java index bf626bc3b..20008e2d6 100644 --- a/src/main/java/au/com/royalpay/payment/manage/mappers/system/ClientFilesMapper.java +++ b/src/main/java/au/com/royalpay/payment/manage/mappers/system/ClientFilesMapper.java @@ -1,5 +1,6 @@ package au.com.royalpay.payment.manage.mappers.system; +import cn.yixblog.support.mybatis.autosql.annotations.AdvanceSelect; import cn.yixblog.support.mybatis.autosql.annotations.AutoMapper; import cn.yixblog.support.mybatis.autosql.annotations.AutoSql; import cn.yixblog.support.mybatis.autosql.annotations.SqlType; @@ -20,13 +21,35 @@ public interface ClientFilesMapper { void update(JSONObject partner); @AutoSql(type = SqlType.SELECT) + @AdvanceSelect(addonWhereClause = "is_valid = 1 and status = 1") List findClientFile(@Param("client_id") int clientId); + @AutoSql(type = SqlType.SELECT) + @AdvanceSelect(addonWhereClause = "is_valid = 1 and (status = 1 or status = 2) and file_name='client_agree_file'") + List findClientPassAggreeFile(@Param("client_id") int clientId); + + @AutoSql(type = SqlType.SELECT) + @AdvanceSelect(addonWhereClause = "is_valid = 1 and (status = 0 or status = 3) and file_name='client_agree_file'") + List findClientAggreeFileCommit(@Param("client_id") int clientId); + + @AutoSql(type = SqlType.SELECT) + @AdvanceSelect(addonWhereClause = "is_valid = 1") + List findAllClientFile(@Param("client_id") int clientId); + @AutoSql(type = SqlType.SELECT) JSONObject findFileById(@Param("file_id") String file_id); List findFileByClientAndType(@Param("client_id") int client_id, @Param("file_name") String file_name); + void deleteByClientAndFileId(@Param("file_id") String file_id); + + void deleteAggreeByClientId(@Param("client_id") int file_id); + void confirmAgreeFile(@Param("client_id") int client_id); + void updateBeforeCompliance(@Param("client_id") int client_id); + + void refuseCompliance(@Param("client_id") int client_id); + + void passCompliance(@Param("client_id") int client_id); } diff --git a/src/main/java/au/com/royalpay/payment/manage/merchants/core/ClientManager.java b/src/main/java/au/com/royalpay/payment/manage/merchants/core/ClientManager.java index 1046aebca..e7b2f4372 100644 --- a/src/main/java/au/com/royalpay/payment/manage/merchants/core/ClientManager.java +++ b/src/main/java/au/com/royalpay/payment/manage/merchants/core/ClientManager.java @@ -12,6 +12,7 @@ import com.alibaba.fastjson.JSONObject; import org.springframework.transaction.annotation.Transactional; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @@ -235,8 +236,22 @@ public interface ClientManager { JSONObject getAuthFiles(JSONObject manager, String clientMoniker); + JSONObject getClientViewAuthFiles(JSONObject manager, String clientMoniker); + + JSONObject getAllAuthFiles(JSONObject manager, String clientMoniker); + + void deleteAuthFiles(String fileId); + + void deleteAuthFilesByAdmin(String fileId); + void uploadAuthFiles(JSONObject manager, String clientMoniker, ClientAuthFilesInfo filesInfo); + List uploadAuthFilesForWaitCompliance(JSONObject manager, String clientMoniker, ClientAuthFilesInfo filesInfo); + + void commitAuthFilesToCompliance(String clientMoniker, JSONObject account, String source); + + JSONObject getClientAggregateFile(JSONObject account, MultipartFile file) throws IOException; + JSONObject getClientsAnalysis(JSONObject manager); List getUnRegister(JSONObject manager); @@ -450,4 +465,10 @@ public interface ClientManager { List listLevel3Client(int client_id); void updateRefundCreditLine(JSONObject manager, String clientMoniker, JSONObject refundLineInfo); + + JSONObject getComplianceFiles(JSONObject account); + + JSONObject getClientInfoByAggree(JSONObject account); + + JSONObject getComplianceFilesForBD(JSONObject account); } diff --git a/src/main/java/au/com/royalpay/payment/manage/merchants/core/impls/ClientManagerImpl.java b/src/main/java/au/com/royalpay/payment/manage/merchants/core/impls/ClientManagerImpl.java index 86bb56b45..4ff99a7c9 100644 --- a/src/main/java/au/com/royalpay/payment/manage/merchants/core/impls/ClientManagerImpl.java +++ b/src/main/java/au/com/royalpay/payment/manage/merchants/core/impls/ClientManagerImpl.java @@ -22,6 +22,7 @@ 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.AppMerchantBean; import au.com.royalpay.payment.manage.application.core.SimpleClientApplyService; +import au.com.royalpay.payment.manage.complianceAudit.core.ClientComplianceApply; import au.com.royalpay.payment.manage.dev.bean.TestMerchantAccountInfo; import au.com.royalpay.payment.manage.device.core.DeviceManager; import au.com.royalpay.payment.manage.management.sysconfig.core.impls.PermissionPartnerManagerImpl; @@ -104,13 +105,12 @@ import org.springframework.ui.Model; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.multipart.MultipartFile; import org.thymeleaf.context.Context; import org.thymeleaf.spring4.SpringTemplateEngine; import javax.annotation.PostConstruct; import javax.annotation.Resource; -import javax.crypto.*; -import javax.crypto.spec.SecretKeySpec; import javax.imageio.ImageIO; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; @@ -121,7 +121,6 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.net.URISyntaxException; import java.net.URL; -import java.nio.charset.StandardCharsets; import java.security.*; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; @@ -158,6 +157,9 @@ public class ClientManagerImpl implements ClientManager, ManagerTodoNoticeProvid private AttachmentClient attachmentClient; @Resource private StringRedisTemplate stringRedisTemplate; + @Resource + private ClientComplianceApply clientComplianceApply; + @Value("${app.redis.prefix}") private String redisPrefix; private ApplicationEventPublisher publisher; @@ -287,6 +289,8 @@ public class ClientManagerImpl implements ClientManager, ManagerTodoNoticeProvid private Locker locker; @Resource private MongoTemplate mongoTemplate; + @Resource + private ClientComplianceCompanyMapper clientComplianceCompanyMapper; @Resource @@ -303,6 +307,8 @@ public class ClientManagerImpl implements ClientManager, ManagerTodoNoticeProvid private String agreetemplatePdfPath; @Value("${app.agreetemplate.aggregate.path}") private String aggregateAgreetemplatePdfPath; + private final String IMG_AGGREGATE_FILE = "https://file.royalpay.com.au/open/2019/08/22/1566440384256_R7Jc3cl5JPZsmVznKffzStwVMELwsl.pdf"; + @Resource private MpWechatApiProvider mpWechatApiProvider; @@ -1985,6 +1991,102 @@ public class ClientManagerImpl implements ClientManager, ManagerTodoNoticeProvid clientInfoCacheSupport.clearClientCache(clientId); } + @Override + public JSONObject getComplianceFiles(JSONObject account) { + JSONObject client = getClientInfo(account.getIntValue("client_id")); + if (client == null) { + throw new InvalidShortIdException(); + } + JSONObject file = signInAccountService.checkAuthFileStatus(client); + file.put("file_company", clientComplianceCompanyMapper.findFileByClientId(account.getIntValue("client_id"))); + return file; + } + + @Override + public JSONObject getClientInfoByAggree(JSONObject account) { + JSONObject client = getClientInfo(account.getIntValue("client_id")); + if (client == null) { + throw new InvalidShortIdException(); + } + String address = client.getString("address").trim(); + if (address.contains(",")) { + client.put("address", address.substring(0, address.lastIndexOf(",")).trim()); + client.put("address_sub", address.substring(address.lastIndexOf(",") + 1).trim()); + } + if (client.getString("acn") != null && !client.getString("acn").equals("")) { + client.put("acn_type", "ACN: (" + client.getString("acn") + ")"); + client.put("company_name_acn", client.getString("company_name") + " (ACN " + client.getString("acn") + ")"); + } else { + client.put("acn_type", "ABN: (" + client.getString("abn") + ")"); + client.put("company_name_acn", client.getString("company_name") + " (ABN " + client.getString("abn") + ")"); + } + + JSONObject weChatRate = merchantInfoProvider.clientCurrentRate(client.getIntValue("client_id"), new Date(), "Wechat"); + if (weChatRate == null) { + throw new BadRequestException("The Partner's Rate is not config!"); + } + client.put("wechat_rate", weChatRate.getBigDecimal("rate_value").setScale(2, BigDecimal.ROUND_DOWN)); + client.put("clean", "T+" + weChatRate.getString("clean_days")); + client.put("clean_days", weChatRate.getString("clean_days")); + + try { + JSONObject alipayRate = merchantInfoProvider.clientCurrentRate(client.getIntValue("client_id"), new Date(), "Alipay"); + if (alipayRate != null) { + client.put("alipay_rate", alipayRate.getBigDecimal("rate_value").setScale(2, BigDecimal.ROUND_DOWN)); + } + + JSONObject bestPayRate = merchantInfoProvider.clientCurrentRate(client.getIntValue("client_id"), new Date(), "Bestpay"); + if (bestPayRate != null) { + client.put("bestpay_rate", bestPayRate.getBigDecimal("rate_value").setScale(2, BigDecimal.ROUND_DOWN)); + } + + JSONObject jdRate = merchantInfoProvider.clientCurrentRate(client.getIntValue("client_id"), new Date(), "jd"); + if (jdRate != null) { + client.put("jd_rate", jdRate.getBigDecimal("rate_value").setScale(2, BigDecimal.ROUND_DOWN)); + } + + JSONObject alipayOnlineRate = merchantInfoProvider.clientCurrentRate(client.getIntValue("client_id"), new Date(), "AlipayOnline"); + if (alipayOnlineRate != null) { + client.put("alipay_online_rate", alipayOnlineRate.getBigDecimal("rate_value").setScale(2, BigDecimal.ROUND_DOWN)); + } + + JSONObject cbBankPayRate = merchantInfoProvider.clientCurrentRate(client.getIntValue("client_id"), new Date(), "CB_BankPay"); + if (cbBankPayRate != null) { + client.put("cbbank_rate", cbBankPayRate.getBigDecimal("rate_value").setScale(2, BigDecimal.ROUND_DOWN)); + } + } catch (Exception ignored) { + throw new BadRequestException("Merchant Rate Not Configure"); + } + + JSONObject bankAccount = getBankAccountByClientId(client.getIntValue("client_id")); + if (bankAccount == null || bankAccount.size() <= 0) { + throw new BadRequestException("The Partner's Account is not config!"); + } + client.put("bank", bankAccount.getString("bank")); + client.put("bsb_no", bankAccount.getString("bsb_no")); + client.put("account_no", bankAccount.getString("account_no")); + client.put("account_name", bankAccount.getString("account_name")); + String start_date = DateFormatUtils.format(new Date(), "dd/MM/yyyy"); + client.put("start_date", start_date); + Date endDate = TimeZoneUtils.nextYearByCurrDay(); + String end_date = DateFormatUtils.format(endDate, "dd/MM/yyyy"); + client.put("end_date", end_date); + return client; + } + + @Override + public JSONObject getComplianceFilesForBD(JSONObject account) { + JSONObject client = getClientInfo(account.getIntValue("client_id")); + if (client == null) { + throw new InvalidShortIdException(); + } + + JSONObject file = clientComplianceApply.complianceAuthFile(client); + file.put("file_company", clientComplianceCompanyMapper.findFileByClientId(account.getIntValue("client_id"))); + file.put("client", client); + return file; + } + @Override @Transactional public void updateClientBDUsers(JSONObject manager, String clientMoniker, JSONObject data) throws Exception { @@ -3052,6 +3154,88 @@ public class ClientManagerImpl implements ClientManager, ManagerTodoNoticeProvid } @Override + public JSONObject getAllAuthFiles(JSONObject manager, String clientMoniker) { + JSONObject client = getClientInfoByMoniker(clientMoniker); + String[] fileKeys = {"client_bank_file", "client_company_file", "client_id_file", "client_agree_file", "client_apply_file"}; + if (client == null) { + throw new InvalidShortIdException(); + } + List clientFiles = clientFilesMapper.findClientFile(client.getIntValue("client_id")); + JSONObject fileJson = new JSONObject(); + if (clientFiles != null && clientFiles.size() > 0) { + for (String fileKey : fileKeys) { + List clientFileUrl = clientFiles.stream() + .filter(json -> (fileKey.equals(json.getString("file_name")))) + .sorted((log1, log2) -> log2.getDate("last_update_date").compareTo(log1.getDate("last_update_date"))) + .map(json -> { + JSONObject params = new JSONObject(); + params.put("file_id", json.getString("file_id")); + params.put("file_value", json.getString("file_value")); + return params; + }) + .collect(Collectors.toList()); + if (clientFileUrl != null && clientFileUrl.size() > 0) { + fileJson.put(fileKey, clientFileUrl); + } + } + } + return fileJson; + } + + + @Override + public JSONObject getClientViewAuthFiles(JSONObject manager, String clientMoniker) { + JSONObject client = getClientInfoByMoniker(clientMoniker); + String[] fileKeys = {"client_bank_file", "client_company_file", "client_id_file", "client_agree_file", "client_apply_file"}; + if (client == null) { + throw new InvalidShortIdException(); + } + List clientFiles = clientFilesMapper.findAllClientFile(client.getIntValue("client_id")); + JSONObject fileJson = new JSONObject(); + if (clientFiles != null && clientFiles.size() > 0) { + for (String fileKey : fileKeys) { + List clientFileUrl = clientFiles.stream() + .filter(json -> (fileKey.equals(json.getString("file_name")))) + .sorted((log1, log2) -> log2.getDate("last_update_date").compareTo(log1.getDate("last_update_date"))) + .map(json -> { + JSONObject params = new JSONObject(); + params.put("file_id", json.getString("file_id")); + params.put("file_value", json.getString("file_value")); + params.put("status", json.getIntValue("status")); + return params; + }) + .collect(Collectors.toList()); + if (clientFileUrl != null && clientFileUrl.size() > 0) { + fileJson.put(fileKey, clientFileUrl); + } + } + } + return fileJson; + + } + + ; + + @Override + public void deleteAuthFiles(String fileId) { + JSONObject file = clientFilesMapper.findFileById(fileId); + if (file.getIntValue("status") == 1 || file.getIntValue("status") == 2) { + throw new BadRequestException("The file has passed and cannot be deleted"); + } + clientFilesMapper.deleteByClientAndFileId(fileId); + } + + @Override + public void deleteAuthFilesByAdmin(String fileId) { + JSONObject file = clientFilesMapper.findFileById(fileId); + if (file == null) { + throw new BadRequestException("The file has deleted"); + } + clientFilesMapper.deleteByClientAndFileId(fileId); + } + + @Override + @Transactional public void uploadAuthFiles(JSONObject manager, String clientMoniker, ClientAuthFilesInfo filesInfo) { JSONObject client = getClientInfoByMoniker(clientMoniker); if (client == null) { @@ -3074,6 +3258,162 @@ public class ClientManagerImpl implements ClientManager, ManagerTodoNoticeProvid // } } + @Override + @Transactional + public List uploadAuthFilesForWaitCompliance(JSONObject manager, String clientMoniker, ClientAuthFilesInfo filesInfo) { + JSONObject client = getClientInfoByMoniker(clientMoniker); + List fileResult = new ArrayList<>(); + if (client == null) { + throw new InvalidShortIdException(); + } + int clientId = client.getIntValue("client_id"); + try { + updateAggregateFilesForWaitCompliance(manager, clientId, CLIENT_AGREE_FILE, filesInfo.getFile_agreement_info(), fileResult); + updateSysClientFilesForWaitCompliance(manager, clientId, CLIENT_APPLY_FILE, filesInfo.getFile_apply_info(), fileResult); + updateSysClientFilesForWaitCompliance(manager, clientId, CLIENT_BANK_FILE, filesInfo.getFile_bank_info(), fileResult); + updateSysClientFilesForWaitCompliance(manager, clientId, CLIENT_COMPANY_FILE, filesInfo.getFile_company_info(), fileResult); + updateSysClientFilesForWaitCompliance(manager, clientId, CLIENT_ID_FILE, filesInfo.getFile_id_info(), fileResult); + } catch (Exception e) { + logger.error("上传合规文件失败", e); + throw new BadRequestException("合同制作出现问题:" + e.getMessage()); + } + // boolean clientSource = client.getIntValue("source") == 4 ? true : false; + // if (filesInfo.getAuthStatus() == 1 && clientSource) { + // client.put("approve_result", 3); + // clientMapper.update(client); + // } + return fileResult; + } + + @Override + @Transactional + public void commitAuthFilesToCompliance(String clientMoniker, JSONObject account, String source) { + JSONObject client = getClientInfoByMoniker(clientMoniker); + int sourceEnum = 2; + if (client == null) { + throw new InvalidShortIdException(); + } + List clientAllAuthFiles = clientFilesMapper.findAllClientFile(client.getIntValue("client_id")); + if (clientAllAuthFiles == null || clientAllAuthFiles.size() == 0) { + throw new BadRequestException("Please check the information is uploaded completely"); + } + String[] fileKeys = {"client_bank_file", "client_company_file", "client_id_file", "client_agree_file"}; + String[] fileNames = {"bank statement", "Certificate of Registration", "ID", "Agreement"}; + for (int i = 0; i < fileKeys.length; i++) { + String fileKey = fileKeys[i]; + if (clientAllAuthFiles.stream().noneMatch(fileJson -> fileKey.equals(fileJson.getString("file_name")))) { + if ("client_agree_file".equals(fileKey)) { + throw new BadRequestException("Please check that the agreement has been signed"); + }else { + throw new BadRequestException("Please check the " + fileNames[i] + " is uploaded completely"); + } + } + + } + if ("app".equals(source.toLowerCase())) { + sourceEnum = 1; + } + + JSONObject fileComp = clientComplianceCompanyMapper.findFileByClientId(client.getIntValue("client_id")); + if (fileComp == null) { + fileComp = new JSONObject(); + fileComp.put("client_id", client.getIntValue("client_id")); + fileComp.put("submit_time", new Date()); + fileComp.put("status", 0); + fileComp.put("source", sourceEnum); + fileComp.put("commit_by_id", account.getString("account_id")); + clientComplianceCompanyMapper.save(fileComp); + clientFilesMapper.updateBeforeCompliance(client.getIntValue("client_id")); + } else if (fileComp.getIntValue("status") == 2) { + fileComp.put("status", 0); + fileComp.put("submit_time", new Date()); + fileComp.put("source", sourceEnum); + fileComp.put("commit_by_id", account.getString("account_id")); + clientComplianceCompanyMapper.update(fileComp); + clientFilesMapper.updateBeforeCompliance(client.getIntValue("client_id")); + } else { + throw new BadRequestException("please do not repeat submission!"); + } + signInAccountService.clearAccountCache(account.getString("account_id")); + } + + @Override + public JSONObject getClientAggregateFile(JSONObject account, MultipartFile file) throws IOException{ + if (StringUtils.isBlank(file.getOriginalFilename())) { + throw new BadRequestException("Please Enter Full Name"); + } + JSONObject client = getClientInfo(account.getIntValue("client_id")); + String address = client.getString("address").trim(); + if (address.contains(",")) { + client.put("address", address.substring(0, address.lastIndexOf(",")).trim()); + client.put("address_sub", address.substring(address.lastIndexOf(",") + 1).trim()); + } + if (client.getString("acn") != null && !client.getString("acn").equals("")) { + client.put("acn_type", "ACN: (" + client.getString("acn") + ")"); + client.put("company_name_acn", client.getString("company_name") + " (ACN " + client.getString("acn") + ")"); + } else { + client.put("acn_type", "ABN: (" + client.getString("abn") + ")"); + client.put("company_name_acn", client.getString("company_name") + " (ABN " + client.getString("abn") + ")"); + } + + JSONObject weChatRate = merchantInfoProvider.clientCurrentRate(client.getIntValue("client_id"), new Date(), "Wechat"); + if (weChatRate == null) { + throw new BadRequestException("The Partner's Rate is not config!"); + } + client.put("wechat_rate", weChatRate.getBigDecimal("rate_value").setScale(2, BigDecimal.ROUND_DOWN)); + client.put("clean", "T+" + weChatRate.getString("clean_days")); + client.put("clean_days", weChatRate.getString("clean_days")); + + try { + JSONObject alipayRate = merchantInfoProvider.clientCurrentRate(client.getIntValue("client_id"), new Date(), "Alipay"); + if (alipayRate != null) { + client.put("alipay_rate", alipayRate.getBigDecimal("rate_value").setScale(2, BigDecimal.ROUND_DOWN)); + } + + JSONObject bestPayRate = merchantInfoProvider.clientCurrentRate(client.getIntValue("client_id"), new Date(), "Bestpay"); + if (bestPayRate != null) { + client.put("bestpay_rate", bestPayRate.getBigDecimal("rate_value").setScale(2, BigDecimal.ROUND_DOWN)); + } + + JSONObject jdRate = merchantInfoProvider.clientCurrentRate(client.getIntValue("client_id"), new Date(), "jd"); + if (jdRate != null) { + client.put("jd_rate", jdRate.getBigDecimal("rate_value").setScale(2, BigDecimal.ROUND_DOWN)); + } + + JSONObject alipayOnlineRate = merchantInfoProvider.clientCurrentRate(client.getIntValue("client_id"), new Date(), "AlipayOnline"); + if (alipayOnlineRate != null) { + client.put("alipay_online_rate", alipayOnlineRate.getBigDecimal("rate_value").setScale(2, BigDecimal.ROUND_DOWN)); + } + + JSONObject cbBankPayRate = merchantInfoProvider.clientCurrentRate(client.getIntValue("client_id"), new Date(), "CB_BankPay"); + if (cbBankPayRate != null) { + client.put("cbbank_rate", cbBankPayRate.getBigDecimal("rate_value").setScale(2, BigDecimal.ROUND_DOWN)); + } + } catch (Exception ignored) { + throw new BadRequestException("Merchant Rate Not Configure"); + } + + JSONObject bankAccount = getBankAccountByClientId(client.getIntValue("client_id")); + if (bankAccount == null || bankAccount.size() <= 0) { + throw new BadRequestException("The Partner's Account is not config!"); + } + client.put("bank", bankAccount.getString("bank")); + client.put("bsb_no", bankAccount.getString("bsb_no")); + client.put("account_no", bankAccount.getString("account_no")); + client.put("account_name", bankAccount.getString("account_name")); + String start_date = DateFormatUtils.format(new Date(), "dd/MM/yyyy"); + client.put("start_date", start_date); + Date endDate = TimeZoneUtils.nextYearByCurrDay(); + String end_date = DateFormatUtils.format(endDate, "dd/MM/yyyy"); + client.put("end_date", end_date); + client.put("full_name", file.getOriginalFilename()); + BufferedImage img = ImageIO.read(file.getInputStream()); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ImageIO.write(img, "png", out); + client.put("img","data:image/png;base64," + Base64.encodeBase64String(out.toByteArray())); + return exportImgAggregateFile(account, client); + } + @Override public JSONObject getSettlementLog(JSONObject manager, String clientMoniker, TradeLogQuery query) { JSONObject client = getClientInfoByMoniker(clientMoniker); @@ -3113,31 +3453,70 @@ public class ClientManagerImpl implements ClientManager, ManagerTodoNoticeProvid public void updateSysClientFiles(JSONObject manager, int clientId, String fileType, String fileValue) { if (fileValue != null) { - - List existFiles = clientFilesMapper.findFileByClientAndType(clientId, fileType); - JSONObject existFile = null; - if (!CollectionUtils.isEmpty(existFiles)) { - existFile = existFiles.get(0); - } JSONObject fileJson = new JSONObject(); fileJson.put("client_id", clientId); fileJson.put("last_update_date", new Date()); fileJson.put("last_update_by", manager.getString("display_name")); - if (existFile != null) { - fileJson.put("file_id", existFile.getString("file_id")); - fileJson.put("file_name", fileType); - fileJson.put("file_value", fileValue); - clientFilesMapper.update(fileJson); - logger.info(clientId + "的fileType文件更新成功"); - } else { + fileJson.put("file_name", fileType); + fileJson.put("file_value", fileValue); + fileJson.put("status", 1); + fileJson.put("is_valid", 1); + clientFilesMapper.save(fileJson); + logger.info(clientId + "的fileType文件上传成功"); + } + } + + public void updateSysClientFilesForWaitCompliance(JSONObject manager, int clientId, String fileType, String fileValue, List fileResult) { + if (fileValue != null) { + String[] values = fileValue.split(","); + for (String value : values) { + JSONObject fileJson = new JSONObject(); + fileJson.put("client_id", clientId); + fileJson.put("last_update_date", new Date()); + fileJson.put("last_update_by", manager.getString("display_name")); fileJson.put("file_name", fileType); - fileJson.put("file_value", fileValue); + fileJson.put("file_value", value); + fileJson.put("status", 0); + fileJson.put("is_valid", 1); clientFilesMapper.save(fileJson); - logger.info(clientId + "的fileType文件上传成功"); + logger.info(clientId + "的fileType文件上传成功" + fileJson.getString("file_id")); + JSONObject file = new JSONObject(); + file.put("file_id", fileJson.getString("file_id")); + file.put("file_value", fileJson.getString("file_value")); + fileResult.add(file); } } } + public void updateAggregateFilesForWaitCompliance(JSONObject manager, int clientId, String fileType, String fileValue, List fileResult) { + if (fileValue != null) { + List passAggregateFiles = clientFilesMapper.findClientPassAggreeFile(clientId); + if (passAggregateFiles != null && passAggregateFiles.size() > 0) { + throw new BadRequestException("合同已提交或审核通过,请勿重复签订合同"); + } + List aggregateFiles = clientFilesMapper.findClientAggreeFileCommit(clientId); + if (aggregateFiles != null && aggregateFiles.size() > 0) { + clientFilesMapper.deleteAggreeByClientId(clientId); + } + JSONObject fileJson = new JSONObject(); + fileJson.put("client_id", clientId); + fileJson.put("last_update_date", new Date()); + fileJson.put("last_update_by", manager.getString("display_name")); + fileJson.put("file_name", fileType); + fileJson.put("file_value", fileValue); + fileJson.put("status", 0); + fileJson.put("is_valid", 1); + clientFilesMapper.save(fileJson); + logger.info(clientId + "的合同文件上传成功" + fileJson.getString("file_id")); + JSONObject file = new JSONObject(); + file.put("file_id", fileJson.getString("file_id")); + file.put("file_value", fileJson.getString("file_value")); + fileResult.add(file); + + } + } + + @Override public JSONObject getClientsAnalysis(JSONObject manager) { JSONObject params = new JSONObject(); @@ -5156,6 +5535,34 @@ public class ClientManagerImpl implements ClientManager, ManagerTodoNoticeProvid clientMapper.update(updateClient); } + private JSONObject exportImgAggregateFile(JSONObject account, JSONObject client) throws IOException{ + JSONObject result = new JSONObject(); + InputStream stream = null; + try { + PdfUtils pdu = new PdfUtils(); + pdu.setTemplatePdfPath(IMG_AGGREGATE_FILE); + pdu.setPdfTemplate(client); + File file = new File(client.getString("client_moniker") + "_agreement.pdf"); + ByteArrayOutputStream bos = pdu.templetPdfBos(file,"STSong-Light","UniGB-UCS2-H"); + stream = new ByteArrayInputStream(bos.toByteArray()); + JSONObject fileInfo = attachmentClient.uploadFile(stream, client.getString("client_moniker") + "_" + System.currentTimeMillis() + "_agreement.pdf", false); + ClientAuthFilesInfo clientAuthFilesInfo = new ClientAuthFilesInfo(); + clientAuthFilesInfo.setFile_agreement_info(fileInfo.getString("url")); + clientAuthFilesInfo.setFile_apply_info(null); + clientAuthFilesInfo.setFile_bank_info(null); + clientAuthFilesInfo.setFile_company_info(null); + clientAuthFilesInfo.setFile_id_info(null); + clientAuthFilesInfo.setAuthStatus(0); + result = uploadAuthFilesForWaitCompliance(account, client.getString("client_moniker"), clientAuthFilesInfo).get(0); + } catch (Exception e) { + logger.error("合同制作出现问题:", e); + throw new BadRequestException("合同制作出现问题:" + e.getMessage()); + } finally { + stream.close(); + } + return result; + } + private TemplateMessage initClientMessage(JSONObject client, String newExpiryDate, String wechatOpenid, String templateId) { TemplateMessage notice = new TemplateMessage(wechatOpenid, templateId, null); notice.put("first", "您好,您的合同费率已到期,根据合同协议系统已自动为您延期1年。", "#ff0000"); diff --git a/src/main/java/au/com/royalpay/payment/manage/merchants/web/PartnerApplyController.java b/src/main/java/au/com/royalpay/payment/manage/merchants/web/PartnerApplyController.java index cf33bad9b..791c74937 100644 --- a/src/main/java/au/com/royalpay/payment/manage/merchants/web/PartnerApplyController.java +++ b/src/main/java/au/com/royalpay/payment/manage/merchants/web/PartnerApplyController.java @@ -1,5 +1,6 @@ package au.com.royalpay.payment.manage.merchants.web; +import au.com.royalpay.payment.manage.merchants.core.ClientManager; import com.google.code.kaptcha.Producer; import au.com.royalpay.payment.manage.merchants.beans.ClientApplyInfo; @@ -40,6 +41,9 @@ public class PartnerApplyController { private ClientApply clientApply; @Resource private Producer captchaProducer; + @Resource + private ClientManager clientManager; + @RequestMapping(method = RequestMethod.POST) public void applyPartner(@CookieValue(CommonConsts.CODE_KEY) String codeKey, @RequestBody @Valid ClientApplyInfo apply, @@ -67,6 +71,12 @@ public class PartnerApplyController { 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) @RequireManager(role = {ManagerRole.ADMIN, ManagerRole.BD_USER, ManagerRole.OPERATOR, ManagerRole.SERVANT}) public JSONObject getApplicationDetail(@PathVariable String client_apply_id) { diff --git a/src/main/java/au/com/royalpay/payment/manage/merchants/web/PartnerManageController.java b/src/main/java/au/com/royalpay/payment/manage/merchants/web/PartnerManageController.java index 31103b707..360bef468 100644 --- a/src/main/java/au/com/royalpay/payment/manage/merchants/web/PartnerManageController.java +++ b/src/main/java/au/com/royalpay/payment/manage/merchants/web/PartnerManageController.java @@ -531,7 +531,12 @@ public class PartnerManageController { @ManagerMapping(value = "/{clientMoniker}/file", method = RequestMethod.GET, role = {ManagerRole.ADMIN, ManagerRole.OPERATOR, ManagerRole.BD_USER, ManagerRole.SERVANT}) public JSONObject getAuthFiles(@PathVariable String clientMoniker, @ModelAttribute(CommonConsts.MANAGER_STATUS) JSONObject manager) { - return clientManager.getAuthFiles(manager, clientMoniker); + return clientManager.getAllAuthFiles(manager, clientMoniker); + } + + @ManagerMapping(value = "/auth_file/{fileId}/delete", method = RequestMethod.PUT, role = {ManagerRole.OPERATOR, ManagerRole.BD_USER}) + public void deleteAuthFiles(@PathVariable String fileId, @ModelAttribute(CommonConsts.MANAGER_STATUS) JSONObject manager) { + clientManager.deleteAuthFilesByAdmin(fileId); } @ManagerMapping(value = "/{clientMoniker}/file", method = RequestMethod.PUT, role = {ManagerRole.ADMIN, ManagerRole.OPERATOR, ManagerRole.BD_USER}) diff --git a/src/main/java/au/com/royalpay/payment/manage/merchants/web/PartnerViewController.java b/src/main/java/au/com/royalpay/payment/manage/merchants/web/PartnerViewController.java index b43d8d5d1..cb7c3f9a3 100644 --- a/src/main/java/au/com/royalpay/payment/manage/merchants/web/PartnerViewController.java +++ b/src/main/java/au/com/royalpay/payment/manage/merchants/web/PartnerViewController.java @@ -2,14 +2,17 @@ package au.com.royalpay.payment.manage.merchants.web; import au.com.royalpay.payment.core.exceptions.ParamInvalidException; import au.com.royalpay.payment.manage.application.core.SimpleClientApplyService; +import au.com.royalpay.payment.manage.mappers.system.ClientComplianceCompanyMapper; import au.com.royalpay.payment.manage.merchants.beans.ClientAuthFilesInfo; import au.com.royalpay.payment.manage.merchants.beans.ClientRegisterInfo; import au.com.royalpay.payment.manage.merchants.beans.NewAccountBean; import au.com.royalpay.payment.manage.merchants.core.ClientManager; import au.com.royalpay.payment.manage.merchants.core.ClientSignEventSupport; +import au.com.royalpay.payment.manage.permission.manager.ManagerMapping; import au.com.royalpay.payment.manage.permission.manager.PartnerMapping; import au.com.royalpay.payment.manage.permission.manager.RequirePartner; import au.com.royalpay.payment.manage.pos.datasource.ReadOnlyConnection; +import au.com.royalpay.payment.manage.signin.core.SignInAccountService; import au.com.royalpay.payment.manage.support.wechatclients.KangaLandWechatApiImpl; import au.com.royalpay.payment.manage.support.wechatclients.RedpackWechatApiImpl; import au.com.royalpay.payment.manage.system.core.ClientContractService; @@ -21,20 +24,25 @@ import au.com.royalpay.payment.tools.exceptions.BadRequestException; import au.com.royalpay.payment.tools.exceptions.ForbiddenException; import au.com.royalpay.payment.tools.http.HttpUtils; import au.com.royalpay.payment.tools.merchants.beans.QRCodeConfig; +import au.com.royalpay.payment.tools.permission.enums.ManagerRole; import au.com.royalpay.payment.tools.permission.enums.PartnerRole; import au.com.royalpay.payment.tools.permission.wechat.WechatMapping; import com.alibaba.fastjson.JSONObject; +import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Controller; +import org.springframework.transaction.annotation.Transactional; import org.springframework.ui.Model; import org.springframework.validation.Errors; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; import java.io.IOException; import java.io.OutputStream; +import java.util.Date; import java.util.List; /** @@ -52,6 +60,10 @@ public class PartnerViewController { private ClientSignEventSupport clientSignEventSupport; @Resource private ClientContractService clientContractService; + @Resource + private ClientComplianceCompanyMapper clientComplianceCompanyMapper; + @Resource + private SignInAccountService signInAccountService; @Resource private SimpleClientApplyService simpleClientApplyService; @@ -496,6 +508,30 @@ public class PartnerViewController { return clientManager.getAuthFiles(null,account.getString("client_moniker")); } + @PartnerMapping(value = "/compliance/complianceInfo", method = RequestMethod.GET) + @ResponseBody + public JSONObject complianceInfo(@ModelAttribute(CommonConsts.PARTNER_STATUS) JSONObject account) { + return null; + } + + @PartnerMapping(value = "/compliance/clientViewFiles", method = RequestMethod.GET) + @ResponseBody + public JSONObject complianceFiles(@ModelAttribute(CommonConsts.PARTNER_STATUS) JSONObject account) { + return clientManager.getComplianceFiles(account); + } + + + @PartnerMapping(value = "/aggregateFile/client_info", method = RequestMethod.GET) + @ResponseBody + public JSONObject getClientInfoByAggree(@ModelAttribute(CommonConsts.PARTNER_STATUS) JSONObject account) { + return clientManager.getClientInfoByAggree(account); + } + +/* @PartnerMapping(value = "/{clientMoniker}/clientViewFiles", method = RequestMethod.GET, role = {ManagerRole.ADMIN, ManagerRole.OPERATOR, ManagerRole.BD_USER, ManagerRole.SERVANT}) + public JSONObject getAuthFiles(@PathVariable String clientMoniker, @ModelAttribute(CommonConsts.MANAGER_STATUS) JSONObject manager) { + return clientManager.getAllAuthFiles(manager, clientMoniker); + }*/ + @PartnerMapping(value = "/verify/email", method = RequestMethod.PUT) @ResponseBody public void sendVerifyEmail(@ModelAttribute(CommonConsts.PARTNER_STATUS) JSONObject account) { @@ -527,4 +563,37 @@ public class PartnerViewController { throw new BadRequestException("已通过审核,暂不能提交和修改"); } } + + @PartnerMapping(value = "/update/wait_compliance_file", method = RequestMethod.PUT) + @ResponseBody + public void updateWaitComplianceFile(@ModelAttribute(CommonConsts.PARTNER_STATUS) JSONObject account, @RequestBody ClientAuthFilesInfo filesInfo) { + JSONObject client = clientManager.getClientInfo(account.getIntValue("client_id")); + JSONObject authFileStatus = signInAccountService.checkAuthFileStatus(client); + if (authFileStatus.getBooleanValue("client_less_file")) { + JSONObject manager = new JSONObject(); + manager.put("display_name","client"); + clientManager.uploadAuthFilesForWaitCompliance(manager, account.getString("client_moniker"), filesInfo); + }else { + throw new BadRequestException("已通过审核,暂不能提交和修改"); + } + } + + + @PartnerMapping(value = "/clientCompliance/{clientMoniker}/viewCommit", method = RequestMethod.POST) + @ResponseBody + public void clientComplianceViewCommit(@PathVariable String clientMoniker ,@ModelAttribute(CommonConsts.PARTNER_STATUS) JSONObject account) { + clientManager.commitAuthFilesToCompliance(clientMoniker, account, "Web"); + } + + @PartnerMapping(value = "/clientCompliance/{clientMoniker}/commit_aggregate_file", method = RequestMethod.POST) + @ResponseBody + public JSONObject getClientArregateFile(@ModelAttribute(CommonConsts.PARTNER_STATUS) JSONObject account, @RequestParam MultipartFile file) throws IOException { + return clientManager.getClientAggregateFile(account, file); + } + + @PartnerMapping(value = "/auth_file/{fileId}/delete", method = RequestMethod.PUT, roles = {PartnerRole.ADMIN, PartnerRole.MANAGER}) + @ResponseBody + public void deleteAuthFiles(@PathVariable String fileId, @ModelAttribute(CommonConsts.PARTNER_STATUS) JSONObject account) { + clientManager.deleteAuthFiles(fileId); + } } diff --git a/src/main/java/au/com/royalpay/payment/manage/signin/core/SignInAccountService.java b/src/main/java/au/com/royalpay/payment/manage/signin/core/SignInAccountService.java index 4802fe677..8d18e669e 100644 --- a/src/main/java/au/com/royalpay/payment/manage/signin/core/SignInAccountService.java +++ b/src/main/java/au/com/royalpay/payment/manage/signin/core/SignInAccountService.java @@ -57,4 +57,6 @@ public interface SignInAccountService { void deleteClientCodeKey(String codekey); void deleteManagerCodeKey(String codekey); + + JSONObject checkAuthFileStatus(JSONObject client); } diff --git a/src/main/java/au/com/royalpay/payment/manage/signin/core/impls/SignInAccountServiceImpl.java b/src/main/java/au/com/royalpay/payment/manage/signin/core/impls/SignInAccountServiceImpl.java index 2129df628..f8bfb61df 100644 --- a/src/main/java/au/com/royalpay/payment/manage/signin/core/impls/SignInAccountServiceImpl.java +++ b/src/main/java/au/com/royalpay/payment/manage/signin/core/impls/SignInAccountServiceImpl.java @@ -42,10 +42,7 @@ import org.thymeleaf.spring4.SpringTemplateEngine; import javax.annotation.PostConstruct; import javax.annotation.Resource; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -87,18 +84,24 @@ public class SignInAccountServiceImpl implements SignInAccountService, Applicati @Resource private ClientFilesMapper clientFilesMapper; @Resource + private ClientComplianceCompanyMapper clientComplianceCompanyMapper; + @Resource private SmsSender smsSender; private final String RESET_CLIENT_ACCOUNT_PREFIX = "RESET_CLIENT_ACCOUNT"; private final String RESET_MANAGER_ACCOUNT_PREFIX = "RESET_MANAGER_ACCOUNT"; + private final String[] FILE_KEYS = {"client_bank_file", "client_company_file", "client_id_file", "client_agree_file"}; + private final String[] PUT_KEYS = {"file_bank_info", "file_company_info", "file_id_info", "file_agreement_info"}; + private final String[] FILE_NAMES = {"* bank statement", "* Certificate of Registration", "* ID", "* Agreement"}; private final int RESET_PASSWORD_TEMPLID = 126978; private ApplicationEventPublisher publisher; private static final List tags = new ArrayList<>(); @PostConstruct - public void init(){ + public void init() { tags.add("account"); } + @Override @Cacheable(value = ":login:managers:", key = "''+#accountId") public JSONObject getManager(String accountId) { @@ -130,7 +133,7 @@ public class SignInAccountServiceImpl implements SignInAccountService, Applicati manager.put("available_func_names", funcNames); manager.put("modules", modules.values()); manager.put("module_names", modules.keySet()); - if((manager.getIntValue("role")& ManagerRole.SERVANT.getMask())>0){ + if ((manager.getIntValue("role") & ManagerRole.SERVANT.getMask()) > 0) { JSONObject cs = sysCustomerServiceMapper.findByManagerId(accountId); if (cs != null) { manager.put("onoff", cs.getBoolean("onoff")); @@ -162,30 +165,19 @@ public class SignInAccountServiceImpl implements SignInAccountService, Applicati } client.putAll(clientConfigService.find(client_id)); client = clientInfoWithNoSecretInfo(client); - if (client.getIntValue("approve_result") == 2 || client.getIntValue("open_status") == 10) { - List resultFiles = new ArrayList<>(); - List clientFiles = clientFilesMapper.findClientFile(client.getIntValue("client_id")); - String[] fileKeys = {"client_bank_file", "client_company_file", "client_id_file", "client_agree_file", "client_apply_file"}; - String[] putKeys = {"file_bank_info", "file_company_info", "file_id_info", "file_agreement_info", "file_apply_info"}; - String[] fileNames = {"* bank statement", "* Certificate of Registration", "* ID", "* Agreement" ,"Application Form(optional)"}; - boolean clientFilesIsLess = false; - for (int i = 0; i < fileKeys.length; i++) { - String fileKey = fileKeys[i]; - if (clientFiles.stream().noneMatch(fileJson -> fileKey.equals(fileJson.getString("file_name")))) { - JSONObject fileJson = new JSONObject(); - fileJson.put("key", putKeys[i]); - fileJson.put("name", fileNames[i]); - fileJson.put("file_value", "not Configure"); - resultFiles.add(fileJson); - if (!"client_apply_file".equals(fileKey)) { - clientFilesIsLess = true; + client.put("client_less_file", false); + if (client.getIntValue("client_id") == 9 &&(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 clientFiles = clientFilesMapper.findAllClientFile(client.getIntValue("client_id")); + if (clientFiles != null && clientFiles.size() > 0) { + clientFiles = clientFiles.stream().filter(fileJson -> ((fileJson.getIntValue("status") == 1 || fileJson.getIntValue("status") == 2))).collect(Collectors.toList()); + String[] fileKeys = {"client_bank_file", "client_company_file", "client_id_file", "client_agree_file"}; + for (String fileKey : fileKeys) { + if (clientFiles.stream().noneMatch(fileJson -> fileKey.equals(fileJson.getString("file_name")))) { + client.put("client_less_file", true); } } - } - client.put("greenChannel", 1); - client.put("client_less_file", clientFilesIsLess); - if (clientFilesIsLess) { - client.put("client_files", resultFiles); + }else { + client.put("client_less_file", true); } } if (client.getInteger("parent_client_id") != null) { @@ -402,7 +394,7 @@ public class SignInAccountServiceImpl implements SignInAccountService, Applicati ctx.setVariable("url", url); ctx.setVariable("accounts", accounts); ctx.setVariable("client_moniker", client_moniker); - final String content = thymeleaf.process( "mail/reset_password", ctx); + final String content = thymeleaf.process("mail/reset_password", ctx); //final String content = VelocityEngineUtils.mergeTemplateIntoString(null, "mail/reset_password.vm", "utf-8", map); new Thread(() -> { @@ -468,11 +460,11 @@ public class SignInAccountServiceImpl implements SignInAccountService, Applicati public static JSONObject clientInfoWithNoSecretInfo(JSONObject client) { JSONObject simpleClient = new JSONObject(); - String[] columns = { "client_id", "client_moniker", "parent_client_id", "company_name", "address", "business_name", "business_structure", "abn", "acn", + String[] columns = {"client_id", "client_moniker", "parent_client_id", "company_name", "address", "business_name", "business_structure", "abn", "acn", "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", "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"}; + "enable_bestpay", "manual_settle", "skip_clearing", "mail_confirm", "surcharge_mode"}; for (String col : columns) { simpleClient.put(col, client.get(col)); } @@ -508,24 +500,24 @@ public class SignInAccountServiceImpl implements SignInAccountService, Applicati } @Override - public void getClientResetPwdCode(JSONObject account,String type) { - if(StringUtils.equals(type,"email") && !account.containsKey("contact_email")){ + public void getClientResetPwdCode(JSONObject account, String type) { + if (StringUtils.equals(type, "email") && !account.containsKey("contact_email")) { throw new BadRequestException("Your account is not bound to your mailbox!"); } - if(StringUtils.equals(type,"phone") && !account.containsKey("contact_phone") && !account.containsKey("nation_code")){ + if (StringUtils.equals(type, "phone") && !account.containsKey("contact_phone") && !account.containsKey("nation_code")) { throw new BadRequestException("Your account is not bound to your phone!"); } String accountId = account.getString("account_id"); String codeKeyValueRedis = stringRedisTemplate.boundValueOps(getResetClientAccountKey(accountId)).get(); - if(StringUtils.isNotEmpty(codeKeyValueRedis)){ - throw new BadRequestException("Captcha has been sent.Please check your "+type+" or try again in 5 minutes."); + if (StringUtils.isNotEmpty(codeKeyValueRedis)) { + throw new BadRequestException("Captcha has been sent.Please check your " + type + " or try again in 5 minutes."); } String codeKeyValue = RandomStringUtils.random(6, false, true); - switch(type){ + switch (type) { case "email": Context ctx = new Context(); - ctx.setVariable("account",account); - ctx.setVariable("captcha",codeKeyValue); + ctx.setVariable("account", account); + ctx.setVariable("captcha", codeKeyValue); final String content = thymeleaf.process("mail/account_reset_email.html", ctx); royalThreadPoolExecutor.execute(() -> { try { @@ -550,28 +542,28 @@ public class SignInAccountServiceImpl implements SignInAccountService, Applicati } break; } - stringRedisTemplate.boundValueOps(getResetClientAccountKey(accountId)).set(codeKeyValue,5, TimeUnit.MINUTES); + stringRedisTemplate.boundValueOps(getResetClientAccountKey(accountId)).set(codeKeyValue, 5, TimeUnit.MINUTES); } @Override public void getManagerResetPwdCode(JSONObject account, String type) { - if(StringUtils.equals(type,"email") && !account.containsKey("email")){ + if (StringUtils.equals(type, "email") && !account.containsKey("email")) { throw new BadRequestException("Your account is not bound to your mailbox!"); } - if(StringUtils.equals(type,"phone") && !account.containsKey("phone") && !account.containsKey("nation_code")){ + if (StringUtils.equals(type, "phone") && !account.containsKey("phone") && !account.containsKey("nation_code")) { throw new BadRequestException("Your account is not bound to your phone!"); } String managerId = account.getString("manager_id"); String codeKeyValueRedis = stringRedisTemplate.boundValueOps(getResetManagerAccountKey(managerId)).get(); - if(StringUtils.isNotEmpty(codeKeyValueRedis)){ - throw new BadRequestException("Captcha has been sent.Please check your "+type+" or try again in 5 minutes."); + if (StringUtils.isNotEmpty(codeKeyValueRedis)) { + throw new BadRequestException("Captcha has been sent.Please check your " + type + " or try again in 5 minutes."); } String codeKeyValue = RandomStringUtils.random(6, false, true); - switch(type){ + switch (type) { case "email": Context ctx = new Context(); - ctx.setVariable("account",account); - ctx.setVariable("captcha",codeKeyValue); + ctx.setVariable("account", account); + ctx.setVariable("captcha", codeKeyValue); final String content = thymeleaf.process("mail/account_reset_email.html", ctx); royalThreadPoolExecutor.execute(() -> { try { @@ -596,16 +588,16 @@ public class SignInAccountServiceImpl implements SignInAccountService, Applicati } break; } - stringRedisTemplate.boundValueOps(getResetManagerAccountKey(managerId)).set(codeKeyValue,5, TimeUnit.MINUTES); + stringRedisTemplate.boundValueOps(getResetManagerAccountKey(managerId)).set(codeKeyValue, 5, TimeUnit.MINUTES); } @Override public void verifyClientCaptcha(JSONObject account, String captcha) { String captchaRedis = stringRedisTemplate.boundValueOps(getResetClientAccountKey(account.getString("account_id"))).get(); - if(StringUtils.isBlank(captchaRedis)){ + if (StringUtils.isBlank(captchaRedis)) { throw new BadRequestException("Captcha has expired"); } - if(StringUtils.equals(captcha,captchaRedis)){ + if (StringUtils.equals(captcha, captchaRedis)) { throw new BadRequestException("Captcha is wrong"); } } @@ -613,10 +605,10 @@ public class SignInAccountServiceImpl implements SignInAccountService, Applicati @Override public void verifyManagerCaptcha(JSONObject account, String captcha) { String captchaRedis = stringRedisTemplate.boundValueOps(getResetManagerAccountKey(account.getString("manager_id"))).get(); - if(StringUtils.isBlank(captchaRedis)){ + if (StringUtils.isBlank(captchaRedis)) { throw new BadRequestException("Captcha has expired"); } - if(StringUtils.equals(captcha,captchaRedis)){ + if (StringUtils.equals(captcha, captchaRedis)) { throw new BadRequestException("Captcha is wrong"); } } @@ -631,20 +623,97 @@ public class SignInAccountServiceImpl implements SignInAccountService, Applicati deleteManagerAccountKey(codekey); } - private void deleteClientAccountKey(String codeKey){ + private void deleteClientAccountKey(String codeKey) { stringRedisTemplate.delete(getResetClientAccountKey(codeKey)); } - private void deleteManagerAccountKey(String codeKey){ + private void deleteManagerAccountKey(String codeKey) { stringRedisTemplate.delete(getResetManagerAccountKey(codeKey)); } - private String getResetClientAccountKey(String codeKey){ + private String getResetClientAccountKey(String codeKey) { return RESET_CLIENT_ACCOUNT_PREFIX + codeKey; } - private String getResetManagerAccountKey(String codeKey){ + private String getResetManagerAccountKey(String codeKey) { return RESET_MANAGER_ACCOUNT_PREFIX + codeKey; } + @Override + public JSONObject checkAuthFileStatus(JSONObject client) { + JSONObject result = new JSONObject(); + result.put("client_less_file", false); + result.put("put_fail_pdf", "https://file.royalpay.com.au/open/2019/08/28/1566959635986_P1GuvCkuWINPhUJUqUQnz8E0u6Lgpx.pdf"); + if (client.getIntValue("client_id") == 9 &&(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 clientFiles = clientFilesMapper.findAllClientFile(client.getIntValue("client_id")); + boolean clientFilesIsLess = false; + for (int i = 0; i < FILE_KEYS.length; i++) { + String fileKey = FILE_KEYS[i]; + if (clientFiles != null && clientFiles.size() > 0) { + List clientFileUrl = clientFiles.stream() + .filter(fileJson -> (fileKey.equals(fileJson.getString("file_name")) && (fileJson.getIntValue("status") == 1 || fileJson.getIntValue("status") == 2))) + .sorted((log1, log2) -> log2.getDate("last_update_date").compareTo(log1.getDate("last_update_date"))) + .map(json -> { + JSONObject params = new JSONObject(); + params.put("file_id", json.getString("file_id")); + params.put("file_value", json.getString("file_value")); + return params; + }) + .collect(Collectors.toList()); + if (clientFileUrl != null && clientFileUrl.size() > 0) { + JSONObject fileJson = new JSONObject(); + fileJson.put("key", PUT_KEYS[i]); + fileJson.put("name", FILE_NAMES[i]); + fileJson.put("file_value", clientFileUrl); + fileJson.put("file_write", false); + result.put(fileKey,fileJson); + } else { + List clientBackToFileUrl = clientFiles.stream() + .filter(fileJson -> (fileKey.equals(fileJson.getString("file_name")) && (fileJson.getIntValue("status") == 0 || fileJson.getIntValue("status") == 3))) + .sorted((log1, log2) -> log2.getDate("last_update_date").compareTo(log1.getDate("last_update_date"))) + .map(json -> { + JSONObject params = new JSONObject(); + params.put("file_id", json.getString("file_id")); + params.put("file_value", json.getString("file_value")); + return params; + }) + .collect(Collectors.toList()); + JSONObject fileJson = new JSONObject(); + fileJson.put("key", PUT_KEYS[i]); + fileJson.put("name", FILE_NAMES[i]); + if (clientBackToFileUrl != null && clientBackToFileUrl.size() > 0) { + if ("client_agree_file".equals(fileKey)) { + List agreeFile = new ArrayList<>(); + agreeFile.add(clientBackToFileUrl.get(0)); + fileJson.put("file_value", agreeFile); + }else { + fileJson.put("file_value", clientBackToFileUrl); + } + } + fileJson.put("file_write", true); + result.put(fileKey,fileJson); + clientFilesIsLess = true; + } + }else { + clientFilesIsLess = true; + for (int c = 0; c < FILE_KEYS.length; c++) { + String key = FILE_KEYS[c]; + JSONObject fileJson = new JSONObject(); + fileJson.put("key", PUT_KEYS[c]); + fileJson.put("name", FILE_NAMES[c]); + fileJson.put("file_write", true); + result.put(key,fileJson); + } + } + result.put("client_less_file", clientFilesIsLess); + if (clientFilesIsLess) { + JSONObject authFileCompliance = clientComplianceCompanyMapper.findFileByClientId(client.getIntValue("client_id")); + if (authFileCompliance != null && StringUtils.isNotBlank(authFileCompliance.getString("description"))) { + result.put("client_refuse_reason", "Refuse reason/打回原因:" + authFileCompliance.getString("description")); + } + } + } + } + return result; + } } diff --git a/src/main/resources/au/com/royalpay/payment/manage/mappers/system/ClientComplianceCompanyMapper.xml b/src/main/resources/au/com/royalpay/payment/manage/mappers/system/ClientComplianceCompanyMapper.xml new file mode 100644 index 000000000..11b887cbe --- /dev/null +++ b/src/main/resources/au/com/royalpay/payment/manage/mappers/system/ClientComplianceCompanyMapper.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/src/main/resources/au/com/royalpay/payment/manage/mappers/system/ClientFilesMapper.xml b/src/main/resources/au/com/royalpay/payment/manage/mappers/system/ClientFilesMapper.xml index 655d7026d..25c6a1df3 100644 --- a/src/main/resources/au/com/royalpay/payment/manage/mappers/system/ClientFilesMapper.xml +++ b/src/main/resources/au/com/royalpay/payment/manage/mappers/system/ClientFilesMapper.xml @@ -5,17 +5,41 @@ select * from sys_files where client_id = #{client_id} and file_name = #{file_name} + and status = 1 + and is_valid = 1 order by last_update_date desc + + update sys_files set state = 2 where file_name = 'source_agree_file' and client_id = #{client_id} - - \ No newline at end of file + and is_valid = 1 + and (status = 0 or status = 3) + + + + update sys_files set `status` = 3 where client_id = #{client_id} and is_valid = 1 and status = 2 + + + + update sys_files set `status` = 1 where client_id = #{client_id} and is_valid = 1 and status = 2 + + + diff --git a/src/main/ui/manage.html b/src/main/ui/manage.html index 5c9e2a43f..97aaaeb7a 100644 --- a/src/main/ui/manage.html +++ b/src/main/ui/manage.html @@ -472,6 +472,11 @@ margin-bottom: 10%;"/> 商户合规 +
  • + + 合规文件审核 + +
  • 合同签约情况 diff --git a/src/main/ui/static/boot/indexMainApp.js b/src/main/ui/static/boot/indexMainApp.js index 3d36c1f4a..252bf4eb6 100644 --- a/src/main/ui/static/boot/indexMainApp.js +++ b/src/main/ui/static/boot/indexMainApp.js @@ -19,6 +19,7 @@ define(['angular', 'angularSanitize', 'angularAnimate', 'angularMessages', 'uiRo var stompClient = null; var newPartnerGuide_counts = 0; var pwdcount = 0; + var complianceNoticeCount = 0; $scope.loadCurrentUser = function () { $http.get('/global/userstatus/current_partner').then(function (resp) { $rootScope.currentUser = resp.data; @@ -35,7 +36,6 @@ define(['angular', 'angularSanitize', 'angularAnimate', 'angularMessages', 'uiRo }*/ if ($rootScope.currentUser.is_password_expired && pwdcount == 0) { - commonDialog.confirm({ title: 'Change Password!', content: 'Your password has been expired,click OK to set a new password' @@ -44,6 +44,25 @@ define(['angular', 'angularSanitize', 'angularAnimate', 'angularMessages', 'uiRo }) pwdcount++; } + $scope.ComplianceToperfect = function () { + $uibModal.open({ + templateUrl: '/static/payment/partner/templates/compliance_files_advice.html', + size: 'lg', + controller: 'partnerFilesCtrl', + resolve: { + file: ['$http', function ($http) { + return $http.get('/client/partner_info/compliance/clientViewFiles'); + }] + } + + + }) + }; + if($scope.currentUser.client.client_less_file && complianceNoticeCount==0 && $scope.currentUser.role!=3) + { + $scope.ComplianceToperfect(); + complianceNoticeCount++; + } connectWebSocket(); if ($scope.currentUser.role == 1 || $scope.currentUser.role == 2) { $scope.getAgStatus(); @@ -458,6 +477,9 @@ define(['angular', 'angularSanitize', 'angularAnimate', 'angularMessages', 'uiRo }) } }]); + app.controller('partnerFilesCtrl', ['$scope', '$http', '$sce', 'file', function ($scope, $http, $sce, file) { + $scope.file = angular.copy(file); + }]); app.factory('myLoginLogView', ['$uibModal', function ($uibModal) { return { show: function () { @@ -623,4 +645,4 @@ define(['angular', 'angularSanitize', 'angularAnimate', 'angularMessages', 'uiRo ngModelOptions: {} }); return app; -}); \ No newline at end of file +}); diff --git a/src/main/ui/static/css/img/loading_contract.gif b/src/main/ui/static/css/img/loading_contract.gif new file mode 100644 index 000000000..768db2d0b Binary files /dev/null and b/src/main/ui/static/css/img/loading_contract.gif differ diff --git a/src/main/ui/static/dashboard/partner-dashboard.js b/src/main/ui/static/dashboard/partner-dashboard.js index 3c2cf472e..07209cc68 100644 --- a/src/main/ui/static/dashboard/partner-dashboard.js +++ b/src/main/ui/static/dashboard/partner-dashboard.js @@ -9,7 +9,12 @@ define(['angular','decimal', 'uiRouter', 'uiBootstrap', 'angularEcharts'], funct $stateProvider.state('partner_dashboard', { url: '/partner_dashboard', templateUrl: '/static/dashboard/templates/partner_dashboard.html', - controller: 'partnerDashboardCtrl' + controller: 'partnerDashboardCtrl', + resolve: { + company_info: ['$http', function ($http) { + return $http.get('/client/partner_info/compliance/complianceInfo'); + }] + } }) }]); /*app.controller('partnerDashboardCtrl', ['$scope', '$http', '$filter', '$uibModal','$timeout', 'chartParser', function ($scope, $http, $filter, $uibModal,$timeout, chartParser) { @@ -568,7 +573,8 @@ define(['angular','decimal', 'uiRouter', 'uiBootstrap', 'angularEcharts'], funct } }]);*/ - app.controller('partnerDashboardCtrl', ['$scope', '$http', '$filter', '$uibModal','$timeout', 'chartParser','clearingDetailService','commonDialog', function ($scope, $http, $filter, $uibModal,$timeout, chartParser,clearingDetailService,commonDialog) { + app.controller('partnerDashboardCtrl', ['$scope', '$http', '$filter', '$uibModal','$timeout', 'chartParser','clearingDetailService','commonDialog','company_info', function ($scope, $http, $filter, $uibModal,$timeout, chartParser,clearingDetailService,commonDialog,company_info) { + $scope.company_info = company_info.data || {}; $scope.sendMailCount = 0; $scope.scales = [ { @@ -967,6 +973,7 @@ define(['angular','decimal', 'uiRouter', 'uiBootstrap', 'angularEcharts'], funct commonDialog.alert({title: 'Message', content: 'Verify that the message has been sent to your mailbox. Please complete the validation as soon as possible', type: 'info'}); }) } + }]); app.controller('contactCustomerServiceDialogCtrl', ['$scope', '$http', function ($scope, $http) { @@ -979,4 +986,4 @@ define(['angular','decimal', 'uiRouter', 'uiBootstrap', 'angularEcharts'], funct } }); return app; -}); \ No newline at end of file +}); diff --git a/src/main/ui/static/lib/jSignature/jSignature.js b/src/main/ui/static/lib/jSignature/jSignature.js new file mode 100644 index 000000000..4a71d75ec --- /dev/null +++ b/src/main/ui/static/lib/jSignature/jSignature.js @@ -0,0 +1,1486 @@ +/** @preserve +jSignature v2 "${buildDate}" "${commitID}" +Copyright (c) 2012 Willow Systems Corp http://willow-systems.com +Copyright (c) 2010 Brinley Ang http://www.unbolt.net +MIT License + +*/ +;(function() { + +var apinamespace = 'jSignature' + +/** +Allows one to delay certain eventual action by setting up a timer for it and allowing one to delay it +by "kick"ing it. Sorta like "kick the can down the road" + +@public +@class +@param +@returns {Type} +*/ +var KickTimerClass = function(time, callback) { + var timer; + this.kick = function() { + clearTimeout(timer); + timer = setTimeout( + callback + , time + ); + } + this.clear = function() { + clearTimeout(timer); + } + return this; +} + +var PubSubClass = function(context){ + 'use strict' + /* @preserve + ----------------------------------------------------------------------------------------------- + JavaScript PubSub library + 2012 (c) Willow Systems Corp (www.willow-systems.com) + based on Peter Higgins (dante@dojotoolkit.org) + Loosely based on Dojo publish/subscribe API, limited in scope. Rewritten blindly. + Original is (c) Dojo Foundation 2004-2010. Released under either AFL or new BSD, see: + http://dojofoundation.org/license for more information. + ----------------------------------------------------------------------------------------------- + */ + this.topics = {}; + // here we choose what will be "this" for the called events. + // if context is defined, it's context. Else, 'this' is this instance of PubSub + this.context = context ? context : this; + /** + * Allows caller to emit an event and pass arguments to event listeners. + * @public + * @function + * @param topic {String} Name of the channel on which to voice this event + * @param **arguments Any number of arguments you want to pass to the listeners of this event. + */ + this.publish = function(topic, arg1, arg2, etc) { + 'use strict' + if (this.topics[topic]) { + var currentTopic = this.topics[topic] + , args = Array.prototype.slice.call(arguments, 1) + , toremove = [] + , torun = [] + , fn + , i, l + , pair; + + for (i = 0, l = currentTopic.length; i < l; i++) { + pair = currentTopic[i]; // this is a [function, once_flag] array + fn = pair[0]; + if (pair[1] /* 'run once' flag set */){ + pair[0] = function(){}; + toremove.push(i); + } + /* don't call the callback right now, it might decide to add or + * remove subscribers which will wreak havoc on our index-based + * iteration */ + torun.push(fn); + } + for (i = 0, l = toremove.length; i < l; i++) { + currentTopic.splice(toremove[i], 1); + } + for (i = 0, l = torun.length; i < l; i++) { + torun[i].apply(this.context, args); + } + } + } + /** + * Allows listener code to subscribe to channel and be called when data is available + * @public + * @function + * @param topic {String} Name of the channel on which to voice this event + * @param callback {Function} Executable (function pointer) that will be ran when event is voiced on this channel. + * @param once {Boolean} (optional. False by default) Flag indicating if the function is to be triggered only once. + * @returns {Object} A token object that cen be used for unsubscribing. + */ + this.subscribe = function(topic, callback, once) { + 'use strict' + if (!this.topics[topic]) { + this.topics[topic] = [[callback, once]]; + } else { + this.topics[topic].push([callback,once]); + } + return { + "topic": topic, + "callback": callback + }; + }; + /** + * Allows listener code to unsubscribe from a channel + * @public + * @function + * @param token {Object} A token object that was returned by `subscribe` method + */ + this.unsubscribe = function(token) { + if (this.topics[token.topic]) { + var currentTopic = this.topics[token.topic]; + + for (var i = 0, l = currentTopic.length; i < l; i++) { + if (currentTopic[i] && currentTopic[i][0] === token.callback) { + currentTopic.splice(i, 1); + } + } + } + } +} + +/// Returns front, back and "decor" colors derived from element (as jQuery obj) +function getColors($e){ + var tmp + , undef + , frontcolor = $e.css('color') + , backcolor + , e = $e[0]; + + var toOfDOM = false; + while(e && !backcolor && !toOfDOM){ + try{ + tmp = $(e).css('background-color'); + } catch (ex) { + tmp = 'transparent'; + } + if (tmp !== 'transparent' && tmp !== 'rgba(0, 0, 0, 0)'){ + backcolor = tmp; + } + toOfDOM = e.body; + e = e.parentNode; + } + + var rgbaregex = /rgb[a]*\((\d+),\s*(\d+),\s*(\d+)/ // modern browsers + , hexregex = /#([AaBbCcDdEeFf\d]{2})([AaBbCcDdEeFf\d]{2})([AaBbCcDdEeFf\d]{2})/ // IE 8 and less. + , frontcolorcomponents; + + // Decomposing Front color into R, G, B ints + tmp = undef; + tmp = frontcolor.match(rgbaregex); + if (tmp){ + frontcolorcomponents = {'r':parseInt(tmp[1],10),'g':parseInt(tmp[2],10),'b':parseInt(tmp[3],10)}; + } else { + tmp = frontcolor.match(hexregex); + if (tmp) { + frontcolorcomponents = {'r':parseInt(tmp[1],16),'g':parseInt(tmp[2],16),'b':parseInt(tmp[3],16)}; + } + } +// if(!frontcolorcomponents){ +// frontcolorcomponents = {'r':255,'g':255,'b':255} +// } + + var backcolorcomponents + // Decomposing back color into R, G, B ints + if(!backcolor){ + // HIghly unlikely since this means that no background styling was applied to any element from here to top of dom. + // we'll pick up back color from front color + if(frontcolorcomponents){ + if (Math.max.apply(null, [frontcolorcomponents.r, frontcolorcomponents.g, frontcolorcomponents.b]) > 127){ + backcolorcomponents = {'r':0,'g':0,'b':0}; + } else { + backcolorcomponents = {'r':255,'g':255,'b':255}; + } + } else { + // arg!!! front color is in format we don't understand (hsl, named colors) + // Let's just go with white background. + backcolorcomponents = {'r':255,'g':255,'b':255}; + } + } else { + tmp = undef; + tmp = backcolor.match(rgbaregex); + if (tmp){ + backcolorcomponents = {'r':parseInt(tmp[1],10),'g':parseInt(tmp[2],10),'b':parseInt(tmp[3],10)}; + } else { + tmp = backcolor.match(hexregex); + if (tmp) { + backcolorcomponents = {'r':parseInt(tmp[1],16),'g':parseInt(tmp[2],16),'b':parseInt(tmp[3],16)}; + } + } +// if(!backcolorcomponents){ +// backcolorcomponents = {'r':0,'g':0,'b':0} +// } + } + + // Deriving Decor color + // THis is LAZY!!!! Better way would be to use HSL and adjust luminocity. However, that could be an overkill. + + var toRGBfn = function(o){return 'rgb(' + [o.r, o.g, o.b].join(', ') + ')'} + , decorcolorcomponents + , frontcolorbrightness + , adjusted; + + if (frontcolorcomponents && backcolorcomponents){ + var backcolorbrightness = Math.max.apply(null, [frontcolorcomponents.r, frontcolorcomponents.g, frontcolorcomponents.b]); + + frontcolorbrightness = Math.max.apply(null, [backcolorcomponents.r, backcolorcomponents.g, backcolorcomponents.b]); + adjusted = Math.round(frontcolorbrightness + (-1 * (frontcolorbrightness - backcolorbrightness) * 0.75)); // "dimming" the difference between pen and back. + decorcolorcomponents = {'r':adjusted,'g':adjusted,'b':adjusted}; // always shade of gray + } else if (frontcolorcomponents) { + frontcolorbrightness = Math.max.apply(null, [frontcolorcomponents.r, frontcolorcomponents.g, frontcolorcomponents.b]); + var polarity = +1; + if (frontcolorbrightness > 127){ + polarity = -1; + } + // shifting by 25% (64 points on RGB scale) + adjusted = Math.round(frontcolorbrightness + (polarity * 96)); // "dimming" the pen's color by 75% to get decor color. + decorcolorcomponents = {'r':adjusted,'g':adjusted,'b':adjusted}; // always shade of gray + } else { + decorcolorcomponents = {'r':191,'g':191,'b':191}; // always shade of gray + } + + return { + 'color': frontcolor + , 'background-color': backcolorcomponents? toRGBfn(backcolorcomponents) : backcolor + , 'decor-color': toRGBfn(decorcolorcomponents) + }; +} + +function Vector(x,y){ + this.x = x; + this.y = y; + this.reverse = function(){ + return new this.constructor( + this.x * -1 + , this.y * -1 + ); + }; + this._length = null; + this.getLength = function(){ + if (!this._length){ + this._length = Math.sqrt( Math.pow(this.x, 2) + Math.pow(this.y, 2) ); + } + return this._length; + }; + + var polarity = function (e){ + return Math.round(e / Math.abs(e)); + }; + this.resizeTo = function(length){ + // proportionally changes x,y such that the hypotenuse (vector length) is = new length + if (this.x === 0 && this.y === 0){ + this._length = 0; + } else if (this.x === 0){ + this._length = length; + this.y = length * polarity(this.y); + } else if(this.y === 0){ + this._length = length; + this.x = length * polarity(this.x); + } else { + var proportion = Math.abs(this.y / this.x) + , x = Math.sqrt(Math.pow(length, 2) / (1 + Math.pow(proportion, 2))) + , y = proportion * x; + this._length = length; + this.x = x * polarity(this.x); + this.y = y * polarity(this.y); + } + return this; + }; + + /** + * Calculates the angle between 'this' vector and another. + * @public + * @function + * @returns {Number} The angle between the two vectors as measured in PI. + */ + this.angleTo = function(vectorB) { + var divisor = this.getLength() * vectorB.getLength(); + if (divisor === 0) { + return 0; + } else { + // JavaScript floating point math is screwed up. + // because of it, the core of the formula can, on occasion, have values + // over 1.0 and below -1.0. + return Math.acos( + Math.min( + Math.max( + ( this.x * vectorB.x + this.y * vectorB.y ) / divisor + , -1.0 + ) + , 1.0 + ) + ) / Math.PI; + } + }; +} + +function Point(x,y){ + this.x = x; + this.y = y; + + this.getVectorToCoordinates = function (x, y) { + return new Vector(x - this.x, y - this.y); + }; + this.getVectorFromCoordinates = function (x, y) { + return this.getVectorToCoordinates(x, y).reverse(); + }; + this.getVectorToPoint = function (point) { + return new Vector(point.x - this.x, point.y - this.y); + }; + this.getVectorFromPoint = function (point) { + return this.getVectorToPoint(point).reverse(); + }; +} + +/* + * About data structure: + * We don't store / deal with "pictures" this signature capture code captures "vectors" + * + * We don't store bitmaps. We store "strokes" as arrays of arrays. (Actually, arrays of objects containing arrays of coordinates. + * + * Stroke = mousedown + mousemoved * n (+ mouseup but we don't record that as that was the "end / lack of movement" indicator) + * + * Vectors = not classical vectors where numbers indicated shift relative last position. Our vectors are actually coordinates against top left of canvas. + * we could calc the classical vectors, but keeping the the actual coordinates allows us (through Math.max / min) + * to calc the size of resulting drawing very quickly. If we want classical vectors later, we can always get them in backend code. + * + * So, the data structure: + * + * var data = [ + * { // stroke starts + * x : [101, 98, 57, 43] // x points + * , y : [1, 23, 65, 87] // y points + * } // stroke ends + * , { // stroke starts + * x : [55, 56, 57, 58] // x points + * , y : [101, 97, 54, 4] // y points + * } // stroke ends + * , { // stroke consisting of just a dot + * x : [53] // x points + * , y : [151] // y points + * } // stroke ends + * ] + * + * we don't care or store stroke width (it's canvas-size-relative), color, shadow values. These can be added / changed on whim post-capture. + * + */ +function DataEngine(storageObject, context, startStrokeFn, addToStrokeFn, endStrokeFn){ + this.data = storageObject; // we expect this to be an instance of Array + this.context = context; + + if (storageObject.length){ + // we have data to render + var numofstrokes = storageObject.length + , stroke + , numofpoints; + + for (var i = 0; i < numofstrokes; i++){ + stroke = storageObject[i]; + numofpoints = stroke.x.length; + startStrokeFn.call(context, stroke); + for(var j = 1; j < numofpoints; j++){ + addToStrokeFn.call(context, stroke, j); + } + endStrokeFn.call(context, stroke); + } + } + + this.changed = function(){}; + + this.startStrokeFn = startStrokeFn; + this.addToStrokeFn = addToStrokeFn; + this.endStrokeFn = endStrokeFn; + + this.inStroke = false; + + this._lastPoint = null; + this._stroke = null; + this.startStroke = function(point){ + if(point && typeof(point.x) == "number" && typeof(point.y) == "number"){ + this._stroke = {'x':[point.x], 'y':[point.y]}; + this.data.push(this._stroke); + this._lastPoint = point; + this.inStroke = true; + // 'this' does not work same inside setTimeout( + var stroke = this._stroke + , fn = this.startStrokeFn + , context = this.context; + setTimeout( + // some IE's don't support passing args per setTimeout API. Have to create closure every time instead. + function() {fn.call(context, stroke)} + , 3 + ); + return point; + } else { + return null; + } + }; + // that "5" at the very end of this if is important to explain. + // we do NOT render links between two captured points (in the middle of the stroke) if the distance is shorter than that number. + // not only do we NOT render it, we also do NOT capture (add) these intermediate points to storage. + // when clustering of these is too tight, it produces noise on the line, which, because of smoothing, makes lines too curvy. + // maybe, later, we can expose this as a configurable setting of some sort. + this.addToStroke = function(point){ + if (this.inStroke && + typeof(point.x) === "number" && + typeof(point.y) === "number" && + // calculates absolute shift in diagonal pixels away from original point + (Math.abs(point.x - this._lastPoint.x) + Math.abs(point.y - this._lastPoint.y)) > 4 + ){ + var positionInStroke = this._stroke.x.length; + this._stroke.x.push(point.x); + this._stroke.y.push(point.y); + this._lastPoint = point; + + var stroke = this._stroke + , fn = this.addToStrokeFn + , context = this.context; + setTimeout( + // some IE's don't support passing args per setTimeout API. Have to create closure every time instead. + function() {fn.call(context, stroke, positionInStroke)} + , 3 + ); + return point; + } else { + return null; + } + }; + this.endStroke = function(){ + var c = this.inStroke; + this.inStroke = false; + this._lastPoint = null; + if (c){ + var stroke = this._stroke + , fn = this.endStrokeFn // 'this' does not work same inside setTimeout( + , context = this.context + , changedfn = this.changed; + setTimeout( + // some IE's don't support passing args per setTimeout API. Have to create closure every time instead. + function(){ + fn.call(context, stroke); + changedfn.call(context); + } + , 3 + ); + return true; + } else { + return null; + } + }; +} + +var basicDot = function(ctx, x, y, size){ + var fillStyle = ctx.fillStyle; + ctx.fillStyle = ctx.strokeStyle; + ctx.fillRect(x + size / -2 , y + size / -2, size, size); + ctx.fillStyle = fillStyle; +} +, basicLine = function(ctx, startx, starty, endx, endy){ + ctx.beginPath(); + ctx.moveTo(startx, starty); + ctx.lineTo(endx, endy); + ctx.closePath(); + ctx.stroke(); +} +, basicCurve = function(ctx, startx, starty, endx, endy, cp1x, cp1y, cp2x, cp2y){ + ctx.beginPath(); + ctx.moveTo(startx, starty); + ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, endx, endy); + ctx.closePath(); + ctx.stroke(); +} +, strokeStartCallback = function(stroke) { + // this = jSignatureClass instance + basicDot(this.canvasContext, stroke.x[0], stroke.y[0], this.settings.lineWidth); +} +, strokeAddCallback = function(stroke, positionInStroke){ + // this = jSignatureClass instance + + // Because we are funky this way, here we draw TWO curves. + // 1. POSSIBLY "this line" - spanning from point right before us, to this latest point. + // 2. POSSIBLY "prior curve" - spanning from "latest point" to the one before it. + + // Why you ask? + // long lines (ones with many pixels between them) do not look good when they are part of a large curvy stroke. + // You know, the jaggedy crocodile spine instead of a pretty, smooth curve. Yuck! + // We want to approximate pretty curves in-place of those ugly lines. + // To approximate a very nice curve we need to know the direction of line before and after. + // Hence, on long lines we actually wait for another point beyond it to come back from + // mousemoved before we draw this curve. + + // So for "prior curve" to be calc'ed we need 4 points + // A, B, C, D (we are on D now, A is 3 points in the past.) + // and 3 lines: + // pre-line (from points A to B), + // this line (from points B to C), (we call it "this" because if it was not yet, it's the only one we can draw for sure.) + // post-line (from points C to D) (even through D point is 'current' we don't know how we can draw it yet) + // + // Well, actually, we don't need to *know* the point A, just the vector A->B + var Cpoint = new Point(stroke.x[positionInStroke-1], stroke.y[positionInStroke-1]) + , Dpoint = new Point(stroke.x[positionInStroke], stroke.y[positionInStroke]) + , CDvector = Cpoint.getVectorToPoint(Dpoint); + + // Again, we have a chance here to draw TWO things: + // BC Curve (only if it's long, because if it was short, it was drawn by previous callback) and + // CD Line (only if it's short) + + // So, let's start with BC curve. + // if there is only 2 points in stroke array, we don't have "history" long enough to have point B, let alone point A. + // Falling through to drawing line CD is proper, as that's the only line we have points for. + if(positionInStroke > 1) { + // we are here when there are at least 3 points in stroke array. + var Bpoint = new Point(stroke.x[positionInStroke-2], stroke.y[positionInStroke-2]) + , BCvector = Bpoint.getVectorToPoint(Cpoint) + , ABvector; + if(BCvector.getLength() > this.lineCurveThreshold){ + // Yey! Pretty curves, here we come! + if(positionInStroke > 2) { + // we are here when at least 4 points in stroke array. + ABvector = (new Point(stroke.x[positionInStroke-3], stroke.y[positionInStroke-3])).getVectorToPoint(Bpoint); + } else { + ABvector = new Vector(0,0); + } + + var minlenfraction = 0.05 + , maxlen = BCvector.getLength() * 0.35 + , ABCangle = BCvector.angleTo(ABvector.reverse()) + , BCDangle = CDvector.angleTo(BCvector.reverse()) + , BCP1vector = new Vector(ABvector.x + BCvector.x, ABvector.y + BCvector.y).resizeTo( + Math.max(minlenfraction, ABCangle) * maxlen + ) + , CCP2vector = (new Vector(BCvector.x + CDvector.x, BCvector.y + CDvector.y)).reverse().resizeTo( + Math.max(minlenfraction, BCDangle) * maxlen + ); + + basicCurve( + this.canvasContext + , Bpoint.x + , Bpoint.y + , Cpoint.x + , Cpoint.y + , Bpoint.x + BCP1vector.x + , Bpoint.y + BCP1vector.y + , Cpoint.x + CCP2vector.x + , Cpoint.y + CCP2vector.y + ); + } + } + if(CDvector.getLength() <= this.lineCurveThreshold){ + basicLine( + this.canvasContext + , Cpoint.x + , Cpoint.y + , Dpoint.x + , Dpoint.y + ); + } +} +, strokeEndCallback = function(stroke){ + // this = jSignatureClass instance + + // Here we tidy up things left unfinished in last strokeAddCallback run. + + // What's POTENTIALLY left unfinished there is the curve between the last points + // in the stroke, if the len of that line is more than lineCurveThreshold + // If the last line was shorter than lineCurveThreshold, it was drawn there, and there + // is nothing for us here to do. + // We can also be called when there is only one point in the stroke (meaning, the + // stroke was just a dot), in which case, again, there is nothing for us to do. + + // So for "this curve" to be calc'ed we need 3 points + // A, B, C + // and 2 lines: + // pre-line (from points A to B), + // this line (from points B to C) + // Well, actually, we don't need to *know* the point A, just the vector A->B + // so, we really need points B, C and AB vector. + var positionInStroke = stroke.x.length - 1; + + if (positionInStroke > 0){ + // there are at least 2 points in the stroke.we are in business. + var Cpoint = new Point(stroke.x[positionInStroke], stroke.y[positionInStroke]) + , Bpoint = new Point(stroke.x[positionInStroke-1], stroke.y[positionInStroke-1]) + , BCvector = Bpoint.getVectorToPoint(Cpoint) + , ABvector; + if (BCvector.getLength() > this.lineCurveThreshold){ + // yep. This one was left undrawn in prior callback. Have to draw it now. + if (positionInStroke > 1){ + // we have at least 3 elems in stroke + ABvector = (new Point(stroke.x[positionInStroke-2], stroke.y[positionInStroke-2])).getVectorToPoint(Bpoint); + var BCP1vector = new Vector(ABvector.x + BCvector.x, ABvector.y + BCvector.y).resizeTo(BCvector.getLength() / 2); + basicCurve( + this.canvasContext + , Bpoint.x + , Bpoint.y + , Cpoint.x + , Cpoint.y + , Bpoint.x + BCP1vector.x + , Bpoint.y + BCP1vector.y + , Cpoint.x + , Cpoint.y + ); + } else { + // Since there is no AB leg, there is no curve to draw. This line is still "long" but no curve. + basicLine( + this.canvasContext + , Bpoint.x + , Bpoint.y + , Cpoint.x + , Cpoint.y + ); + } + } + } +} + + +/* +var getDataStats = function(){ + var strokecnt = strokes.length + , stroke + , pointid + , pointcnt + , x, y + , maxX = Number.NEGATIVE_INFINITY + , maxY = Number.NEGATIVE_INFINITY + , minX = Number.POSITIVE_INFINITY + , minY = Number.POSITIVE_INFINITY + for(strokeid = 0; strokeid < strokecnt; strokeid++){ + stroke = strokes[strokeid] + pointcnt = stroke.length + for(pointid = 0; pointid < pointcnt; pointid++){ + x = stroke.x[pointid] + y = stroke.y[pointid] + if (x > maxX){ + maxX = x + } else if (x < minX) { + minX = x + } + if (y > maxY){ + maxY = y + } else if (y < minY) { + minY = y + } + } + } + return {'maxX': maxX, 'minX': minX, 'maxY': maxY, 'minY': minY} +} +*/ + +function conditionallyLinkCanvasResizeToWindowResize(jSignatureInstance, settingsWidth, apinamespace, globalEvents){ + 'use strict' + if ( settingsWidth === 'ratio' || settingsWidth.split('')[settingsWidth.length - 1] === '%' ) { + + this.eventTokens[apinamespace + '.parentresized'] = globalEvents.subscribe( + apinamespace + '.parentresized' + , (function(eventTokens, $parent, originalParentWidth, sizeRatio){ + 'use strict' + + return function(){ + 'use strict' + + var w = $parent.width(); + if (w !== originalParentWidth) { + + // UNsubscribing this particular instance of signature pad only. + // there is a separate `eventTokens` per each instance of signature pad + for (var key in eventTokens){ + if (eventTokens.hasOwnProperty(key)) { + globalEvents.unsubscribe(eventTokens[key]); + delete eventTokens[key]; + } + } + + var settings = jSignatureInstance.settings; + jSignatureInstance.$parent.children().remove(); + for (var key in jSignatureInstance){ + if (jSignatureInstance.hasOwnProperty(key)) { + delete jSignatureInstance[key]; + } + } + + // scale data to new signature pad size + settings.data = (function(data, scale){ + var newData = []; + var o, i, l, j, m, stroke; + for ( i = 0, l = data.length; i < l; i++) { + stroke = data[i]; + + o = {'x':[],'y':[]}; + + for ( j = 0, m = stroke.x.length; j < m; j++) { + o.x.push(stroke.x[j] * scale); + o.y.push(stroke.y[j] * scale); + } + + newData.push(o); + } + return newData; + })( + settings.data + , w * 1.0 / originalParentWidth + ) + + $parent[apinamespace](settings); + } + } + })( + this.eventTokens + , this.$parent + , this.$parent.width() + , this.canvas.width * 1.0 / this.canvas.height + ) + ) + } +}; + + +function jSignatureClass(parent, options, instanceExtensions) { + + var $parent = this.$parent = $(parent) + , eventTokens = this.eventTokens = {} + , events = this.events = new PubSubClass(this) + , globalEvents = $.fn[apinamespace]('globalEvents') + , settings = { + 'width' : 'ratio' + ,'height' : 'ratio' + ,'sizeRatio': 4 // only used when height = 'ratio' + ,'color' : '#000' + ,'background-color': '#fff' + ,'decor-color': '#eee' + ,'lineWidth' : 0 + ,'minFatFingerCompensation' : -10 + ,'showUndoButton': false + ,'readOnly': false + ,'data': [] + ,'signatureLine': false + }; + + $.extend(settings, getColors($parent)); + if (options) { + $.extend(settings, options); + } + this.settings = settings; + + for (var extensionName in instanceExtensions){ + if (instanceExtensions.hasOwnProperty(extensionName)) { + instanceExtensions[extensionName].call(this, extensionName); + } + } + + this.events.publish(apinamespace+'.initializing'); + + // these, when enabled, will hover above the sig area. Hence we append them to DOM before canvas. + this.$controlbarUpper = (function(){ + var controlbarstyle = 'padding:0 !important; margin:0 !important;'+ + 'width: 100% !important; height: 0 !important; -ms-touch-action: none; touch-action: none;'+ + 'margin-top:-1em !important; margin-bottom:1em !important;'; + return $('
    ').appendTo($parent); + })(); + + this.isCanvasEmulator = false; // will be flipped by initializer when needed. + var canvas = this.canvas = this.initializeCanvas(settings) + , $canvas = $(canvas); + + this.$controlbarLower = (function(){ + var controlbarstyle = 'padding:0 !important; margin:0 !important;'+ + 'width: 100% !important; height: 0 !important; -ms-touch-action: none; touch-action: none;'+ + 'margin-top:-1.5em !important; margin-bottom:1.5em !important; position: relative;'; + return $('
    ').appendTo($parent); + })(); + + this.canvasContext = canvas.getContext("2d"); + + // Most of our exposed API will be looking for this: + $canvas.data(apinamespace + '.this', this); + + settings.lineWidth = (function(defaultLineWidth, canvasWidth){ + if (!defaultLineWidth){ + return Math.max( + Math.round(canvasWidth / 400) /*+1 pixel for every extra 300px of width.*/ + , 2 /* minimum line width */ + ); + } else { + return defaultLineWidth; + } + })(settings.lineWidth, canvas.width); + + this.lineCurveThreshold = settings.lineWidth * 3; + + // Add custom class if defined + if(settings.cssclass && $.trim(settings.cssclass) != "") { + $canvas.addClass(settings.cssclass); + } + + // used for shifting the drawing point up on touch devices, so one can see the drawing above the finger. + this.fatFingerCompensation = 0; + + var movementHandlers = (function(jSignatureInstance) { + + //================================ + // mouse down, move, up handlers: + + // shifts - adjustment values in viewport pixels drived from position of canvas on the page + var shiftX + , shiftY + , setStartValues = function(){ + var tos = $(jSignatureInstance.canvas).offset() + shiftX = tos.left * -1 + shiftY = tos.top * -1 + } + , getPointFromEvent = function(e) { + var firstEvent = (e.changedTouches && e.changedTouches.length > 0 ? e.changedTouches[0] : e); + // All devices i tried report correct coordinates in pageX,Y + // Android Chrome 2.3.x, 3.1, 3.2., Opera Mobile, safari iOS 4.x, + // Windows: Chrome, FF, IE9, Safari + // None of that scroll shift calc vs screenXY other sigs do is needed. + // ... oh, yeah, the "fatFinger.." is for tablets so that people see what they draw. + return new Point( + Math.round(firstEvent.pageX + shiftX) + , Math.round(firstEvent.pageY + shiftY) + jSignatureInstance.fatFingerCompensation + ); + } + , timer = new KickTimerClass( + 750 + , function() { jSignatureInstance.dataEngine.endStroke(); } + ); + + this.drawEndHandler = function(e) { + if (!jSignatureInstance.settings.readOnly) { + try { e.preventDefault(); } catch (ex) {} + timer.clear(); + jSignatureInstance.dataEngine.endStroke(); + } + }; + this.drawStartHandler = function(e) { + if (!jSignatureInstance.settings.readOnly) { + e.preventDefault(); + // for performance we cache the offsets + // we recalc these only at the beginning the stroke + setStartValues(); + jSignatureInstance.dataEngine.startStroke( getPointFromEvent(e) ); + timer.kick(); + } + }; + this.drawMoveHandler = function(e) { + if (!jSignatureInstance.settings.readOnly) { + e.preventDefault(); + if (!jSignatureInstance.dataEngine.inStroke){ + return; + } + jSignatureInstance.dataEngine.addToStroke( getPointFromEvent(e) ); + timer.kick(); + } + }; + + return this; + + }).call( {}, this ) + + // + //================================ + + ;(function(drawEndHandler, drawStartHandler, drawMoveHandler) { + var canvas = this.canvas + , $canvas = $(canvas) + , undef; + if (this.isCanvasEmulator){ + $canvas.bind('mousemove.'+apinamespace, drawMoveHandler); + $canvas.bind('mouseup.'+apinamespace, drawEndHandler); + $canvas.bind('mousedown.'+apinamespace, drawStartHandler); + } else { + var hasEventListener = typeof canvas.addEventListener === 'function'; + this.ontouchstart = function(e) { + canvas.onmousedown = canvas.onmouseup = canvas.onmousemove = undef; + + this.fatFingerCompensation = ( + settings.minFatFingerCompensation && + settings.lineWidth * -3 > settings.minFatFingerCompensation + ) ? settings.lineWidth * -3 : settings.minFatFingerCompensation; + + drawStartHandler(e); + + if (hasEventListener) { + canvas.addEventListener('touchend', drawEndHandler); + canvas.addEventListener('touchstart', drawStartHandler); + canvas.addEventListener('touchmove', drawMoveHandler); + } else { + canvas.ontouchend = drawEndHandler; + canvas.ontouchstart = drawStartHandler; + canvas.ontouchmove = drawMoveHandler; + } + }; + + if (hasEventListener) { + canvas.addEventListener('touchstart', this.ontouchstart); + } else { + canvas.ontouchstart = ontouchstart; + } + + canvas.onmousedown = function(e) { + if (hasEventListener) { + canvas.removeEventListener('touchstart', this.ontouchstart); + } else { + canvas.ontouchstart = canvas.ontouchend = canvas.ontouchmove = undef; + } + + drawStartHandler(e); + + canvas.onmousedown = drawStartHandler; + canvas.onmouseup = drawEndHandler; + canvas.onmousemove = drawMoveHandler; + } + if (window.navigator.msPointerEnabled) { + canvas.onmspointerdown = drawStartHandler; + canvas.onmspointerup = drawEndHandler; + canvas.onmspointermove = drawMoveHandler; + } + } + }).call( + this + , movementHandlers.drawEndHandler + , movementHandlers.drawStartHandler + , movementHandlers.drawMoveHandler + ) + + //========================================= + // various event handlers + + // on mouseout + mouseup canvas did not know that mouseUP fired. Continued to draw despite mouse UP. + // it is bettr than + // $canvas.bind('mouseout', drawEndHandler) + // because we don't want to break the stroke where user accidentally gets ouside and wants to get back in quickly. + eventTokens[apinamespace + '.windowmouseup'] = globalEvents.subscribe( + apinamespace + '.windowmouseup' + , movementHandlers.drawEndHandler + ); + + this.events.publish(apinamespace+'.attachingEventHandlers'); + + // If we have proportional width, we sign up to events broadcasting "window resized" and checking if + // parent's width changed. If so, we (1) extract settings + data from current signature pad, + // (2) remove signature pad from parent, and (3) reinit new signature pad at new size with same settings, (rescaled) data. + conditionallyLinkCanvasResizeToWindowResize.call( + this + , this + , settings.width.toString(10) + , apinamespace, globalEvents + ); + + // end of event handlers. + // =============================== + + this.resetCanvas(settings.data); + + // resetCanvas renders the data on the screen and fires ONE "change" event + // if there is data. If you have controls that rely on "change" firing + // attach them to something that runs before this.resetCanvas, like + // apinamespace+'.attachingEventHandlers' that fires a bit higher. + this.events.publish(apinamespace+'.initialized'); + + return this; +} // end of initBase + +//========================================================================= +// jSignatureClass's methods and supporting fn's + +jSignatureClass.prototype.resetCanvas = function(data, dontClear){ + var canvas = this.canvas + , settings = this.settings + , ctx = this.canvasContext + , isCanvasEmulator = this.isCanvasEmulator + , cw = canvas.width + , ch = canvas.height; + + // preparing colors, drawing area + if (!dontClear){ + ctx.clearRect(0, 0, cw + 30, ch + 30); + } + + ctx.shadowColor = ctx.fillStyle = settings['background-color'] + if (isCanvasEmulator){ + // FLashCanvas fills with Black by default, covering up the parent div's background + // hence we refill + ctx.fillRect(0,0,cw + 30, ch + 30); + } + + ctx.lineWidth = Math.ceil(parseInt(settings.lineWidth, 10)); + ctx.lineCap = ctx.lineJoin = "round"; + + // signature line + if(settings.signatureLine) { + if (null != settings['decor-color']) { + ctx.strokeStyle = settings['decor-color']; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 0; + var lineoffset = Math.round( ch / 5 ); + basicLine(ctx, lineoffset * 1.5, ch - lineoffset, cw - (lineoffset * 1.5), ch - lineoffset); + } + + if (!isCanvasEmulator){ + ctx.shadowColor = ctx.strokeStyle; + ctx.shadowOffsetX = ctx.lineWidth * 0.5; + ctx.shadowOffsetY = ctx.lineWidth * -0.6; + ctx.shadowBlur = 0; + } + } + + ctx.strokeStyle = settings.color; + + // setting up new dataEngine + + if (!data) { data = []; } + + var dataEngine = this.dataEngine = new DataEngine( + data + , this + , strokeStartCallback + , strokeAddCallback + , strokeEndCallback + ); + + settings.data = data; // onwindowresize handler uses it, i think. + $(canvas).data(apinamespace+'.data', data) + .data(apinamespace+'.settings', settings); + + // we fire "change" event on every change in data. + // setting this up: + dataEngine.changed = (function(target, events, apinamespace) { + 'use strict' + return function() { + events.publish(apinamespace+'.change'); + target.trigger('change'); + } + })(this.$parent, this.events, apinamespace); + // let's trigger change on all data reloads + dataEngine.changed(); + + // import filters will be passing this back as indication of "we rendered" + return true; +}; + +function initializeCanvasEmulator(canvas){ + if (canvas.getContext){ + return false; + } else { + // for cases when jSignature, FlashCanvas is inserted + // from one window into another (child iframe) + // 'window' and 'FlashCanvas' may be stuck behind + // in that other parent window. + // we need to find it + var window = canvas.ownerDocument.parentWindow; + var FC = window.FlashCanvas ? + canvas.ownerDocument.parentWindow.FlashCanvas : + ( + typeof FlashCanvas === "undefined" ? + undefined : + FlashCanvas + ); + + if (FC) { + canvas = FC.initElement(canvas); + + var zoom = 1; + // FlashCanvas uses flash which has this annoying habit of NOT scaling with page zoom. + // It matches pixel-to-pixel to screen instead. + // Since we are targeting ONLY IE 7, 8 with FlashCanvas, we will test the zoom only the IE8, IE7 way + if (window && window.screen && window.screen.deviceXDPI && window.screen.logicalXDPI){ + zoom = window.screen.deviceXDPI * 1.0 / window.screen.logicalXDPI; + } + if (zoom !== 1){ + try { + // We effectively abuse the brokenness of FlashCanvas and force the flash rendering surface to + // occupy larger pixel dimensions than the wrapping, scaled up DIV and Canvas elems. + $(canvas).children('object').get(0).resize(Math.ceil(canvas.width * zoom), Math.ceil(canvas.height * zoom)); + // And by applying "scale" transformation we can talk "browser pixels" to FlashCanvas + // and have it translate the "browser pixels" to "screen pixels" + canvas.getContext('2d').scale(zoom, zoom); + // Note to self: don't reuse Canvas element. Repeated "scale" are cumulative. + } catch (ex) {} + } + return true; + } else { + throw new Error("Canvas element does not support 2d context. jSignature cannot proceed."); + } + } + +} + +jSignatureClass.prototype.initializeCanvas = function(settings) { + // =========== + // Init + Sizing code + + var canvas = document.createElement('canvas') + , $canvas = $(canvas); + + // We cannot work with circular dependency + if (settings.width === settings.height && settings.height === 'ratio') { + settings.width = '100%'; + } + + $canvas.css( + { + 'margin': 0, + 'padding': 0, + 'border': 'none', + 'height': settings.height === 'ratio' || !settings.height ? 1 : settings.height.toString(10), + 'width': settings.width === 'ratio' || !settings.width ? 1 : settings.width.toString(10), + '-ms-touch-action': 'none', + 'touch-action': 'none', + 'background-color': settings['background-color'] + } + ); + + $canvas.appendTo(this.$parent); + + // we could not do this until canvas is rendered (appended to DOM) + if (settings.height === 'ratio') { + $canvas.css( + 'height' + , Math.round( $canvas.width() / settings.sizeRatio ) + ); + } else if (settings.width === 'ratio') { + $canvas.css( + 'width' + , Math.round( $canvas.height() * settings.sizeRatio ) + ); + } + + $canvas.addClass(apinamespace); + + // canvas's drawing area resolution is independent from canvas's size. + // pixels are just scaled up or down when internal resolution does not + // match external size. So... + + canvas.width = $canvas.width(); + canvas.height = $canvas.height(); + + // Special case Sizing code + + this.isCanvasEmulator = initializeCanvasEmulator(canvas); + + // End of Sizing Code + // =========== + + // normally select preventer would be short, but + // Canvas emulator on IE does NOT provide value for Event. Hence this convoluted line. + canvas.onselectstart = function(e){if(e && e.preventDefault){e.preventDefault()}; if(e && e.stopPropagation){e.stopPropagation()}; return false;}; + + return canvas; +} + + +var GlobalJSignatureObjectInitializer = function(window){ + + var globalEvents = new PubSubClass(); + + // common "window resized" event listener. + // jSignature instances will subscribe to this chanel. + // to resize themselves when needed. + ;(function(globalEvents, apinamespace, $, window){ + 'use strict' + + var resizetimer + , runner = function(){ + globalEvents.publish( + apinamespace + '.parentresized' + ) + }; + + // jSignature knows how to resize its content when its parent is resized + // window resize is the only way we can catch resize events though... + $(window).bind('resize.'+apinamespace, function(){ + if (resizetimer) { + clearTimeout(resizetimer); + } + resizetimer = setTimeout( + runner + , 500 + ); + }) + // when mouse exists canvas element and "up"s outside, we cannot catch it with + // callbacks attached to canvas. This catches it outside. + .bind('mouseup.'+apinamespace, function(e){ + globalEvents.publish( + apinamespace + '.windowmouseup' + ) + }); + + })(globalEvents, apinamespace, $, window) + + var jSignatureInstanceExtensions = { + /* + 'exampleExtension':function(extensionName){ + // we are called very early in instance's life. + // right after the settings are resolved and + // jSignatureInstance.events is created + // and right before first ("jSignature.initializing") event is called. + // You don't really need to manupilate + // jSignatureInstance directly, just attach + // a bunch of events to jSignatureInstance.events + // (look at the source of jSignatureClass to see when these fire) + // and your special pieces of code will attach by themselves. + + // this function runs every time a new instance is set up. + // this means every var you create will live only for one instance + // unless you attach it to something outside, like "window." + // and pick it up later from there. + + // when globalEvents' events fire, 'this' is globalEvents object + // when jSignatureInstance's events fire, 'this' is jSignatureInstance + + // Here, + // this = is new jSignatureClass's instance. + + // The way you COULD approch setting this up is: + // if you have multistep set up, attach event to "jSignature.initializing" + // that attaches other events to be fired further lower the init stream. + // Or, if you know for sure you rely on only one jSignatureInstance's event, + // just attach to it directly + + this.events.subscribe( + // name of the event + apinamespace + '.initializing' + // event handlers, can pass args too, but in majority of cases, + // 'this' which is jSignatureClass object instance pointer is enough to get by. + , function(){ + if (this.settings.hasOwnProperty('non-existent setting category?')) { + console.log(extensionName + ' is here') + } + } + ) + } + */ + }; + + var exportplugins = { + 'default':function(data){return this.toDataURL()} + , 'native':function(data){return data} + , 'image':function(data){ + /*this = canvas elem */ + var imagestring = this.toDataURL(); + + if (typeof imagestring === 'string' && + imagestring.length > 4 && + imagestring.slice(0,5) === 'data:' && + imagestring.indexOf(',') !== -1){ + + var splitterpos = imagestring.indexOf(','); + + return [ + imagestring.slice(5, splitterpos) + , imagestring.substr(splitterpos + 1) + ]; + } + return []; + } + }; + + // will be part of "importplugins" + function _renderImageOnCanvas( data, formattype, rerendercallable ) { + 'use strict' + // #1. Do NOT rely on this. No worky on IE + // (url max len + lack of base64 decoder + possibly other issues) + // #2. This does NOT affect what is captured as "signature" as far as vector data is + // concerned. This is treated same as "signature line" - i.e. completely ignored + // the only time you see imported image data exported is if you export as image. + + // we do NOT call rerendercallable here (unlike in other import plugins) + // because importing image does absolutely nothing to the underlying vector data storage + // This could be a way to "import" old signatures stored as images + // This could also be a way to import extra decor into signature area. + + var img = new Image() + // this = Canvas DOM elem. Not jQuery object. Not Canvas's parent div. + , c = this; + + img.onload = function () { + var ctx = c.getContext("2d"); + var oldShadowColor = ctx.shadowColor; + ctx.shadowColor = "transparent"; + ctx.drawImage( + img, 0, 0 + , ( img.width < c.width) ? img.width : c.width + , ( img.height < c.height) ? img.height : c.height + ); + ctx.shadowColor = oldShadowColor; + }; + + img.src = 'data:' + formattype + ',' + data; + } + + var importplugins = { + 'native':function(data, formattype, rerendercallable){ + // we expect data as Array of objects of arrays here - whatever 'default' EXPORT plugin spits out. + // returning Truthy to indicate we are good, all updated. + rerendercallable( data ); + } + , 'image': _renderImageOnCanvas + , 'image/png;base64': _renderImageOnCanvas + , 'image/jpeg;base64': _renderImageOnCanvas + , 'image/jpg;base64': _renderImageOnCanvas + }; + + function _clearDrawingArea( data, dontClear ) { + this.find('canvas.'+apinamespace) + .add(this.filter('canvas.'+apinamespace)) + .data(apinamespace+'.this').resetCanvas( data, dontClear ); + return this; + } + + function _setDrawingData( data, formattype ) { + var undef; + + if (formattype === undef && typeof data === 'string' && data.substr(0,5) === 'data:') { + formattype = data.slice(5).split(',')[0]; + // 5 chars of "data:" + mimetype len + 1 "," char = all skipped. + data = data.slice(6 + formattype.length); + if (formattype === data) { + return; + } + } + + var $canvas = this.find('canvas.'+apinamespace).add(this.filter('canvas.'+apinamespace)); + + if (!importplugins.hasOwnProperty(formattype)) { + throw new Error(apinamespace + " is unable to find import plugin with for format '"+ String(formattype) +"'"); + } else if ($canvas.length !== 0) { + importplugins[formattype].call( + $canvas[0] + , data + , formattype + , (function(jSignatureInstance){ + return function(){ return jSignatureInstance.resetCanvas.apply(jSignatureInstance, arguments) } + })($canvas.data(apinamespace+'.this')) + ); + } + + return this; + } + + var elementIsOrphan = function(e){ + var topOfDOM = false; + e = e.parentNode; + while (e && !topOfDOM){ + topOfDOM = e.body; + e = e.parentNode; + } + return !topOfDOM; + } + + //These are exposed as methods under $obj.jSignature('methodname', *args) + var plugins = {'export':exportplugins, 'import':importplugins, 'instance': jSignatureInstanceExtensions} + , methods = { + 'init' : function( options ) { + return this.each( function() { + if (!elementIsOrphan(this)) { + new jSignatureClass(this, options, jSignatureInstanceExtensions); + } + }) + } + , 'destroy': function() { + return this.each(function() { + if(!elementIsOrphan(this)) { + var sig = $(this).find('canvas').data(apinamespace + '.this'); + if(sig) { + sig.$controlbarLower.remove(); + sig.$controlbarUpper.remove(); + $(sig.canvas).remove(); + for (var e in sig.eventTokens){ + if (sig.eventTokens.hasOwnProperty(e)){ + globalEvents.unsubscribe(sig.eventTokens[e]); + } + } + } + } + }); + } + , 'getSettings' : function() { + return this.find('canvas.'+apinamespace) + .add(this.filter('canvas.'+apinamespace)) + .data(apinamespace+'.this').settings; + } + , 'isModified' : function() { + return this.find('canvas.'+apinamespace) + .add(this.filter('canvas.'+apinamespace)) + .data(apinamespace+'.this') + .dataEngine + ._stroke !== null; + } + , 'updateSetting' : function(param, val, forFuture) { + var $canvas = this.find('canvas.'+apinamespace) + .add(this.filter('canvas.'+apinamespace)) + .data(apinamespace+'.this'); + $canvas.settings[param] = val; + $canvas.resetCanvas(( forFuture ? null : $canvas.settings.data ), true); + return $canvas.settings[param]; + } + // around since v1 + , 'clear' : _clearDrawingArea + // was mistakenly introduced instead of 'clear' in v2 + , 'reset' : _clearDrawingArea + , 'addPlugin' : function(pluginType, pluginName, callable){ + if (plugins.hasOwnProperty(pluginType)){ + plugins[pluginType][pluginName] = callable; + } + return this; + } + , 'listPlugins' : function(pluginType){ + var answer = []; + if (plugins.hasOwnProperty(pluginType)){ + var o = plugins[pluginType]; + for (var k in o){ + if (o.hasOwnProperty(k)){ + answer.push(k); + } + } + } + return answer; + } + , 'getData' : function( formattype ) { + var undef, $canvas=this.find('canvas.'+apinamespace).add(this.filter('canvas.'+apinamespace)); + if (formattype === undef) { + formattype = 'default'; + } + if ($canvas.length !== 0 && exportplugins.hasOwnProperty(formattype)){ + return exportplugins[formattype].call( + $canvas.get(0) // canvas dom elem + , $canvas.data(apinamespace+'.data') // raw signature data as array of objects of arrays + , $canvas.data(apinamespace+'.settings') + ); + } + } + // around since v1. Took only one arg - data-url-formatted string with (likely png of) signature image + , 'importData' : _setDrawingData + // was mistakenly introduced instead of 'importData' in v2 + , 'setData' : _setDrawingData + // this is one and same instance for all jSignature. + , 'globalEvents' : function(){return globalEvents} + , 'disable' : function() { + this.find("input").attr("disabled", 1); + this.find('canvas.'+apinamespace) + .addClass("disabled") + .data(apinamespace+'.this') + .settings + .readOnly=true; + } + , 'enable' : function() { + this.find("input").removeAttr("disabled"); + this.find('canvas.'+apinamespace) + .removeClass("disabled") + .data(apinamespace+'.this') + .settings + .readOnly=false; + } + // there will be a separate one for each jSignature instance. + , 'events' : function() { + return this.find('canvas.'+apinamespace) + .add(this.filter('canvas.'+apinamespace)) + .data(apinamespace+'.this').events; + } + } // end of methods declaration. + + $.fn[apinamespace] = function(method) { + 'use strict' + if ( !method || typeof method === 'object' ) { + return methods.init.apply( this, arguments ); + } else if ( typeof method === 'string' && methods[method] ) { + return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 )); + } else { + $.error( 'Method ' + String(method) + ' does not exist on jQuery.' + apinamespace ); + } + } + +} // end of GlobalJSignatureObjectInitializer + +GlobalJSignatureObjectInitializer(window) + +})(); diff --git a/src/main/ui/static/lib/jSignature/jquery.js b/src/main/ui/static/lib/jSignature/jquery.js new file mode 100644 index 000000000..ee0233703 --- /dev/null +++ b/src/main/ui/static/lib/jSignature/jquery.js @@ -0,0 +1,4 @@ +/*! jQuery v1.7.1 jquery.com | jquery.org/license */ +(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!ck[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){cl||(cl=c.createElement("iframe"),cl.frameBorder=cl.width=cl.height=0),b.appendChild(cl);if(!cm||!cl.createElement)cm=(cl.contentWindow||cl.contentDocument).document,cm.write((c.compatMode==="CSS1Compat"?"":"")+""),cm.close();d=cm.createElement(a),cm.body.appendChild(d),e=f.css(d,"display"),b.removeChild(cl)}ck[a]=e}return ck[a]}function cu(a,b){var c={};f.each(cq.concat.apply([],cq.slice(0,b)),function(){c[this]=a});return c}function ct(){cr=b}function cs(){setTimeout(ct,0);return cr=f.now()}function cj(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ci(){try{return new a.XMLHttpRequest}catch(b){}}function cc(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){if(c!=="border")for(;g=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?parseFloat(d):j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.1",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c
    a",d=q.getElementsByTagName("*"),e=q.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=q.getElementsByTagName("input")[0],b={leadingWhitespace:q.firstChild.nodeType===3,tbody:!q.getElementsByTagName("tbody").length,htmlSerialize:!!q.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:q.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete q.test}catch(s){b.deleteExpando=!1}!q.addEventListener&&q.attachEvent&&q.fireEvent&&(q.attachEvent("onclick",function(){b.noCloneEvent=!1}),q.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),q.appendChild(i),k=c.createDocumentFragment(),k.appendChild(q.lastChild),b.checkClone=k.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,k.removeChild(i),k.appendChild(q),q.innerHTML="",a.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",q.style.width="2px",q.appendChild(j),b.reliableMarginRight=(parseInt((a.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0);if(q.attachEvent)for(o in{submit:1,change:1,focusin:1})n="on"+o,p=n in q,p||(q.setAttribute(n,"return;"),p=typeof q[n]=="function"),b[o+"Bubbles"]=p;k.removeChild(q),k=g=h=j=q=i=null,f(function(){var a,d,e,g,h,i,j,k,m,n,o,r=c.getElementsByTagName("body")[0];!r||(j=1,k="position:absolute;top:0;left:0;width:1px;height:1px;margin:0;",m="visibility:hidden;border:0;",n="style='"+k+"border:5px solid #000;padding:0;'",o="
    "+""+"
    ",a=c.createElement("div"),a.style.cssText=m+"width:0;height:0;position:static;top:0;margin-top:"+j+"px",r.insertBefore(a,r.firstChild),q=c.createElement("div"),a.appendChild(q),q.innerHTML="
    t
    ",l=q.getElementsByTagName("td"),p=l[0].offsetHeight===0,l[0].style.display="",l[1].style.display="none",b.reliableHiddenOffsets=p&&l[0].offsetHeight===0,q.innerHTML="",q.style.width=q.style.paddingLeft="1px",f.boxModel=b.boxModel=q.offsetWidth===2,typeof q.style.zoom!="undefined"&&(q.style.display="inline",q.style.zoom=1,b.inlineBlockNeedsLayout=q.offsetWidth===2,q.style.display="",q.innerHTML="
    ",b.shrinkWrapBlocks=q.offsetWidth!==2),q.style.cssText=k+m,q.innerHTML=o,d=q.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,i={doesNotAddBorder:e.offsetTop!==5,doesAddBorderForTableAndCells:h.offsetTop===5},e.style.position="fixed",e.style.top="20px",i.fixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",i.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,i.doesNotIncludeMarginInBodyOffset=r.offsetTop!==j,r.removeChild(a),q=a=null,f.extend(b,i))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.nodeName.toLowerCase()]||f.valHooks[g.type];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;h=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/\bhover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function(a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")}; +f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;le&&i.push({elem:this,matches:d.slice(e)});for(j=0;j0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

    ";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
    ";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h0)for(h=g;h=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/",""],legend:[1,"
    ","
    "],thead:[1,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],col:[2,"","
    "],area:[1,"",""],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div
    ","
    "]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function() +{for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1>");try{for(var c=0,d=this.length;c1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||!bc.test("<"+a.nodeName)?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!_.test(k))k=b.createTextNode(k);else{k=k.replace(Y,"<$1>");var l=(Z.exec(k)||["",""])[1].toLowerCase(),m=bg[l]||bg._default,n=m[0],o=b.createElement("div");b===c?bh.appendChild(o):U(b).appendChild(o),o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=$.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]===""&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&X.test(k)&&o.insertBefore(b.createTextNode(X.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return br.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bq,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bq.test(g)?g.replace(bq,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bz(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bA=function(a,b){var c,d,e;b=b.replace(bs,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b)));return c}),c.documentElement.currentStyle&&(bB=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f===null&&g&&(e=g[b])&&(f=e),!bt.test(f)&&bu.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f||0,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),bz=bA||bB,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bD=/%20/g,bE=/\[\]$/,bF=/\r?\n/g,bG=/#.*$/,bH=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bI=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bJ=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bK=/^(?:GET|HEAD)$/,bL=/^\/\//,bM=/\?/,bN=/)<[^<]*)*<\/script>/gi,bO=/^(?:select|textarea)/i,bP=/\s+/,bQ=/([?&])_=[^&]*/,bR=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bS=f.fn.load,bT={},bU={},bV,bW,bX=["*/"]+["*"];try{bV=e.href}catch(bY){bV=c.createElement("a"),bV.href="",bV=bV.href}bW=bR.exec(bV.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bS)return bS.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
    ").append(c.replace(bN,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bO.test(this.nodeName)||bI.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bF,"\r\n")}}):{name:b.name,value:c.replace(bF,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b_(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b_(a,b);return a},ajaxSettings:{url:bV,isLocal:bJ.test(bW[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bX},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bZ(bT),ajaxTransport:bZ(bU),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?cb(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cc(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bH.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bG,"").replace(bL,bW[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bP),d.crossDomain==null&&(r=bR.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bW[1]&&r[2]==bW[2]&&(r[3]||(r[1]==="http:"?80:443))==(bW[3]||(bW[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),b$(bT,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bK.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bM.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bQ,"$1_="+x);d.url=y+(y===d.url?(bM.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bX+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=b$(bU,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)ca(g,a[g],c,e);return d.join("&").replace(bD,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cd=f.now(),ce=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cd++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ce.test(b.url)||e&&ce.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ce,l),b.url===j&&(e&&(k=k.replace(ce,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cf=a.ActiveXObject?function(){for(var a in ch)ch[a](0,1)}:!1,cg=0,ch;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ci()||cj()}:ci,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cf&&delete ch[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cg,cf&&(ch||(ch={},f(a).unload(cf)),ch[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var ck={},cl,cm,cn=/^(?:toggle|show|hide)$/,co=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cp,cq=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cr;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cu("show",3),a,b,c);for(var g=0,h=this.length;g=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cy(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cy(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,d,"padding")):this[d]():null},f.fn["outer"+c]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,d,a?"margin":"border")):this[d]():null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c],h=e.document.body;return e.document.compatMode==="CSS1Compat"&&g||h&&h["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var i=f.css(e,d),j=parseFloat(i);return f.isNumeric(j)?j:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window); \ No newline at end of file diff --git a/src/main/ui/static/lib/jSignature/modernizr.js b/src/main/ui/static/lib/jSignature/modernizr.js new file mode 100644 index 000000000..89fddeb8e --- /dev/null +++ b/src/main/ui/static/lib/jSignature/modernizr.js @@ -0,0 +1,4 @@ +/* Modernizr 2.5.2 (Custom Build) | MIT & BSD + * Build: http://www.modernizr.com/download/#-borderradius-csscolumns-canvas-touch-mq-cssclasses-addtest-teststyles-testprop-testallprops-prefixes-domprefixes-fullscreen_api + */ +;window.Modernizr=function(a,b,c){function A(a){j.cssText=a}function B(a,b){return A(m.join(a+";")+(b||""))}function C(a,b){return typeof a===b}function D(a,b){return!!~(""+a).indexOf(b)}function E(a,b){for(var d in a)if(j[a[d]]!==c)return b=="pfx"?a[d]:!0;return!1}function F(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:C(f,"function")?f.bind(d||b):f}return!1}function G(a,b,c){var d=a.charAt(0).toUpperCase()+a.substr(1),e=(a+" "+o.join(d+" ")+d).split(" ");return C(b,"string")||C(b,"undefined")?E(e,b):(e=(a+" "+p.join(d+" ")+d).split(" "),F(e,b,c))}var d="2.5.2",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k,l={}.toString,m=" -webkit- -moz- -o- -ms- ".split(" "),n="Webkit Moz O ms",o=n.split(" "),p=n.toLowerCase().split(" "),q={},r={},s={},t=[],u=t.slice,v,w=function(a,c,d,e){var f,i,j,k=b.createElement("div"),l=b.body,m=l?l:b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),k.appendChild(j);return f=["­",""].join(""),k.id=h,m.innerHTML+=f,m.appendChild(k),l||g.appendChild(m),i=c(k,a),l?k.parentNode.removeChild(k):m.parentNode.removeChild(m),!!i},x=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return w("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},y={}.hasOwnProperty,z;!C(y,"undefined")&&!C(y.call,"undefined")?z=function(a,b){return y.call(a,b)}:z=function(a,b){return b in a&&C(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=u.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(u.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(u.call(arguments)))};return e});var H=function(c,d){var f=c.join(""),g=d.length;w(f,function(c,d){var f=b.styleSheets[b.styleSheets.length-1],h=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"",i=c.childNodes,j={};while(g--)j[i[g].id]=i[g];e.touch="ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch||(j.touch&&j.touch.offsetTop)===9},g,d)}([,["@media (",m.join("touch-enabled),("),h,")","{#touch{top:9px;position:absolute}}"].join("")],[,"touch"]);q.canvas=function(){var a=b.createElement("canvas");return!!a.getContext&&!!a.getContext("2d")},q.touch=function(){return e.touch},q.borderradius=function(){return G("borderRadius")},q.csscolumns=function(){return G("columnCount")};for(var I in q)z(q,I)&&(v=I.toLowerCase(),e[v]=q[I](),t.push((e[v]?"":"no-")+v));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)z(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,g.className+=" "+(b?"":"no-")+a,e[a]=b}return e},A(""),i=k=null,e._version=d,e._prefixes=m,e._domPrefixes=p,e._cssomPrefixes=o,e.mq=x,e.testProp=function(a){return E([a])},e.testAllProps=G,e.testStyles=w,g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+t.join(" "):""),e}(this,this.document),Modernizr.addTest("fullscreen",function(){for(var a=0;a
    + +
    diff --git a/src/main/ui/static/payment/partner/partner-apply.js b/src/main/ui/static/payment/partner/partner-apply.js index c936abe44..cc1e1c034 100644 --- a/src/main/ui/static/payment/partner/partner-apply.js +++ b/src/main/ui/static/payment/partner/partner-apply.js @@ -93,7 +93,6 @@ define(['angular', 'static/commons/commons', 'uiBootstrap', 'uiRouter', 'ngBootS } }]); - app.controller('partnerApplicationDetailCtrl', ['$rootScope', '$scope', '$http', '$state', '$uibModal', 'commonDialog', 'partner', function ($rootScope, $scope, $http, $state, $uibModal, commonDialog, partner) { $scope.partner = partner.data; @@ -226,4 +225,4 @@ define(['angular', 'static/commons/commons', 'uiBootstrap', 'uiRouter', 'ngBootS }; }); return app; -}); \ No newline at end of file +}); diff --git a/src/main/ui/static/payment/partner/partner-manage.js b/src/main/ui/static/payment/partner/partner-manage.js index 9e5dae172..da8781f80 100644 --- a/src/main/ui/static/payment/partner/partner-manage.js +++ b/src/main/ui/static/payment/partner/partner-manage.js @@ -104,6 +104,10 @@ define(['angular', 'decimal', 'static/commons/commons', 'uiBootstrap', 'uiRouter url: '/partners/compliance', templateUrl: '/static/payment/partner/templates/partner_compliance.html', controller: 'compliancePartnerCtrl' + }).state('complianceDocumentAudit', { + url: '/partners/compliance', + templateUrl: '/static/payment/partner/templates/partner_compliance.html', + controller: 'compliancePartnerCtrl' }).state('partners.detail', { url: '/{clientMoniker}/detail', templateUrl: '/static/payment/partner/templates/partner_detail.html', @@ -3811,6 +3815,25 @@ define(['angular', 'decimal', 'static/commons/commons', 'uiBootstrap', 'uiRouter return url; }; + $scope.deleteComplianceFiles = function (file_id) { + commonDialog.confirm({ + title: 'Warning', + content: 'This operation will delete the file, Are you sure?' + }).then(function () { + $http.put('/sys/partners/auth_file/' + file_id + '/delete').then(function (resp) { + commonDialog.alert({ + title: 'Success', + content: 'Delete Successful', + type: 'success' + }); + $state.reload(); + }, function (resp) { + commonDialog.alert({title: 'Error', content: resp.data.message, type: 'error'}); + }) + }) +}; + + $scope.updateFile = function () { $http.put('/sys/partners/' + $scope.partner.client_moniker + '/file', $scope.file).then(function () { commonDialog.alert({ diff --git a/src/main/ui/static/payment/partner/partner.js b/src/main/ui/static/payment/partner/partner.js index b13eab1ba..a6e84e3fd 100644 --- a/src/main/ui/static/payment/partner/partner.js +++ b/src/main/ui/static/payment/partner/partner.js @@ -2,9 +2,9 @@ * partner info in partner client * Created by yixian on 2016-07-03. */ -define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBootstrap'], function (angular, Decimal) { +define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload', 'uiBootstrap'], function (angular, Decimal) { 'use strict'; - var app = angular.module('partnerInfoApp', ['ui.router', 'frapontillo.bootstrap-switch', 'ngFileUpload','ui.bootstrap']); + var app = angular.module('partnerInfoApp', ['ui.router', 'frapontillo.bootstrap-switch', 'ngFileUpload', 'ui.bootstrap']); app.config(['$stateProvider', function ($stateProvider) { $stateProvider.state('basic', { url: '/basic', @@ -37,6 +37,22 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot return $http.get('/client/partner_info/compliance/files'); }] } + }).state('compliance_to_perfect', { + url: '/compliance_to_perfect', + templateUrl: '/static/payment/partner/templates/client_compliance_to_perfect.html', + controller: 'clientCommitToComplianceFilesCtrl', + resolve: { + file: ['$http', function ($http) { + return $http.get('/client/partner_info/compliance/clientViewFiles'); + }], + partner: ['$http', function ($http) { + return $http.get('/client/partner_info'); + }] + } + }).state('compliance_contract', { + url: '/compliance/contract', + templateUrl: '/static/payment/partner/templates/contract_for_compliance.html', + controller: 'aggregateFileCtrl' }).state('basic.clearing_config', { url: '/clearing_config', templateUrl: '/static/payment/partner/templates/client_bankaccounts.html', @@ -60,7 +76,7 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot controller: 'clientDeviceCtrl' }) }]); - app.controller('clientPartnerDetailCtrl', ['$scope', '$http', 'partner','industryMap','businessStructuresMap','commonDialog','Upload','$state', function ($scope, $http, partner,industryMap,businessStructuresMap,commonDialog,Upload,$state) { + app.controller('clientPartnerDetailCtrl', ['$scope', '$http', 'partner', 'industryMap', 'businessStructuresMap', 'commonDialog', 'Upload', '$state', function ($scope, $http, partner, industryMap, businessStructuresMap, commonDialog, Upload, $state) { $scope.business_structures = businessStructuresMap.configs(); $scope.industries = industryMap.configs(); $scope.partner = partner.data; @@ -143,7 +159,7 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot var removeClientPayDesc = function (items, key) { for (var i = 0; i < items.length; i++) { var item = items[i]; - if (item.indexOf(key)>=0) { + if (item.indexOf(key) >= 0) { items.splice(items.indexOf(item), 1); i = i - 1; } @@ -157,7 +173,7 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot var $idx = $scope.partner.client_pay_desc.indexOf(type); if ($idx >= 0) { if (type == '203') { - removeClientPayDesc($scope.partner.client_pay_desc,'2030') + removeClientPayDesc($scope.partner.client_pay_desc, '2030') } $scope.partner.client_pay_desc.splice($idx, 1); } else { @@ -201,7 +217,7 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot alert("Logo is necessary!"); return; } - if($scope.partner.partner_type == 'photo'){ + if ($scope.partner.partner_type == 'photo') { if (!$scope.partner.company_photo) { alert('Shop Photo1 is necessary'); return; @@ -213,11 +229,11 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot } var content = ''; - if ($scope.partner.client_pay_type.length==0) { + if ($scope.partner.client_pay_type.length == 0) { alert('请选择商户支付场景') return; } - if ($scope.partner.client_pay_desc.length==0) { + if ($scope.partner.client_pay_desc.length == 0) { alert('请选择商户支付方式') return; } @@ -227,7 +243,7 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot return; } } - if ( $scope.partner.client_pay_type.indexOf('2') >= 0) { + if ($scope.partner.client_pay_type.indexOf('2') >= 0) { if ($scope.partner.client_pay_desc.join(',').indexOf('20') < 0) { alert("请检查线下支付场景是否已选择支付方式"); return; @@ -262,27 +278,27 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot }; //修改邮箱 $scope.updateEmail = function () { - $http.put('/client/partner_info/compliance_audit').then( + $http.put('/client/partner_info/complianceAudit').then( ); }; $scope.commitToCompliance = function () { $http.get('/client/partner_info/compliance/files').then(function (resp) { $scope.complianceFiles = resp.data; - if($scope.complianceFiles.client_id_file == null||$scope.complianceFiles.client_bank_file == null || $scope.complianceFiles.client_company_file == null){ + if ($scope.complianceFiles.client_id_file == null || $scope.complianceFiles.client_bank_file == null || $scope.complianceFiles.client_company_file == null) { commonDialog.alert({title: 'Message', content: '请前去完善合规资料,再进行提交!', type: 'info'}); return; } - if($scope.partner.business_structure == null ||$scope.partner.logo_url == null || $scope.partner.description == null ||($scope.partner.partner_type == 'website' && $scope.partner.company_website == null) || ($scope.partner.partner_type == 'photo'&&($scope.partner.store_photo == null || $scope.partner.company_photo == null))){ + if ($scope.partner.business_structure == null || $scope.partner.logo_url == null || $scope.partner.description == null || ($scope.partner.partner_type == 'website' && $scope.partner.company_website == null) || ($scope.partner.partner_type == 'photo' && ($scope.partner.store_photo == null || $scope.partner.company_photo == null))) { commonDialog.alert({title: 'Message', content: '请前去完善商户资料,再进行提交!', type: 'info'}); return; } - if(($scope.partner.business_structure == "Company" && $scope.partner.acn == null) || ($scope.partner.business_structure != "Company" && $scope.partner.abn == null)){ + if (($scope.partner.business_structure == "Company" && $scope.partner.acn == null) || ($scope.partner.business_structure != "Company" && $scope.partner.abn == null)) { commonDialog.alert({title: 'Message', content: '请前去完善商户资料,再进行提交!', type: 'info'}); return; } - if(!$scope.partner.mail_confirm){ + if (!$scope.partner.mail_confirm) { commonDialog.alert({title: 'Message', content: '请验证邮箱后,再进行提交!', type: 'info'}); return; } @@ -296,7 +312,7 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot }).then(function (choice) { $scope.submitted = true; if (choice == 1) { - $http.post('/client/partner_info/compliance_audit').then(function (){ + $http.post('/client/partner_info/complianceAudit').then(function () { commonDialog.alert({title: 'Success', content: '已提交至合规,请耐心等待审核!', type: 'info'}); $state.reload(); }, function () { @@ -307,7 +323,7 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot }); } }]); - app.controller('clientPartnerEditCtrl', ['$scope', '$http', 'commonDialog','stateMap','countryMap','partner','$state','Upload',function ($scope, $http, commonDialog,stateMap,countryMap,partner,$state,Upload) { + app.controller('clientPartnerEditCtrl', ['$scope', '$http', 'commonDialog', 'stateMap', 'countryMap', 'partner', '$state', 'Upload', function ($scope, $http, commonDialog, stateMap, countryMap, partner, $state, Upload) { $scope.partner = partner.data; $scope.states = stateMap.configs(); $scope.countries = countryMap.configs(); @@ -405,7 +421,7 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot var removeClientPayDesc = function (items, key) { for (var i = 0; i < items.length; i++) { var item = items[i]; - if (item.indexOf(key)>=0) { + if (item.indexOf(key) >= 0) { items.splice(items.indexOf(item), 1); i = i - 1; } @@ -419,7 +435,7 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot var $idx = $scope.partner.client_pay_desc.indexOf(type); if ($idx >= 0) { if (type == '203') { - removeClientPayDesc($scope.partner.client_pay_desc,'2030') + removeClientPayDesc($scope.partner.client_pay_desc, '2030') } $scope.partner.client_pay_desc.splice($idx, 1); } else { @@ -441,7 +457,7 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot alert("Logo is necessary!"); return; } - if($scope.partner.partner_type == 'photo'){ + if ($scope.partner.partner_type == 'photo') { if (!$scope.partner.company_photo) { alert('Shop Photo1 is necessary'); return; @@ -453,11 +469,11 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot } var content = ''; - if ($scope.partner.client_pay_type.length==0) { + if ($scope.partner.client_pay_type.length == 0) { alert('请选择商户支付场景') return; } - if ($scope.partner.client_pay_desc.length==0) { + if ($scope.partner.client_pay_desc.length == 0) { alert('请选择商户支付方式') return; } @@ -467,7 +483,7 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot return; } } - if ( $scope.partner.client_pay_type.indexOf('2') >= 0) { + if ($scope.partner.client_pay_type.indexOf('2') >= 0) { if ($scope.partner.client_pay_desc.join(',').indexOf('20') < 0) { alert("请检查线下支付场景是否已选择支付方式"); return; @@ -502,26 +518,26 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot }; }]); - app.controller('clientResetPartnerPublicKeyDialogCtrl', ['$scope', '$http', 'gateway_sign', function ($scope, $http,gateway_sign) { + app.controller('clientResetPartnerPublicKeyDialogCtrl', ['$scope', '$http', 'gateway_sign', function ($scope, $http, gateway_sign) { $scope.gateway_sign = angular.copy(gateway_sign); $scope.uploadPublicKey = function (mch_public_key) { $scope.errmsg = null; - $http.put('/client/partner_info/partner_public_key', {partner_public_key:mch_public_key}).then(function () { + $http.put('/client/partner_info/partner_public_key', {partner_public_key: mch_public_key}).then(function () { $scope.$close(); }, function (resp) { $scope.errmsg = resp.data.message; }) } }]); - app.controller('clientRefreshPlatformPublicKeyDialogCtrl', ['$scope', '$http', 'gateway_sign','commonDialog', function ($scope, $http,gateway_sign,commonDialog) { + app.controller('clientRefreshPlatformPublicKeyDialogCtrl', ['$scope', '$http', 'gateway_sign', 'commonDialog', function ($scope, $http, gateway_sign, commonDialog) { $scope.gateway_sign = angular.copy(gateway_sign); - $scope.copyPublicKey = function() { - var e=document.getElementById("c-cpKey"); + $scope.copyPublicKey = function () { + var e = document.getElementById("c-cpKey"); e.select(); var successful = document.execCommand("Copy"); if (successful) { commonDialog.alert({title: 'Success', content: '已复制到剪切板!', type: 'success'}); - }else { + } else { commonDialog.alert({title: 'Error', content: '您的浏览器不支持!请手动复制', type: 'error'}); } }; @@ -711,11 +727,11 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot } }; - /* $scope.downloadAsZip = function () { - var url = '/sys/partners/' + $scope.partner.client_moniker + '/download/complianceAsZIP'; - return url; - }; -*/ + /* $scope.downloadAsZip = function () { + var url = '/sys/partners/' + $scope.partner.client_moniker + '/download/complianceAsZIP'; + return url; + }; + */ $scope.updateFile = function () { $http.put('/client/partner_info/update/file', $scope.file).then(function () { commonDialog.alert({ @@ -739,7 +755,268 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot $scope.commitPartner = function () { if ($scope.file) { if ($scope.file.file_bank_info != null && $scope.file.file_company_info != null && $scope.file.file_id_info != null && $scope.file_apply_info != null) { - $http.put('/client/partner_info/compliance_audit').then(function (resp) { + $http.put('/client/partner_info/complianceAudit').then(function (resp) { + + }); + } else { + commitError(); + } + } else { + commitError(); + } + }; + }]); + app.controller('clientCommitToComplianceFilesCtrl', ['$scope', '$http', '$rootScope', 'commonDialog', '$state', 'Upload', 'file','partner', function ($scope, $http, $rootScope, commonDialog, $state, Upload, file, partner) { + $scope.file = file.data || {}; + $scope.partner = partner.data || {}; + //audit files + $scope.uploadBankFile = function (file) { + if (file != null) { + if (file.size > 3 * 1024 * 1024) { + commonDialog.alert({title: 'Error', content: '文件大小不能超过3MB,请压缩后重试', type: 'error'}) + } else { + $scope.bankFileProgress = {value: 0}; + Upload.upload({ + url: '/attachment/files', + data: {file: file} + }).then(function (resp) { + delete $scope.bankFileProgress; + $scope.file.file_bank_info = resp.data.url; + $scope.updateFile(); + if ($scope.file.file_bank_info.endsWith('pdf')) { + $scope.bankIsImage = false; + } else { + $scope.bankIsImage = true; + } + }, function (resp) { + delete $scope.bankFileProgress; + commonDialog.alert({title: 'Upload Failed', content: resp.data.message, type: 'error'}) + }, function (evt) { + $scope.bankFileProgress.value = parseInt(100 * evt.loaded / evt.total); + }) + } + } + }; + $scope.agreeIsImage = true; + if ($scope.file.file_agreement_info && $scope.file.file_agreement_info.endsWith('pdf')) { + $scope.agreeIsImage = false; + } + $scope.bankIsImage = true; + if ($scope.file.file_bank_info && $scope.file.file_bank_info.endsWith('pdf')) { + $scope.bankIsImage = false; + } + $scope.companyIsImage = true; + if ($scope.file.file_company_info && $scope.file.file_company_info.endsWith('pdf')) { + $scope.companyIsImage = false; + } + $scope.applyIsImage = true; + if ($scope.file.file_apply_info && $scope.file.file_apply_info.endsWith('pdf')) { + $scope.applyIsImage = false; + } + $scope.idIsImage = true; + if ($scope.file.file_id_info && $scope.file.file_id_info.endsWith('pdf')) { + $scope.idIsImage = false; + } + + $scope.uploadCompanyFile = function (file) { + if (file != null) { + if (file.size > 3 * 1024 * 1024) { + commonDialog.alert({title: 'Error', content: '文件大小不能超过3MB,请压缩后重试', type: 'error'}) + } else { + $scope.companyFileProgress = {value: 0}; + Upload.upload({ + url: '/attachment/files', + data: {file: file} + }).then(function (resp) { + delete $scope.companyFileProgress; + $scope.file.file_company_info = resp.data.url; + $scope.updateFile(); + if ($scope.file.file_company_info.endsWith('pdf')) { + $scope.companyIsImage = false; + } else { + $scope.companyIsImage = true; + } + }, function (resp) { + delete $scope.companyFileProgress; + commonDialog.alert({title: 'Upload Failed', content: resp.data.message, type: 'error'}) + }, function (evt) { + $scope.companyFileProgress.value = parseInt(100 * evt.loaded / evt.total); + }) + } + } + }; + //上传ID信息 + $scope.uploadIDFile = function (file) { + if (file != null) { + if (file.size > 3 * 1024 * 1024) { + commonDialog.alert({title: 'Error', content: '文件大小不能超过3MB,请压缩后重试', type: 'error'}) + } else { + $scope.idFileProgress = {value: 0}; + Upload.upload({ + url: '/attachment/files', + data: {file: file} + }).then(function (resp) { + delete $scope.idFileProgress; + $scope.file.file_id_info = resp.data.url; + $scope.updateFile(); + if ($scope.file.file_id_info.endsWith('pdf')) { + $scope.idIsImage = false; + } else { + $scope.idIsImage = true; + } + }, function (resp) { + delete $scope.idFileProgress; + commonDialog.alert({title: 'Upload Failed', content: resp.data.message, type: 'error'}) + }, function (evt) { + $scope.idFileProgress.value = parseInt(100 * evt.loaded / evt.total); + }) + } + } + }; + + //上传协议文件 + $scope.uploadAgreementFile = function (file) { + if (file != null) { + if (file.size > 10 * 1024 * 1024) { + commonDialog.alert({title: 'Error', content: '文件大小不能超过5MB,请压缩后重试', type: 'error'}) + } else { + $scope.agreementFileProgress = {value: 0}; + Upload.upload({ + url: '/attachment/files', + data: {file: file} + }).then(function (resp) { + delete $scope.agreementFileProgress; + $scope.file.file_agreement_info = resp.data.url; + $scope.updateFile(); + if ($scope.file.file_agreement_info.endsWith('pdf')) { + $scope.agreeIsImage = false; + } else { + $scope.agreeIsImage = true; + } + }, function (resp) { + delete $scope.agreementFileProgress; + commonDialog.alert({title: 'Upload Failed', content: resp.data.message, type: 'error'}) + }, function (evt) { + $scope.agreementFileProgress.value = parseInt(100 * evt.loaded / evt.total); + }) + } + } + }; + + //上传申请表 + $scope.uploadApplyFile = function (file) { + if (file != null) { + if (file.size > 3 * 1024 * 1024) { + commonDialog.alert({title: 'Error', content: '文件大小不能超过3MB,请压缩后重试', type: 'error'}) + } else { + $scope.applyFileProgress = {value: 0}; + Upload.upload({ + url: '/attachment/files', + data: {file: file} + }).then(function (resp) { + delete $scope.applyFileProgress; + $scope.file.file_apply_info = resp.data.url; + $scope.updateFile(); + if ($scope.file.file_apply_info.endsWith('pdf')) { + $scope.applyIsImage = false; + } else { + $scope.applyIsImage = true; + } + }, function (resp) { + delete $scope.applyFileProgress; + commonDialog.alert({title: 'Upload Failed', content: resp.data.message, type: 'error'}) + }, function (evt) { + $scope.applyFileProgress.value = parseInt(100 * evt.loaded / evt.total); + }) + + } + } + }; + + /* $scope.downloadAsZip = function () { + var url = '/sys/partners/' + $scope.partner.client_moniker + '/download/complianceAsZIP'; + return url; + }; + */ + $scope.updateFile = function () { + $http.put('/client/partner_info/update/wait_compliance_file', $scope.file).then(function () { + commonDialog.alert({ + title: 'Success', + content: 'Upload Successful', + type: 'success' + }); + $state.reload(); + }, function (resp) { + commonDialog.alert({title: 'Error', content: resp.data.message, type: 'error'}); + }) + }; + + function commitError() { + commonDialog.alert({ + title: 'Error', + content: 'Missing file', + type: 'error' + }); + }; + + + $scope.clientComplianceViewCommit = function () { + if (!$scope.file.client_agree_file) { + commonDialog.alert({title: 'Error', content: '请提交* Agreement', type: 'error'}); + return; + } else if (!$scope.file.client_id_file) { + commonDialog.alert({title: 'Error', content: '请提交* ID', type: 'error'}); + return; + } else if (!$scope.file.client_bank_file) { + commonDialog.alert({title: 'Error', content: '请提交* bank statement', type: 'error'}); + return; + } else if (!$scope.file.client_company_file) { + commonDialog.alert({title: 'Error', content: '请提交* Certificate of Registration', type: 'error'}); + return; + } + ; + commonDialog.confirm({ + title: 'Warning', + content: 'Are you sure commit for compliance audit?' + }).then(function () { + $http.post('/client/partner_info/clientCompliance/' + $scope.partner.client_moniker + '/viewCommit', $scope.file).then(function () { + commonDialog.alert({ + title: 'Success', + content: 'Commit Successful', + type: 'success' + }); + $state.reload(); + }, function (resp) { + commonDialog.alert({title: 'Error', content: resp.data.message, type: 'error'}); + }) + }) + + }; + + $scope.deleteComplianceFiles = function (file_id) { + $scope.file_id = file_id; + commonDialog.confirm({ + title: 'Warning', + content: 'This operation will delete the file, Are you sure?' + }).then(function () { + $http.put('/client/partner_info/auth_file/' + $scope.file_id + '/delete').then(function (resp) { + commonDialog.alert({ + title: 'Success', + content: 'Delete Successful', + type: 'success' + }); + $state.reload(); + }, function (resp) { + commonDialog.alert({title: 'Error', content: resp.data.message, type: 'error'}); + }) + }) + }; + + + $scope.commitPartner = function () { + if ($scope.file) { + if ($scope.file.file_bank_info != null && $scope.file.file_company_info != null && $scope.file.file_id_info != null && $scope.file_apply_info != null) { + $http.put('/client/partner_info/complianceAudit').then(function (resp) { }); } else { @@ -749,9 +1026,114 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot commitError(); } }; + + }]); - app.controller('clientPaymentInfoCtrl', ['$scope', '$http', '$state', 'commonDialog','$uibModal', function ($scope, $http, $state, commonDialog, $uibModal) { + app.controller('aggregateFileCtrl', ['$scope', '$http', '$rootScope', 'commonDialog', '$state', '$uibModal', 'Upload', function ($scope, $http, $rootScope, commonDialog, $state, $uibModal, Upload) { + $scope.signInfo = {}; + $scope.confirmBtn = false; + $scope.signItBtn = true; + + $scope.aggregateFile = function () { + $http.get('/client/partner_info/aggregateFile/client_info').then(function (resp) { + $scope.aggregateFileInfo = resp.data; + }, function (resp) { + commonDialog.alert({ + title: '生成合同失败', + content: "错误原因:" + resp.data.message + ",请联系BD或客服", + type: 'error' + }); + var url = $state.href('compliance_to_perfect'); + window.open(url); + }) + }; + $scope.aggregateFile(); + + $scope.goBottom = function () { + window.scrollTo(0, document.documentElement.scrollHeight - document.documentElement.clientHeight); + $uibModal.open({ + backdrop: false, + templateUrl: '/static/payment/partner/templates/agreement_signature_dialog.html', + controller: 'agreementSignDialogCtrl', + size: 'md' + }).result.then(function (result) { + $scope.confirmBtn = true; + $scope.signItBtn = false; + $scope.signInfo.fullName = result.fullName; + $scope.signInfo.src = result.src; + + }) + }; + + + $scope.submitContract = function () { + $uibModal.open({ + backdrop: false, + templateUrl: '/static/payment/partner/templates/confirmForSubmit.html', + controller: 'submitContractCtrl', + size: 'md' + }).result.then(function (result) { + $scope.uploadSign(); + }) + }; + + $scope.uploadSign = function () { + if ($scope.signInfo.fullName == null || $scope.signInfo.fullName == "") { + commonDialog.alert({title: 'Submit Fail', content: 'Please confirm full name entered', type: 'error'}); + return; + } + if ($scope.signInfo.src == null || $scope.signInfo.src == "") { + commonDialog.alert({title: 'Submit Fail', content: 'Please confirm signature', type: 'error'}); + return; + } + document.getElementById("loading_logo").style =""; + $scope.white(); + var signFile = dataURLtoFile($scope.signInfo.src, $scope.signInfo.fullName); + $scope.logoProgress = {value: 0}; + Upload.upload({ + url: '/client/partner_info/clientCompliance/' + $scope.aggregateFileInfo.client_moniker + '/commit_aggregate_file', + data: {file: signFile} + }).then(function (resp) { + document.getElementById('loading').style.display = "none"; + document.getElementById('loading_logo').style.display = "none"; + delete $scope.logoProgress; + $state.go('compliance_to_perfect', {reload: true}); + window.open(resp.data.file_value, '_blank'); + }, function (resp) { + delete $scope.logoProgress; + document.getElementById('loading').style.display = "none"; + document.getElementById('loading_logo').style.display = "none"; + commonDialog.alert({title: 'Commit Aggregate File Fail', content: resp.data.message, type: 'error'}) + }, function (evt) { + $scope.logoProgress.value = parseInt(100 * evt.loaded / evt.total); + }); + + }; + + $scope.white=function(){ + $("#loading").css({ + "position":"absolute",    //绝对位置 + "display":"block",    //让对象成为块级元素 + "background-color":"white",  //背景白色 + "z-index":"9999",  //最上层显示 + "opacity":"0.7"  //背景透明度 + }); + } + + function dataURLtoFile(dataurl, filename) {//将base64转换为文件 + var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1], + bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n); + while (n--) { + u8arr[n] = bstr.charCodeAt(n); + } + return new File([u8arr], filename, {type: mime}); + } + + }]); + + + app.controller('clientPaymentInfoCtrl', ['$scope', '$http', '$state', 'commonDialog', '$uibModal', function ($scope, $http, $state, commonDialog, $uibModal) { $scope.paymentInfo = $scope.partner; $scope.old_customer_surcharge_rate = angular.copy($scope.partner.customer_surcharge_rate); $scope.qrConfig = {currency: 'AUD'}; @@ -760,13 +1142,13 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot switch_verify_ip: false }; - $scope.clientCopyHfLink = function() { - var e=document.getElementById("c-cpbt"); + $scope.clientCopyHfLink = function () { + var e = document.getElementById("c-cpbt"); e.select(); var successful = document.execCommand("Copy"); if (successful) { commonDialog.alert({title: 'Success', content: '已复制到剪切板!', type: 'success'}); - }else { + } else { commonDialog.alert({title: 'Error', content: '您的浏览器不支持!', type: 'error'}); } }; @@ -787,7 +1169,7 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot } $http.put('/client/partner_info/switch_gateway_v2', {enable_gateway_version2: $scope.paymentInfo.enable_gateway_version2}).then(function () { $scope.loadPartnerPaymentInfo(); - }, function (resp) { + }, function (resp) { commonDialog.alert({ title: 'failed to change Gateway Version', content: resp.data.message, @@ -834,7 +1216,11 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot } } }).result.then(function () { - commonDialog.alert({title: 'Success!', content: 'Partner Public Key Upload Successfully', type: 'success'}) + commonDialog.alert({ + title: 'Success!', + content: 'Partner Public Key Upload Successfully', + type: 'success' + }) $scope.loadPartnerPaymentInfo(); }) }; @@ -851,7 +1237,11 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot } } }).result.then(function () { - commonDialog.alert({title: 'Success!', content: 'RoyalPay Public Key Refresh Successfully', type: 'success'}) + commonDialog.alert({ + title: 'Success!', + content: 'RoyalPay Public Key Refresh Successfully', + type: 'success' + }) $scope.loadPartnerPaymentInfo(); }) }; @@ -868,17 +1258,21 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot } } }).result.then(function () { - commonDialog.alert({title: 'Success!', content: 'Refund Password Changed Successfully', type: 'success'}) + commonDialog.alert({ + title: 'Success!', + content: 'Refund Password Changed Successfully', + type: 'success' + }) }) }; - $scope.clientCopyCBBannkPayLink = function() { - var e=document.getElementById("c-cpcbbankpay"); + $scope.clientCopyCBBannkPayLink = function () { + var e = document.getElementById("c-cpcbbankpay"); e.select(); var successful = document.execCommand("Copy"); if (successful) { commonDialog.alert({title: 'Success', content: '已复制到剪切板!', type: 'success'}); - }else { + } else { commonDialog.alert({title: 'Error', content: '您的浏览器不支持!', type: 'error'}); } }; @@ -1065,61 +1459,61 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot slidesPc.push({ image: '/static/images/hfpaylink_intro/pc_hf_pay_step1.png', text: '第一次打开支付链接进行下单,第一步用微信扫码页面弹出的二维码,确认您的身份', - id:0 + id: 0 }); slidesPc.push({ image: '/static/images/hfpaylink_intro/pc_hf_pay_step2.png', text: '下一步,填写订单信息。注意订单金额货币单位是澳元(AUD)。请认真填写商品名称,否则可能导致交易不成功。填写完成,最后提交订单。', - id:1 + id: 1 }); slidesPc.push({ image: '/static/images/hfpaylink_intro/pc_hf_pay_step3.png', text: '页面显示支付二维码,使用相应的支付app进行扫码支付。如订单有误,请点击"返回上一步"修改订单。', - id:2 + id: 2 }); slidesPc.push({ image: '/static/images/hfpaylink_intro/hf_pay_end.png', text: '完成支付,跳转到支付成功页面。', - id:3 + id: 3 }); }; var addSlidePcBank = function () { slidesPcBank.push({ image: '/static/images/hfpaylink_intro/pc_bank_hf_pay_step1.png', text: '第一次打开支付链接进行下单,第一步用微信扫码页面弹出的二维码,确认您的身份', - id:0 + id: 0 }); slidesPcBank.push({ image: '/static/images/hfpaylink_intro/pc_bank_hf_pay_step2.png', text: '下一步,填写订单信息。注意订单金额货币单位是澳元(AUD)。请认真填写商品名称,否则可能导致交易不成功。填写完成,最后提交订单。', - id:1 + id: 1 }); slidesPcBank.push({ image: '/static/images/hfpaylink_intro/pc_bank_hf_pay_step3.png', text: '提交订单后,页面跳转到对应银行,请填写相关信息完成支付。', - id:2 + id: 2 }); slidesPcBank.push({ image: '/static/images/hfpaylink_intro/hf_pay_end.png', text: '完成支付,跳转到支付成功页面。', - id:3 + id: 3 }); }; var addSlideApp = function () { slidesApp.push({ image: '/static/images/hfpaylink_intro/app_bank_hf_pay_step1.png', text: '使用微信客户端,填写订单信息。注意订单金额货币单位是澳元(AUD)。请认真填写商品名称,否则可能导致交易不成功。填写完成,最后提交订单。', - id:0 + id: 0 }); slidesApp.push({ image: '/static/images/hfpaylink_intro/app_bank_hf_pay_step2.png', text: '提交订单后,页面跳转到汇付天下支付页面,请填写相关信息完成支付。', - id:1 + id: 1 }); }; $scope.toPcFirst = function () { $scope.reset(); - if(!$scope.pcfirst) { + if (!$scope.pcfirst) { return; } addSlidePc(); @@ -1127,7 +1521,7 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot }; $scope.toBankFirst = function () { $scope.reset(); - if(!$scope.bankfirst) { + if (!$scope.bankfirst) { return; } addSlidePcBank(); @@ -1135,7 +1529,7 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot }; $scope.toAppFirst = function () { $scope.reset(); - if(!$scope.appfirst) { + if (!$scope.appfirst) { return; } addSlideApp(); @@ -1177,8 +1571,8 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot commonDialog.confirm({ title: 'Warning', content: 'Refresh Credential will expire the current one, ' + - 'which will cause the current payment service disabled. ' + - 'Are you sure going on?' + 'which will cause the current payment service disabled. ' + + 'Are you sure going on?' }).then(function () { $http.put('/sys/partners/' + $scope.partner.client_moniker + '/credential_code').then(function () { $state.reload(); @@ -1232,9 +1626,9 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot }) } }]); - app.controller('clientClearingConfigCtrl', ['$scope', '$http','$state','commonDialog', function ($scope, $http,$state,commonDialog) { - $scope.bankCtrl = {rate_name:'Wechat'}; - $scope.init = {manual:false}; + app.controller('clientClearingConfigCtrl', ['$scope', '$http', '$state', 'commonDialog', function ($scope, $http, $state, commonDialog) { + $scope.bankCtrl = {rate_name: 'Wechat'}; + $scope.init = {manual: false}; $scope.getBankAccount = function () { $http.get('/client/partner_info/bank_account').then(function (resp) { $scope.bankaccount = resp.data; @@ -1251,32 +1645,32 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot }); }; $scope.manualSettle = function (manualSettle) { - if(!$scope.init.manual){ + if (!$scope.init.manual) { $scope.init.manual = true; return; } var waring_messsage; - if(manualSettle){ + if (manualSettle) { waring_messsage = "Open" - }else { + } else { waring_messsage = "Close" } commonDialog.confirm({ title: 'Confirmation', - content: 'Are you sure to '+waring_messsage+'?', - }).then(function(){ - $http.put('/client/partner_info/manual_settle?'+'manual_settle='+manualSettle).then(function (resp) { - },function (resp) { + content: 'Are you sure to ' + waring_messsage + '?', + }).then(function () { + $http.put('/client/partner_info/manual_settle?' + 'manual_settle=' + manualSettle).then(function (resp) { + }, function (resp) { commonDialog.alert({title: 'Error!', content: resp.data.message, type: 'error'}) }) - },function () { + }, function () { $state.reload(); }); }; $scope.getRates(); }]); - app.controller('clientSubPartnersCtrl', ['$scope', '$http','partner', function ($scope, $http,partner) { + app.controller('clientSubPartnersCtrl', ['$scope', '$http', 'partner', function ($scope, $http, partner) { $scope.partner = partner.data; $scope.loadSubPartners = function () { $http.get('/client/partner_info/sub_partners').then(function (resp) { @@ -1292,7 +1686,7 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot }; $scope.loadSubPartners(); }]); - app.controller('clientDeviceCtrl','newDive','newDeviceDialogCtrl', ['$scope', '$http', 'orderService', 'commonDialog', 'refunder','$filter', '$uibModal', function ($scope, $http, orderService, commonDialog, refunder,$filter,$uibModal) { + app.controller('clientDeviceCtrl', 'newDive', 'newDeviceDialogCtrl', ['$scope', '$http', 'orderService', 'commonDialog', 'refunder', '$filter', '$uibModal', function ($scope, $http, orderService, commonDialog, refunder, $filter, $uibModal) { $scope.pagination = {}; $scope.params = {}; /** @@ -1398,8 +1792,26 @@ define(['angular', 'decimal', 'uiRouter', 'ngBootSwitch', 'ngFileUpload','uiBoot }; + }]); + app.controller('submitContractCtrl', ['$scope', '$http', function ($scope, $http) { + $scope.choice = false; + $scope.chooseYes = function () { + $scope.choice = true; + $scope.$close($scope.choice); + }; }]); + + app.controller('agreementSignDialogCtrl', ['$scope', '$http', 'commonDialog', function ($scope, $http, commonDialog) { + $scope.submitSign = function (fullName) { + var sign = $("#signature").jSignature('getData', 'image'); + var signInfo = {}; + signInfo.src = 'data:' + sign[0] + ',' + sign[1]; + signInfo.fullName = fullName; + $scope.$close(signInfo); + }; + }]); + return app; }); diff --git a/src/main/ui/static/payment/partner/partner_compliance.js b/src/main/ui/static/payment/partner/partner_compliance.js new file mode 100644 index 000000000..f60cd70f7 --- /dev/null +++ b/src/main/ui/static/payment/partner/partner_compliance.js @@ -0,0 +1,112 @@ +define(['angular', 'static/commons/commons', 'uiBootstrap', 'uiRouter', 'ngBootSwitch', 'ngFileUpload', 'uiSelect'], function (angular) { + 'use strict'; + var app = angular.module('complianceAuthFile', ['ui.bootstrap', 'ui.router', 'frapontillo.bootstrap-switch', 'ngFileUpload', 'ui.select']); + app.config(['$stateProvider', function ($stateProvider) { + $stateProvider.state('partner_compliance_auth', { + url: '/partners/complianceForClient', + templateUrl: 'static/sys/templates/partner_compliance_for_client.html', + controller: 'compliancePartnerForClientCtrl' + }).state('compliance_detail', { + url: '/{client_moniker}/compliance_detail', + templateUrl: '/static/payment/partner/templates/client_compliance_to_auth.html', + controller: 'partnerComplianceCompanyDetail', + resolve: { + file: ['$http','$stateParams',function ($http, $stateParams) { + return $http.get('/compliance/audit/compliance/clientViewFiles/'+ $stateParams.client_moniker); + }] + } + }).state('partner_detail', { + url: '/{client_moniker}/partner_detail', + templateUrl: '/static/payment/partner/templates/partner_detail_for_compliance.html', + controller: 'partnerComplianceCompanyDetail', + resolve: { + file: ['$http','$stateParams',function ($http, $stateParams) { + return $http.get('/compliance/audit/compliance/clientViewFiles/'+ $stateParams.client_moniker); + }] + } + }).state('compliance_for_audit_detail', { + url: '/partner/complianceForAudit', + templateUrl: '/static/payment/partner/templates/compliance_auth.html', + controller: 'partnerComplianceAuditCtrl', + + }) + }]); + + app.controller('compliancePartnerForClientCtrl', ['$scope', '$sce', '$http', '$filter', '$uibModal', 'businessStructuresMap', 'industryMap', 'stateMap', 'sectorMap', 'countryMap', + function ($scope, $sce, $http, $filter, $uibModal, businessStructuresMap, industryMap, stateMap, sectorMap, countryMap) { + $scope.pagination = {}; + $scope.industries = industryMap.configs(); + $scope.states = stateMap.configs(); + $scope.countries = countryMap.configs(); + $scope.params = {}; + $scope.loadClientCompliance = function (page) { + var params = angular.copy($scope.params); + params.page = page || $scope.pagination.page || 1; + $http.get('/compliance/audit/listClientCompliances', {params: params}).then(function (resp) { + $scope.compliances = resp.data.data; + $scope.pagination = resp.data.pagination; + }); + }; + $scope.loadClientCompliance(1); + $scope.statusSelected = function (arr) { + return $scope.params.status != null && $scope.params.status.filter(function (status) { + return arr.indexOf(status) >= 0 + }).length > 0 + }; + }]); + + app.controller('partnerComplianceCompanyDetail', ['$rootScope', '$scope', '$http', '$state', '$uibModal', 'commonDialog', 'file', function ($rootScope, $scope, $http, $state, $uibModal, commonDialog, file) { + $scope.file = file.data || {}; + $scope.partner = $scope.file.client; + + $scope.passPartnerComplianceFiles = function () { + commonDialog.confirm({ + title: 'Confirm!', + content: '确认是否通过商户合规文件?' + }).then(function () { + $http.put('/compliance/audit/'+$scope.file.client.client_id+'/pass/complianceFile',{}).then(function (resp) { + $state.reload(); + }, function (resp) { + commonDialog.alert({title: 'Error', content: resp.data.message, type: 'error'}); + }) + }) + } + + + $scope.refusePartnerComplianceFiles = function (obj) { + var partner = angular.copy(obj); + $uibModal.open({ + templateUrl: '/static/payment/partner/templates/refuse_reason.html', + controller: 'refusePartnerComplianceFilesCtrl', + resolve: { + partner: partner + } + }) + }; + }]); + + app.controller('refusePartnerComplianceFilesCtrl', ['$scope', '$http', '$state', 'partner', function ($scope, $http, $state, partner) { + $scope.partner = angular.copy(partner); + $scope.partner.description = ""; + + $scope.refusePartnerComplianceFiles = function () { + var a = $scope.partner.description; + $http.put('/compliance/audit/'+$scope.partner.client_id+'/refuse/complianceFile',{description:$scope.partner.description}).then(function (resp) { + $state.reload(); + }, function (resp) { + commonDialog.alert({title: 'Error', content: resp.data.message, type: 'error'}); + }) + } + }]); + + + app.controller('partnerComplianceAuditCtrl', ['$rootScope','$state','$stateParams', function ($rootScope,$state,$stateParams) { + if ($state.is('compliance_for_audit_detail')){ + $state.go('partner_detail'); + } + }]); + + + + return app; +}); diff --git a/src/main/ui/static/payment/partner/templates/agreement_signature_dialog.html b/src/main/ui/static/payment/partner/templates/agreement_signature_dialog.html new file mode 100644 index 000000000..d9f8cac48 --- /dev/null +++ b/src/main/ui/static/payment/partner/templates/agreement_signature_dialog.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + diff --git a/src/main/ui/static/payment/partner/templates/client_compliance_to_auth.html b/src/main/ui/static/payment/partner/templates/client_compliance_to_auth.html new file mode 100644 index 000000000..78da221e1 --- /dev/null +++ b/src/main/ui/static/payment/partner/templates/client_compliance_to_auth.html @@ -0,0 +1,168 @@ + + +
    +
    +
    +
    + + + + + + +
    1 + + +
    + + + + +
    + +
    + + + + + + + +
    1 + + +
    +
    +
    + +
    + +
    + + + + + + + +
    1 + + +
    +
    +
    + +
    + +
    + + + + + + + +
    1 + + +
    +
    +
    + + + + + + + + diff --git a/src/main/ui/static/payment/partner/templates/client_compliance_to_perfect.html b/src/main/ui/static/payment/partner/templates/client_compliance_to_perfect.html new file mode 100644 index 000000000..784dc06fb --- /dev/null +++ b/src/main/ui/static/payment/partner/templates/client_compliance_to_perfect.html @@ -0,0 +1,201 @@ + +
    +

    商户合规文件补充 + + ({{file.client_refuse_reason}}) +

    +
    +
    +
    +
    + +
    +
    + +
    + + + + + + + + + +
    1 + + + +
    + + +
    +
    +
    +

    Example:请保证图片信息清晰可见,如示例 + +

    +
    +
    +
    + +
    + +
    +
    + +
    + + + + + + + + + +
    1 + + + +
    +
    +
    +
    +
    +

    Example:公司请提供以下文件图片,如示例 + +

    + +

    sole trade(个体户),partnership(合伙),trust(信托)请在http://abr.business.gov.au,将查询结果截图上传 + +

    +
    +
    +
    +
    + +
    + +
    +
    + +
    + + + + + + + + + +
    1 + + + +
    +
    +
    +
    +
    +

    Example:请保证图片(护照或驾照)信息清晰可见,如示例 + + +

    +
    +
    +
    +
    + +
    +
    + +
    +
    + Resign The Contract + + Sign The Contract + + + + + + + + +
    1 + + + +
    +
    +
    +
    +
    +
    + +
    +
    +
    + + + + + diff --git a/src/main/ui/static/payment/partner/templates/client_partner_detail.html b/src/main/ui/static/payment/partner/templates/client_partner_detail.html index e185467ba..5752a88ec 100644 --- a/src/main/ui/static/payment/partner/templates/client_partner_detail.html +++ b/src/main/ui/static/payment/partner/templates/client_partner_detail.html @@ -52,6 +52,7 @@
  • Compliance files
  • +
    @@ -825,4 +826,4 @@
    - \ No newline at end of file + diff --git a/src/main/ui/static/payment/partner/templates/compliance_files_advice.html b/src/main/ui/static/payment/partner/templates/compliance_files_advice.html new file mode 100644 index 000000000..a451aa68d --- /dev/null +++ b/src/main/ui/static/payment/partner/templates/compliance_files_advice.html @@ -0,0 +1,65 @@ + + + + diff --git a/src/main/ui/static/payment/partner/templates/confirmForSubmit.html b/src/main/ui/static/payment/partner/templates/confirmForSubmit.html new file mode 100644 index 000000000..227e479b2 --- /dev/null +++ b/src/main/ui/static/payment/partner/templates/confirmForSubmit.html @@ -0,0 +1,29 @@ + + + + + diff --git a/src/main/ui/static/payment/partner/templates/contract_for_compliance.html b/src/main/ui/static/payment/partner/templates/contract_for_compliance.html new file mode 100644 index 000000000..5f6e94a85 --- /dev/null +++ b/src/main/ui/static/payment/partner/templates/contract_for_compliance.html @@ -0,0 +1,1246 @@ + + + + + + + + + +
    + +
    + + +
    +
    +
    +
    +
    + +
    + +
    +
    + +
    + +
    + + +
    +

    +

    1

    +

    1

    +

    1

    +

    1

    +

    CORNWALL STODART

    +

    Level 10

    +

    114 William Street

    +

    DX 636

    +

    MELBOURNE VIC 3000

    +

    Telephone: +61 3 9608 2000

    +

    Facsimile: +61 3 9608 2222

    +

    Email: j.masterson@cornwalls.com.au

    +

    Reference: Joel Masterson:1040326

    +
    +

    SERVICES AGREEMENT

    +

    +

    Date

    +

    +

    +

    +

    This Services Agreement is made on the day it is signed by all parties.

    +

    +

    PARTIES

    +

    +
    + + +
    + + + + + + + + + + + + + + + + + + +
    +

    +
    +

    The Supplier

    +
    +

    The Client:

    +
    +

    Name:

    +
    +

    Tunnel Show Pty Ltd (ACN 601 619 685)

    +

    trading as RoyalPay

    +
    +

    +

    +
    +

    Address:

    +
    +

    Level 11
    15 William Street
    Melbourne VIC3000

    +
    +

    {{aggregateFileInfo.address}}
    {{aggregateFileInfo.address_sub}}
    {{aggregateFileInfo.suburb}} {{aggregateFileInfo.state}} {{aggregateFileInfo.postcode}}

    +
    +
    + +
    + + +
    +

    INTRODUCTION

    +

    +
      +
    • The Supplier is a leading Australian financial technology company dedicated to providing mobile payment platforms to facilitate simple and efficient cross-border payments.
    • +
    • To this end, the Supplier has developed a certain software application and platform which provides Clients (who have web-based gateway APIs (being application programming interfaces) and point-of-sale payment processing and settlement capabilities in the Chinese currency) with a payment integration system, which:
    • +
    +
      +
    1. utilises the WeChat Pay payment technology that is owned by Tencent Holdings Limited (China) and that is licensed to the Supplier to facilitate third party payments in Australia;
    2. +
    3. utilises the Alipaypayment technology that is owned by Alipay.com Co., Ltd. (China) and that is licensed to the Supplier to facilitate third party payments in Australia;
    4. +
    5. utilises the BestPaypayment technology that is owned by China Telecom Orange Finance (China) and that is licensed to the Supplier to facilitate third party payments in Australia;
    6. +
    7. utilises theJDpay payment technology that is owned by Chinabank Payment (Beijing) Technology Co., Ltd.and that is licensed to the Supplier to facilitate third party payments in Australia;
    8. +
    9. utilisesthe CB BankPay payment technology licensed to the Supplier by Lakala Payment Co., Ltd, Yeepay Co., Ltd, PnR Data Service Co.,Ltdand other companiesto facilitate third party payments in Australia;
    10. +
    11. processes payments from users of the WeChat Pay, Alipay, BestPay, JDpay, CB BankPay and otherpayment technologies and Chinese bank cardsin Chinese Yuan, converts such payments to the Australian Dollar equivalent and then sends such payments to the Client by utilising settlement functions forming part of the software application;
    12. +
    13. allows the Client to access the merchant management portal feature of the software application which facilitates transaction management, reporting and provides transactional analytics; and
    14. +
    15. provides the Client with opportunity to access to additional unique WeChat Pay marketing features including WeChat Lucky Money (an incentive program where payers randomly receive cashbacks) and WeChat Moment (being social media advertising aimed at enhancing a Clients brand awareness).
    16. +
    17. provides the Client with access to additional Alipay Discovery marketing features such as 12% Off Event (an incentive program where payers randomly receive discount) and Discovery Channel (location based services to attract nearby clients).
    18. +
    +

    +

    (RoyalPay Cross-Border Payment Services)

    +
      +
    • The Client wishes to use the RoyalPay Cross-Border Payment Services provided by the Supplier for receiving payments from the Clients customers, for the Clients own goods and/or services provided by the Client to its customers at the Clients own retail outlets and/or through the Clients own website.
    • +
    • The parties agree that the Supplier will supply the Client with the RoyalPay Cross-Border Payment Services in accordance with the terms of this Agreement.
    • +
    +

    +

    OPERATIVE PROVISIONS

    +

    +

    The Parties agree as follows:

    +

    +

    1.Agreement

    +
      +
    • 1.1 The Supplier agrees to provide the RoyalPay Cross-BorderPaymentServices (and to provide the Client with any applicable documentationor guidancepertaining to the Clients use of the RoyalPay Cross-Border Payment Services) subject to the terms of this Agreement.
    • +
    • 1.2 The Client agrees to pay to the Supplier the Fees and charges for the RoyalPay Cross-Border Payment Services as set out in Schedule B.
    • +
    • 1.3 This Agreement shall apply to the supply and provision of any and all RoyalPay Cross-Border Payment Services to the Client during the Term.
    • +
    +

    2.Contract Manager

    +
      +
    • 2.1 The Clientmay nominate a manager in Schedule Bor otherwise in writing from time to time to act as the Clients contract manager in dealing with the Supplier under this Agreement (ContractManager).
    • +
    • 2.2 If the Supplier appoints a Contract Manager, the Contract Manager will be the authorised representative of the Supplier for the purposes of this Agreement.
    • +
    +

    3.Term

    +
      +
    • 3.1 This Agreement commences on the Commencement Date and continues for the Initial Term (Term), unless extended under clause 3.2or otherwise terminated earlier in accordance with clause 15.
    • +
    • 3.2 The Term of this Agreement will automatically extend beyond the Initial Term for subsequent rolling 12 month periods (Renewed Term), unless:
    • +
        +
      • (a) either party notifies the other of termination in writing, at least thirty (30) calendar days before the end of the Initial Term or any Renewed Term (as the case may be); or
      • +
      • (b) this Agreement is terminated earlier pursuant to clause 15.
      • +
      +
    +

    4.Acceptance Testing

    +
      +
    • 4.1 The Client shall conduct and complete all Acceptance Testing as soon as practicable but within five (5) Business Days of the delivery of the RoyalPay Cross-Border Payment Services by the Supplier. The Client will allow the Supplier to be present at any Acceptance Testing, provided that the Supplier gives the Client sufficient notice of its wish to be present.
    • +
    • 4.2 Where, during Acceptance Testing and for a reasonable period after Acceptance Testing is completed, the Client encounters any malfunction, defect or suspected error in the Suppliers RoyalPay Cross-Border Payment Services, the Supplier will correct any such malfunction, defect and suspected errors as soon as practicable.
    • +
    • 4.3 If the Supplier disputes the validity of the Acceptance Testing, the parties agree to resolve the dispute as set out in clause 17.
    • +
    • 4.4 In the event of a malfunction, defect or suspected error relating to the integration of a product or service supplied to the Supplier by a third party, the Supplier will provide reasonable and prompt assistance to the Client to identify the problem and who is responsible for resolving it.
    • +
    +

    5.Delivery and Provision of RoyalPay Cross-Border Payment Services

    +
      +

      5.1 The Client engages the Supplierto provide the RoyalPay Cross-Border Payment Services and the Supplier agrees to use reasonable endeavours to provide the RoyalPay Cross-Border Payment Services(together with any applicable documentation or guidance pertaining to the Clients use of the RoyalPay Cross-Border Payment Services)to the Client, in accordance with the terms and conditions of this Agreement.

      +
    • 5.2 The Supplier shall endeavour to provide a reasonably commercial level of availability of the RoyalPay Cross-Border Payment Services for twenty-four (24) hours a day, seven (7) days a week, with at 99.98% up time (annualised), excluding:
    • +
        +
      • (c) any planned maintenance periods of which the Supplier will provide the Client with at least seven (7) Business Days prior written notice of;
      • +
      • (d) any unscheduled maintenance periods which the Supplier will endeavour to perform outside of the Suppliers normal business hours. In situations where unscheduled maintenance is required, the Supplier shall use its best efforts to provide the Client with at least six (6) hours notice in advance of such maintenance taking place; and
      • +
      • (e) any communications, banking, terminal and external systems failures which are outside of the control of the Supplier.
      • +
      +
    • 5.3 The Supplier shall, as part of the provision of the RoyalPay Cross-Border Payment Services, provide to the Client (at no additional cost to the Client) standard customer support during the Suppliers normal business hours in accordance with the Suppliers Service Level Agreement (SLA) which is operative during the Term. In particular, the Supplier shall provide the Client with Level 1, 2 and 3 support (as detailed in the SLA) as part of the provision of the RoyalPay Cross-Border Payment Services.
    • +
    • 5.4 Further to clause 5.3, the Client may (in its sole discretion) elect to purchase additional and/or enhanced support services which shall be provided by the Supplier at the Suppliers hourly rate applicable at the relevant time.
    • +
    • 5.5 The Client acknowledgesthat the Supplieris providing the RoyalPay Cross-Border Payment Services to the Client on a non-exclusive basis and that the Supplier may provide services of the same or a similar nature to any third party.
    • +
    • 5.6 The Suppliermay provide the RoyalPay Cross-Border Payment Services in the manner determined by it in its absolute and sole discretion, so longas in doing so, it does not breach any material provision of this Agreement.
    • +
    • 5.7 The parties acknowledge that the Supplier has entered this Agreement with the Client on the basis that the RoyalPay Cross-Border Payment Services utilise the WeChat Pay payment technology licensed to the Supplier by Tencent Holdings Limited (China). The parties agree that, should the WeChat Pay payment technology become unavailable to the Supplier (for any reason whatsoever), the Supplier shall be entitled, without penalty, to immediately terminate this Agreement by providing written notice of such termination to the Client.
    • +
    • 5.8 The parties acknowledge that the Supplier has entered this Agreement with the Client on the basis that the RoyalPay Cross-Border Payment Services utilise the Alipay payment technologylicensed to the Supplier by Alipay.com Co., Ltd.. The parties agree that, should the Alipaypayment technology become unavailable to the Supplier (for any reason whatsoever), the Supplier shall be entitled, without penalty, to immediately terminate this Agreement by providing written notice of such termination to the Client.
    • +
    • 5.9 The parties acknowledge that the Supplier has entered this Agreement with the Client on the basis that the RoyalPay Cross-Border Payment Services utilise the BestPaypayment technology licensed to the Supplier by China Telecom Orange Finance. The parties agree that, should the BestPaypayment technology become unavailable to the Supplier (for any reason whatsoever), the Supplier shall be entitled, without penalty, to immediately terminate this Agreement by providing written notice of such termination to the Client.
    • +
    • 5.10 The parties acknowledge that the Supplier has entered this Agreement with the Client on the basis that the RoyalPay Cross-Border Payment Services utilise the JDpaypayment technology licensed to the Supplier by Chinabank Payment (Beijing) Technology Co., Ltd. The parties agree that, should the JDpaypayment technology become unavailable to the Supplier (for any reason whatsoever), the Supplier shall be entitled, without penalty, to immediately terminate this Agreement by providing written notice of such termination to the Client.
    • +
    • 5.11 The parties acknowledge that the Supplier has entered this Agreement with the Client on the basis that the RoyalPay Cross-Border Payment Services utilise the CB BankPay payment technology licensed to the Supplier byLakala Payment Co., Ltd, Yeepay Co., Ltd, PnR Data Service Co.,Ltd and other companies. The parties agree that, should the CB BankPay payment technology become unavailable to the Supplier (for any reason whatsoever), the Supplier shall be entitled, without penalty, to immediately terminate this Agreement by providing written notice of such termination to the Client.
    • +
    • 5.12 The parties acknowledge that at any time, the Supplier may become licensed to use other payment technologies to provide the RoyalPay Cross-Border Payment Services, in which event the Supplier will endeavour to provide notice together with any applicable rates and details to the Client.
    • +
    • 5.13 The Client acknowledges that:
    • + +
        +
      • the RoyalPay Cross-Border Payment Services consists of complex software which may never be wholly free from defects, errors, bugs and compatibility issues and, in this respect, the Supplier gives no warranty or representation that the software pertaining to the RoyalPay Cross-Border Payment Services will be wholly free from such defects, errors, bugs or compatibility issues;
      • +
      +

      +
        +
      • complex software may be applied by the Supplier when upgrading existing functionalities of the RoyalPay Cross-Border Payment Services and, where this occurs, the Client shall not be subject to any additional fees or charges as a result of such upgrade, except where such upgrade introduces new functionalities to the RoyalPay Cross-Border Payment Services for which the Client will be notified in advance; and
      • +
      +

      +
        +
      • the Supplier will not, and does not, purport to provide any legal, accountancy, taxation or any other financial advisory services under this Agreement or in relation to the RoyalPay Cross-Border Payment Services, except as otherwise expressly provided herein.
      • +
      +

      +
    • 5.14 The Supplier has no obligation to the Client to rectify any defect or fault in the RoyalPay Cross-Border Payment Services caused or contributed to by:
    • +
        +
      • (a)the acts or omissions of the Client or the Clients Personnel; or
      • +
      • (b)faults or defects that arise in telecommunication services provided to the Client by a third party supplier other than such suppliers engaged or contracted by the Supplier, regardless of whether the costs associated with such services are paid or reimbursed by the Client or relate specifically to the RoyalPay Cross-Border Payment Services provided to the Client.
      • +
      +
    +

    6.RoyalPay Cross-Border Payment Services Fees, Settlement Terms and Refunds

    +
      +
    • 6.1 The details relating to Fees pertaining to the RoyalPay Cross-Border Payment Services, as well as the settlement terms for remittance of payments to the Client that have been made by the Clients customers and the terms and conditions regarding refunds are described in Schedule B.
    • +
    • 6.2 If the Client fails to pay the Fees to the Supplier in accordance with the Payment Terms, the Client is liable to pay the Supplier:
    • +
        +
      • (a)interest at the interest rate set out under the Penalty Interest Rate Act 1983 (Vic)from time to time, calculated daily, on all outstanding amounts from the date upon which payment was due until payment is received by the Supplier in full; and
      • +
      • (b)all reasonable costs and expenses incurred by the Supplier (including legal costs on a full indemnity basis) in relation to the recovery of the Fees and interest from the Client.
      • +
      +
    • 6.3 For the avoidance of doubt, the Client will not be liable for Fees which are not described in Schedule B, other than those that may be agreed to by the Client in writing from time to time.
    • +
    • 6.4 Unless otherwise agreed by the parties in writing, all expenses incurred by the Supplier in the provision of the RoyalPay Cross-Border Payment Services are to be borne by the Supplier.
    • +
    • 6.5 The Supplier may reissue an invoice if any error in the invoice is later discovered. Where:
    • +
        +
      • (a)the Client has overpaid any Fees as a result of such an error, the Clients accountwill be credited with the overpayment or, if the Client has stopped acquiring the RoyalPay Cross-Border Payment Services from the Supplier, the Supplier will refund the overpayment promptly after the Clients request but after deduction of any other amounts due by theClient; or
      • +
      • (b)the Client has underpaid any charges as a result of the error, the Client will be required to pay the correct amount to the Supplier after the invoice is reissued in accordance with the Payment Terms.
      • +
      +
    • 6.6 In the event that the Client fails to promptly notify the Supplier of any changes to, or termination of, its business or contact method which materially impacts the Suppliers settlement process for remittance of payments to the Client, and where the Supplier receives no response from the Client within three (3) Business Days after contacting the Client to ascertain this information, then the Supplier may, upon a customers request and in relation to payments already made by a customer but not yet settled with the Client, disburse or refund such payment directly to the customers nominated account.
    • +
    • 6.7 It would not be settled on both public holidays of Australia and China.
    • +

      + 6.8 In the case of special circumstances, it is subjected to Supplier liquidation notice (e-mail, WeChat, management platform, App and other channels) +
    +

    7.GST

    +
      +
    • 7.1 Unlessotherwise stated in Schedule B, the charges and other amounts payable by the Clientunder this Agreement are calculated or expressedexcludingGST.
    • +
    • 7.2 The Supplier warrants that it is registered for GST and will remain so registered for the duration of the Agreement.
    • +
    • 7.3 The Client warrants that it is registered for GST and will remain so registered for the duration of the Agreement.
    • +
    +

    8.Supplier Obligations, Warranties and Representations

    +
      +
    • 8.1 The Supplier shall be solely responsible for the establishment, integration, operation and management of the RoyalPay Cross-Border Payment Services in accordance with established administrative rules, system functionalities and other applicable terms and conditions, and shall provide all necessary trainings and documentations to facilitate the understanding and operation of Supplier's products and services provided.
    • +
    • 8.2 In addition, the Supplier shall be responsible for receiving, addressing and resolving any complaints that may arise from defects pertaining to the RoyalPay Cross-Border Payment Services.
    • +
    • 8.3 The Supplier represents and warrants that the RoyalPay Cross-Border Payment Services:
    • +
        +
      • (a)do not infringe the Intellectual Property Rights of any third party, other than with respect to the WeChat Pay payment technology that is owned by Tencent Holdings Limited (China), the Alipay payment technology that is owned by Alipay.com Co., Ltd., the BestPaypayment technology that is owned by China Telecom Orange Finance, the JDpay payment technology that is owned by Chinabank Payment (Beijing) Technology Co., Ltd.And the CB BankPay payment technology that is owned by Lakala Payment Co., Ltd, Yeepay Co., Ltd, PnR Data Service Co.,Ltdand other companies; and
      • +
      • (b)will be supplied:
      • +
          +
        • (i)in accordance with the terms of this Agreement,
        • +
        • (ii)in a professional and timely manner, in accordance with best industry practices, and with reasonable care and skill; and
        • +
        • (iii)in accordance with all relevant Laws.
        • +
        +
      +
    • 8.4 The Supplier warrants that it will provide necessary customer support functions relating to business and system enquiries and relating to the RoyalPay Cross-Border Payment Services to the Client in accordance with the terms of the SLA.
    • +
    • 8.5 The Supplier is fully authorised in all commercial and technical capacities by Tencent Holdings Limited (China) to offer the WeChat Pay payment gateway to users in Australia.
    • +
    • 8.6 The Supplier is fully authorised in all commercial and technical capacities by Alipay.com CO., Ltd. (China) to offer the Alipay payment gateway to users in Australia.
    • +
    • 8.7 The Supplier is fully authorised in all commercial and technical capacities by China Telecom Orange Finance (China) to offer the BestPaypayment gateway to users in Australia.
    • +
    • 8.8 The Supplier is fully authorised in all commercial and technical capacities by Chinabank Payment (Beijing) Technology Co., Ltd. to offer the JDpaypayment gateway to users in Australia.
    • +
    • 8.9 The Supplier is fully authorised in all commercial and technical capacities by Lakala Payment Co., Ltd, Yeepay Co., Ltd, PnR Data Service Co.,Ltdand other companiesto offer the CB BankPay payment gateway to users in Australia.
    • +
    • 8.10 The Supplier:
    • +
        +
      • does not represent, warrant or guarantee that any Service will be free of interruptions, delays, faults or errors; and
      • +
      +

      +
        +
      • except as expressly provided in this Agreement, is not liable to the Client or any other third party for any interruptions, delays, faults or errors in connection with the supply of the RoyalPay Cross-Border Payment Services (in part or in full).
      • +
      +

      +
    • 8.11 The Supplier agrees that it will not employ any reverse engineering method aimed at deciphering any computer system or procedures operated by the Client and the Supplier further agrees that it will not copy, amend edit, consolidate, or alter the said computer systems or procedures (including, without limitation, any source programs, object programs, software files, data processing in local computer storage devices, data from the Clients terminals which is transmitted to servers, server data, etc.).
    • +
    • 8.12 The parties agree that the Supplier may, provided the Client has given its express written consent to do so, promote the RoyalPay Cross-Border Payment Services and its business association with the Client by using the Clients name, trademark, marketing materials and any such like details.
    • +
    • 8.13 The Supplier represents and warrants, as at the date of this Agreement, that:
    • +
        +
      • (a)it has the power and has taken all corporate and other action required, to enter into this Agreement and to authorise the execution and delivery of this Agreement and the performance of the Suppliers obligations under this Agreement;
      • +
      • (b)this Agreement constitutes a valid and legally binding obligation of the Supplier in accordance with its terms except to the extent of discretions of courts regarding the availability of equitable remedies and Laws affecting creditors rights generally; and
      • +
      • (c)the execution, delivery and performance of this Agreement does not violate any existing Law or any document or agreement to which the Supplier is a party or which is binding on the Supplier or any of its assets.
      • +
      +
    • 8.14 The Supplier acknowledges that all representations, covenants and warranties in this Agreement are provided on an ongoing basis and will survive the execution, performance and termination of this Agreement.
    • +
    • 8.15 The Suppler is an Australian company incorporated in Melbourne, Victoria and is classified as a finance service company with the official status of Authorised Representative of Flexewallet Pty Ltd (ACN 164 657 032 and Australian Financial Services Licensee 448066) and is legally bound by various compliance regimes for the delivery of services such as the RoyalPay Cross-Border Payment Services.
    • +
    +

    9.Client Obligations, Warranties and Representations

    +
      +
    • 9.1 The Client must:
    • +
        +
      • (a)ensure that, in respect of the RoyalPay Cross-Border Payment Services, the Client meets, and continues to meet all pre-requisites or conditions for supply of the RoyalPay Cross-Border Payment Services;
      • +
      • (b)ensure that its network and systems comply with the relevant specifications provided by the Supplier from time to time and, in this respect, the Client shall be solely responsible for procuring and maintaining its network connections and telecommunications link from its systems to the Suppliers system. The Client is required to notify the Supplier of any system changes that might impact the RoyalPay Cross-Border Payment Services and allow the Supplier to perform any necessary system testing to ensure the full integrity of RoyalPay Cross-Border Payment Services is maintained.
      • +
      • (c)use the RoyalPay Cross-Border Payment Services for its intended purpose;
      • +
      • (d)perform all tasks and provide all materials required of it, and by the dates specified, in this Agreement;
      • +
      • (e)co-operate with the Supplier to allow the Supplier to provide the RoyalPay Cross-Border Payment Services and ensure that the Clients procedures for handling orders and delivering goods and/or services are compatible with the settlement process that forms part of the RoyalPay Cross-Border Payment Services;
      • +
      • (f)provide the Supplier with accurate information regarding the commercial aspects of the Client such as changes to company ownership, corporate structure, business domicile and website details and details of products and/or services offered by the Client which may materially affect the scope of the RoyalPay Cross-Border Payment Services;
      • +
      • (g)provide the Supplier with all information in the Clients possession, custody or control that the Supplier reasonably requires to provide the RoyalPay Cross-Border Payment Services and ensure that all information it gives to the Supplier is correct, current and complete;
      • +
      • (h)in the event that the Supplier reasonably believes or has been given notice of an unauthorised or fraudulent use of the RoyalPay Cross-Border Payment Services, provide the Supplier with all information in the Clients possession, custody or control which may be relevant to the unauthorised or fraudulent use;
      • +
      • (i)comply with all applicable Laws, regulations, codes and standards (including any technical standards of a Government Agency);
      • +
      • (j)comply with all requirements, documentation, user manuals and specifications referred to in the Schedules and any additional documents that are provided by the Supplier to the Client and which the Client has agreed in writing to be bound by, such agreement not to be unreasonably withheld;
      • +
      • (k)conduct and retain backups of any of the Clients data (whether hosted on the Suppliers systems or provided to the Supplier in connection with the RoyalPay Cross-Border Payment Services) to the extent reasonable, having regard to the nature of the data;
      • +
      • (l)immediately report to the Supplier, in writing, any unauthorised use of the RoyalPay Cross-Border Payment Services;
      • +
      • (m)use its best endeavours to display (in accordance with directions of the Supplier) relevant visual merchandise items such as trademarks, logos, branding and advertising materials of the Supplier pertaining to the RoyalPay Cross-Border Payment Services provided pursuant to this Agreement in prominent positions and on the Clients website
      • +
      • (n)not:
      • +
          +
        • (i)employ any reverse engineering method aimed at deciphering any computer system or procedures operated by the Supplier and the Client further agrees that it will not copy, amend edit, consolidate, or alter the said computer systems or procedures (including, without limitation, any source programs, object programs, software files, data processing in local computer storage devices, data from the Suppliers terminals which is transmitted to servers, server data, etc.). In addition, the Client agrees that it will not alter or amend the original functions of the software pertaining to the RoyalPay Cross-Border Payment Services or add further functions to the software pertaining to the RoyalPay Cross-Border Payment Services.
        • +
        • (ii)unreasonably delay any action, approval, direction, determination or decision which is reasonably required of the Client under this Agreement or for the Supplier to provide the RoyalPay Cross-Border Payment Services;
        • +
        • (iii)engage in, or procure, assist or allow any of its Personnel or any other person to engage in, any fraudulent or unauthorised use of the RoyalPay Cross-Border Payment Services. If the Client engages in any such use of the RoyalPay Cross-Border Payment Services, the Client must report such use to the Supplier as soon as it becomes aware of such use; or
        • +
        • (iv)engage in unlawful, harmful, threatening, defamatory, infringing, obscene, harassing, sexually explicit or racially offensive activities or facilitate illegal activities, promote unlawful violent or cause damage or injury to any person or property.
        • +
        +
      +
    • 9.2 The Client shall be solely responsible for:
    • +
    • (a)understanding how to access and use the RoyalPay Cross-Border Payment Services;
    • +
    • (b)each of its Personnel who have access to, or use of, the RoyalPay Cross-Border Payment Services;
    • +
    • (c)any use of the RoyalPay Cross-Border Payment Services, or instruction or direction given, through the Clients login and/or using the Clients passwords;
    • +
    • (d)any use of the RoyalPay Cross-Border Payment Services by the Clients Personnel which is fraudulent or not authorised by the Client or the Supplier;
    • +
    • (e)implementing and maintaining the security of the Clients network and systems. The Client is liable for all Fees resulting from any use of the RoyalPay Cross-Border Payment Services by any person, whether or not authorised by the Client, resulting from a virus, trojan, worm or other malicious computer code, denial of service attacks, a hacking incident (including a SIM, PABX, IP PBX or SIP Gateway device hack), or other means of exploiting a weakness in the Clients network, systems or security measures.
    • +
    • 9.3 Upon request from the Supplier, the Client must (in accordance with various compliance obligations including those set out in the Anti-Money Laundering and Counter-Terrorism Financing Act 2006 (Cth)and the Corporations Act 2001 (Cth)), duly provide to the Supplier within five (5) Business Days of receiving a request from the Supplier, all necessary documentation relating to Know Your Customer / Know Your Customers Business, including (but not limited to) company ultimate beneficial ownership details and identifications, evidence of incorporation, compliance AML/CTF programs and any licensing registrations(if applicable), etc.). Notwithstanding any other clause in this Agreement, failure by the Client to provide such required documentation may result in the immediate termination of this Agreement by the Supplier. Information provided to the Supplier by the Client pursuant to this clause shall be retained by the Supplier for a period of at least seven (7) years.
    • +
    • 9.4 The Client acknowledges and agrees that the Supplier is not liable (whether in contract, tort or otherwise) whatsoever for any Loss suffered or incurred by the Client as a result, whether directly or indirectly, of any fact, matter or circumstance that is within the responsibility of the Client under this clause except to the extent caused or contributed to by the Supplier.
    • +
    • 9.5 The Client acknowledges and agrees that:
    • +
    • (a)if the Supplier acquires products or services from any supplier including a carrier (as defined under the Telecommunications Act) in connection with the supply of the RoyalPay Cross-Border Payment Services, the Client will comply with all reasonable directions given by the Supplier as are necessary for the Supplier to comply with its obligations under any agreement with that Supplier;
    • +
    • (b)the Supplier may:
    • +
        +
      • (i)be required by Law, to retain metadata, information and other content in relation to the Clients use of the RoyalPay Cross-Border Payment Services, intercept the Clients communications and pass on details of the Clients use of the RoyalPay Cross-Border Payment Services (including any retained metadata, information and content) to a Government Agency, a law enforcement authority or other authority;
      • +
      • (ii)monitor the Clients usage of the RoyalPay Cross-Border Payment Services including where requested, or directed, to do so by a Government Agency, a law enforcement authority or other authority; and
      • +
      • (iii)investigate any alleged misuse of the RoyalPay Cross-Border Payment Services by the Client including where requested, or directed, to do so by a Government Agency, a law enforcement authority or other authority.
      • +
      +
    • If the Supplier is so required to retain, intercept, monitor or investigate, then the Supplier will immediately provide the Client with written notice to the extent that the Supplier is permitted to do so by Law.
    • +
    • 9.6 The supply of the RoyalPay Cross-Border Payment Servicesmay rely on:
    • +
        +
      • (a)systems, networks, infrastructure and services provided by a third party (whether or not that third party has an agreement or understanding with the Client) including a third party who provides carriage services; and
      • +
      • (b)the Clients systems, networks and infrastructure (Third Party Facilities).
      • +
      +
    • 9.7 Without limiting any other provision of this Agreement, the Client acknowledges and agrees that:
    • +
        +
      • (a)due to reasons beyond the Suppliers control, a defect or failure in one or more Third Party Facilities may cause a delay, failure or interruption to the RoyalPay Cross-Border Payment Services;
      • +
      • (b)without limiting the Clients entitlements under the SLA, the Supplier is not liable to the Client in any way for any Loss (including Consequential Loss) that the Client may incur or suffer as a result of any such delay, failure or interruption; and
      • +
      • (c)if the Client requires the Supplier to undertake any remedial work to repair the RoyalPay Cross-Border Payment Services affected by such a delay, failure or interruption, the Supplier may charge the Client, and the Client must pay, an additional charge, the amount of which will be agreed in advance, for the remedial work that the Supplier undertakes. The Client acknowledges that in some cases the agreed additional charge will be payable by the Client even if the Supplier commences such remedial work and only discovers, after commencing such remedial work, that the delay, failure or interruption to the RoyalPay Cross-Border Payment Services was caused by a defect or failure in Third Party Facilities.
      • +
      +
    • 9.8 The Client represents and warrants to the Supplier that, as at the date this Agreement is formed:
    • +
        +
      • (a)it has had the opportunity to obtain independent professional advice in relation to the legal, taxation, accounting and financial consequences of this Agreement, and has not relied on the Supplier in relation to any of those matters; and
      • +
      • (b)where the Supplier has provided the Client with information about any of the Suppliers Personnel, each of the Personnel has consented to the Client disclosing the information to the Supplier and for the Supplier to use, or disclose, the information to provide the RoyalPay Cross-Border Payment Services and exercise any rights and perform any obligations in respect of the RoyalPay Cross-Border Payment Services.
      • +
      • (c)it has the power and has taken all corporate and other action required, to enter into this Agreement and to authorise the execution and delivery of this Agreement and the performance of the Clients obligations under this Agreement;
      • +
      • (d)this Agreement constitutes a valid and legally binding obligation of the Client in accordance with its terms except to the extent of discretions of courts regarding the availability of equitable remedies and Laws affecting creditors rights generally; and
      • +
      • (e)the execution, delivery and performance of this Agreement does not violate any existing Law or any document or agreement to which the Client is a party or which is binding on the Client or any of its assets.
      • +
      +
    • 9.9 The Client acknowledges and agrees that the Supplier has the right to permanently or temporarily cease providing the RoyalPay Cross-Border Payment Services, withhold any monies payable to the Client under this Agreement (including unsettled funds), suspend the operation of this Agreement or terminate this Agreement in accordance with clause 15:
    • +
        +
      • (a)if the Supplier suspects on reasonable grounds that the Client does not comply with all applicable Laws, regulations, codes and standardsrequired to provide the RoyalPay Cross-Border Payment Services and the Client fails within a reasonable time or refuses to provide information or documents sufficient to demonstrate that it does comply with all such laws and standards; or
      • +
      • (b)if the Supplier suspects on reasonable grounds that the Client has fraudulently or otherwise misused the RoyalPay Cross-Border Payment Services and the Client fails within a reasonable time or refuses to provide information or documents sufficient to demonstrate that it has not committed such activities; or
      • +
      • (c)if the Client has failed or refused to comply with any of its obligations under this clause 9.
      • +
      +
    • 9.10 The parties acknowledge and agree that in the event an investigation is carried out by the Supplier under clause 9.5(b) and it is reasonably concluded that the Client has fraudulently or otherwise misused the RoyalPay Cross-Border Payment Services causing loss to the Supplier:
    • +
        +
      • (a)the Supplier may issue a written notice to the Client to:
      • +
          +
        • (i)transfer a sum to the Supplier sufficient to cover the loss that the Supplier has or is expected to incur (including any amounts the Supplier has refunded to third parties affected by the Clients misuse of the RoyalPay Cross-Border Payment Services); or
        • +
        • (ii)refund a sum directly to any third parties affected by the Clients misuse of the RoyalPay Cross-Border Payment Services;
        • +
        +
      • (b)the Client is deemed to have given express authorisation to the Supplier to offset or otherwise apply any unsettled funds ordinarily payable to the Client for the purpose of making good any loss caused to the Supplier by the Clients misuse of the RoyalPay Cross-Border Payment Services.
      • +
      +
    • 9.11 The Client acknowledges that all representations, covenants and warranties in this Agreement are provided on an ongoing basis and will survive the execution, performance and termination of this Agreement.
    • +
    +

    10.Confidential Information

    +
      +
    • 10.1 Subject to clause 10.2each party must:
    • +
        +
      • (a)keep confidential the Confidential Information of the other party and must ensure that it does not disclose or permit the disclosure of that Confidential Information to any person; and
      • +
      • (b)take all steps and do all things that are reasonably necessary or prudent or desirable in order to safeguard the confidentiality of the Confidential Information of the other party.
      • +
      +
    • 10.2 A party may disclose the Confidential Information of the other party:
    • +
        +
      • (a)to its Affiliates, employees, agents, advisers or financiers (each a Recipient) if, and only if:
      • +
          +
        • (i)the Recipient reasonably needs to know that Confidential Information in order for that party to comply with is obligations under this Agreement;
        • +
        • (ii)that party has informed the Recipient of the obligations of confidentiality under this Agreement; and
        • +
        • (iii)the Recipient has accepted the obligation to treat the Confidential Information as confidential in the same manner and to the same extent as required of that party;
        • +
        +
      • (b)with the consent of the other party;
      • +
      • (c)in connection with legal or other proceedings relating to this Agreement; or
      • +
      • (d)if compelled by Law or by a Government Agency, court, tribunal or stock exchange.
      • +
      +
    • 10.3 Notwithstanding clause 10.1, each party may:
    • +
        +
      • (a)disclose the other partys Confidential Information to any third party where it is necessary, on a need to know basis, in order to perform this Agreement; and
      • +
      • (b)use or disclose the other partys Confidential Information to make contact with, or to deal directly with, the Clients end users and other Personnel in connection with the provision and invoicing of the RoyalPay Cross-Border Payment Services;
      • +
      +
    • 10.4 For the purposes of this clause, the terms of this Agreement will be considered Confidential Information of each party.
    • +
    • 10.5 The rights and obligations of each party pursuant to this clause, survive the expiry or termination of this Agreement.
    • +
    +

    11.Intellectual Property

    +
      +
    • 11.1 Any Intellectual Property Rights owned by either party prior to entry into this Agreement, or developed independently of this Agreement by either party, will continue to be owned by that party. In particular, all Intellectual Property Rights pertaining to the RoyalPay Cross-Border Payment Services will at all times remain the sole and exclusive property of the Supplier.
    • +
    • 11.2 None of the Suppliers Intellectual Property Rights are transferred to the Client and, unless specifically authorised by this Agreement, the Client cannot and will not use or reproduce such Intellectual Property Rights for any purpose unconnected with this Agreement.
    • +
    • 11.3 The rights and obligations of each party pursuant to this clause survive the expiry or termination of this Agreement.
    • +
    • 11.4 The Supplier will not infringe the Intellectual Property rights of the Client or any other person in the provision of the RoyalPay Cross-Border Payment Services.
    • +
    • 11.5 If either party becomes aware of any infringement or potential infringement of the other partys Intellectual Property Rights, it must immediately notify the other party.
    • +
    • 11.6 Neither party will remove or modify any Intellectual Property Rights labels or ownership designations or symbols from any of the other partys information or data, including the TM, ® or © symbols.
    • +
    +

    12.Privacy

    +
      +
    • 12.1 Each party must comply with the Privacy Act 1988 (Cth) in respect of any personal or sensitive information (as those terms are defined in the Privacy Act 1988 (Cth)) to which it obtains access as a result of its performance of this Agreement.
    • +
    +

    13.Subcontractors

    +
      +
    • 13.1 The Supplier may not sub-contract the whole or any part of its obligations under this Agreement without the prior written consent of the Client, such consent not to be unreasonably withheld but on such reasonable conditions as the Client may impose and such consent may be withdrawn or varied by the Client in its absolute discretion.
    • +
    • 13.2 Prior to engaging any approved subcontractor, the Supplier must enter into agreements with that approved subcontractor that must include terms necessary or desirable for the Supplier to comply with all of its obligations under this Agreement.
    • +
    • 13.3 The Clients consent to a sub-contract will not relieve the Supplier of any liability, obligation, guarantee or indemnity under this Agreement. The Supplier will be liable to the Client for the acts or omissions (including negligent acts or omissions) of all subcontractors and their employees and agents as fully as if they were the acts or omissions of the Supplier or any of its employees or agents.
    • +
    +

    14.Restraint

    +
      +
    • 14.1 Neither party will ridicule or disparage the other.
    • +
    • 14.2 The Client acknowledges that the Supplier has the exclusive right to supply the WeChat Pay/Alipay/BestPay/JDpay/CB BankPaypayment technology in Australia and consequently the Client acknowledges and agrees that it is restrained from entering into any agreement with third parties relating to the provision of a service similar to the RoyalPay Cross-Border Payment Services. The exclusivity shall remain in effect for twelve (12) months from the Commencement Date of this Agreement.
    • +
    +

    15.Termination

    +
      +
    • 15.1 Without prejudice to any other rights or remedies to which the parties may be entitled, either party (TerminatingParty) may immediately terminate this Agreement without liability to the other (Non-TerminatingParty) in the event that the Non-Terminating Party:
    • +
        +
      • (a)commits a material breach of any of the terms of this Agreement and (if such a breach is remediable) fails to remedy that breach within thirty (30) calendar days of the Non-Terminating Party being notified in writing of the breach by the Terminating Party; or
      • +
      • (b)an order is made or a resolution is passed for the winding up of the Non-Terminating Party, or circumstances arise which entitle a court of competent jurisdiction to make a winding-up order in relation to the Non-Terminating Party; or
      • +
      • (c)an order is made for the appointment of an administrator to manage the affairs, business and property of the Non-Terminating Party, or documents are filed with a court of competent jurisdiction for the appointment of an administrator of the Non-Terminating Party, or notice of intention to appoint an administrator is given by the Non-Terminating Party or its directors; or
      • +
      • (d)a receiver is appointed of any of the Non-Terminating Party's assets or undertaking, or if circumstances arise which entitle a court of competent jurisdiction or a creditor to appoint a receiver or manager of the Non-Terminating Party, or if any other person takes possession of or sells the Non-Terminating Partys assets; or
      • +
      • (e)the Non-Terminating Party makes any arrangement or composition with its creditors, or makes an application to a court of competent jurisdiction for the protection of its creditors in any way; or
      • +
      • (f)the Non-Terminating Party ceases, or threatens to cease, to trade; or
      • +
      • (g)the Non-Terminating Party takes or suffers any similar or analogous action in any jurisdiction in consequence of debt.
      • +
      +
    • 15.2 Upon the termination of this Agreement for any reason:
    • +
        +
      • (a)the supply of the RoyalPay Cross-Border Payment Services provided by the Supplier to the Client pursuant to this Agreement shall cease immediately;
      • +
      • (b)each party shall return and make no further use of any equipment, property, documentation and other relevant items (including copies) belonging to the other party; and
      • +
      • (c)each party shall settle all unpaid invoices and expenses incurred under this Agreement.
      • +
      +
    • 15.3 The provisions of Clause 15.1 notwithstanding, the client may terminate this Agreement upon 30 days written notice to the supplier.
    • +
    +

    16.Liability and Indemnity

    +
      +
    • 16.1 Subject to clauses 16.2to 16.11(inclusive), each party indemnifies, holds harmless and keeps indemnified the other party against any Loss incurred or suffered by the indemnified party, resulting directly from any breach by the party providing the indemnity of any provision of this Agreement or any act, omission, misconduct or negligence of the party providing the indemnity, including (without limitation) any costs incurred in enforcing this indemnity.
    • +
    • 16.2 Without limiting any other provision in this Agreement, the Client shall fully indemnify, hold harmless and defend Flexewallet Pty Ltd (Flexewallet Pty Ltd AFSL #448066 which is a wholly owned subsidiary of publicly listed company Novatti Group Limited ASX code: NOV, both incorporated in Melbourne Australia and which has officially registered the Supplier as an Authorised Representative of their licensing regime in order to enable the offering of non-cash products and services) and its directors, officers, employees, agents, shareholders and related bodies corporate from and against all claims, demands, actions, suits, damages, liabilities, losses, settlements, judgments, costs and expenses (including but not limited to reasonable legal fees), whether or not involving a third party claim, which arise out of or relate to:
    • +
        +
      • (a)any breach of any representation or warranty of the Client contained in this Agreement;
      • +
      • (b)any breach or violation of any term, covenant, undertaking or other obligation or duty under this Agreement or under applicable Law, in each case whether or not caused by the negligence of Flexewallet Pty Ltd or any other indemnified party and whether or not the relevant claim has merit.
      • +
      +
    • 16.3 The indemnity in this Clause 16is a continuing obligation, and continues after this Agreement ends. It is not necessary for the indemnified party to suffer loss, incur expense or make payment before enforcing its right under this Clause 16.
    • +
    • 16.4 Subject always to the following clauses, to the maximum extent permitted by Law, the Supplier makes no representation nor gives any guarantee or warranty (including of merchantability, acceptable quality, fitness for any particular purpose or fitness for disclosed result) in connection with the RoyalPay Cross-Border Payment Services.
    • +
    • 16.5 There may be non-excludable statutory guarantees, implied conditions, and warranties or liabilities that the Supplier has to the Client under the Competition and Consumer Act 2010 (Cth)and other consumer protection laws that may apply to the RoyalPay Cross-Border Payment Services and that cannot be excluded (Non-Excludable Conditions or Liabilities).Nothing in this Agreement removes or limits any of the Non-Excludable Conditions or Liabilities.
    • +
    • 16.6 Notwithstanding clause 16.5, to the extent that the Suppliers liability can be limited under the Competition and Consumer Act 2010 (Cth)and other consumer protection laws that may apply to the RoyalPay Cross-Border Payment Services, the Supplier limits its liability to the Client for any breach of or non-compliance with, any of the Non-Excludable Conditions or Liabilities, at the Suppliers sole discretion, to resupplying the RoyalPay Cross-Border Payment Services or payment of, or reimbursement for, the cost of having the RoyalPay Cross-Border Payment Services resupplied, and the Client acknowledges that this limitation of liability is fair and reasonable in all the circumstances.
    • +
    • 16.7 Neither party will be liable to the other for any Consequential Loss suffered or incurred by the other party under, or in connection with this Agreement (whether based in contract, tort (including negligence), statute or otherwise.
    • +
    • 16.8 Either partys total liability to the other for any and all Loss in connection with this Agreement shall be limited to the sum of $50,000.
    • +
    • 16.9 The exclusions and limitations of liability in this clause do not apply to any Loss arising from any fraudulent act or omission of either party or its employees, agents or contractors in connection with the supply or use of the RoyalPay Cross-Border Payment Services.
    • +
    • 16.10 The Suppliers liability for any Loss suffered or incurred by the Client under, or in connection with this Agreement (whether based in contract, tort (including negligence), statute or otherwise) is reduced to the extent that:
    • +
        +
      • (a)the acts or omissions of the Client or the Clients Personnel;
      • +
      • (b)the use of any Third Party Facilities (as defined in clause 9.6); or
      • +
      • (c)the acts, omissions or equipment of a third person
      • +
      • caused or contributed to that Loss.
      • +
      +
    • 16.11 The Suppliers liability to the Client will be reduced to the extent that the Client has not taken reasonable steps to mitigate or avoid the Loss flowing from the event giving rise to the Suppliers liability to the Client.
    • +
    +

    17.Dispute Resolution

    +
      +
    • 17.1 Any dispute arising in connection with this Agreement which cannot be settled by negotiation between the parties in accordance with the procedure detailed in clause 17.2must be submitted for mediation.
    • +
    • 17.2 Prior to referring a matter to mediation, the parties must:
    • +
        +
      • (a)refer the dispute to their respective Contract Managers for consideration who must meet (either in person or via teleconference) and undertake genuine and good faith negotiations with a view to resolving the dispute or difference within five (5) Business Days of referral of the dispute; and
      • +
      • (b)if the respective Contract Managers do not resolve the dispute within five (5) Business Days, the dispute must be referred to the relevant C-level executive (or equivalent) of each party who must meet (either in person or via teleconference) and undertake genuine and good faith negotiations with a view to resolving the dispute or difference within a further ten (10) Business Days.
      • +
      +
    • 17.3 If the process in clause 17.2is unsuccessful or the parties fail or refuse to meet then a mediator will be appointed by the parties acting at all times reasonably and if not so appointed within seven (7) Business Days from actual or scheduled completion of the procedure detailed in clause 17.2, either party may refer the dispute to the President of the Law Institute of Victoria who will appoint a suitable mediator with adequate experience in mediating the type of dispute and issues in question.
    • +
    • 17.4 Mediation must take place in Melbourne, Victoria and the parties may attend mediation by way of audio-visual or such other technological means.
    • +
    • 17.5 During such dispute resolution process, the parties may be legally represented.
    • +
    • 17.6 The parties must act in good faith during the dispute resolution process with a view to resolving the dispute.
    • +
    • 17.7 The dispute resolution process procedure is confidential and any written statements prepared for the mediator or for a party and any discussion between the parties and between the parties and the mediator before or during the mediation, are made on a without prejudice basis and cannot be used in any legal proceedings.
    • +
    • 17.8 Nothing in this clause will prevent a party from seeking urgent equitable relief before an appropriate court or tribunal; or seeking orders to enforce this clause 17.
    • +
    • 17.9 Each party will bear their own costs and the costs of the mediation will be borne equally by the parties to the dispute.
    • +
    • 17.10 The parties must continue to perform their respective obligations under this Agreement pending resolution of a dispute.
    • +
    • 17.11 Resolution in a court or tribunal should only be considered as a last resort and failure to institute court or tribunal proceedings does not amount to a breach of this Agreement or this clause 17.
    • +
    • 17.12 If, in relation to a dispute, a party breaches any provision of this clause 17, each other party need not comply with this clause 17in relation to that dispute.
    • +
    • 17.13 If a party does not observe the procedure in this clause 17then this clause 17may be pleaded as a bar to any proceeding, pending the outcome of the proper observance of the procedure in this clause 17.
    • +
    +

    18.Independent Contractor

    +
      +
    • 18.1 The Client and Supplier acknowledge that they are in a relationship of principal and independent contractor and that the Supplier provides the RoyalPay Cross-Border Services in the capacity of independent contractor only. In no circumstances will the Supplier or any of its employees, agents, officers, advisers or sub-contractors claim or be deemed to be an employee, servant or agent of the Client. Further, the parties agree that:
    • +
        +
      • (a)the liabilities and obligations of the parties arising out of or in connection with this Agreement are several and not joint or joint and several; and
      • +
      • (b)a party does not have the authority to pledge the credit of or to act for the other party or to make representations, warranties or undertakings or assume obligations for or on behalf of the other party; and
      • +
      • (c)a party is not an employee of the other party and does not have any claim against the other party for superannuation, annual leave, public holiday, sick leave, long service leave, or, to the extent permitted by the Law, in respect of any claims under any Workers Compensation legislation, or on any other basis.
      • +
      +
    +

    19.Assignment

    +
      +
    • 19.1 A party must not assign its rights or obligations under this Agreement without the prior written consent of the other party (not be unreasonably withheld).
    • +
    • 19.2 Any Change of Control of a party is deemed to constitute an assignment of this Agreement for the purposes of clause 19.1and a party must not effect a Change of Control without the prior written consent of the other party (not to be unreasonably withheld).
    • +
    +

    20.Inconsistency with Other Agreements

    +
      +
    • 20.1 This Agreement, together with the SLA, constitute the entire agreement between the parties and no alternative agreements, terms, conditions, obligations or other provisions of any nature not contained in this Agreement or the SLA will be of any effect, shall add to or vary the terms and conditions of this Agreement.
    • +
    • 21.Miscellaneous Provisions
    • +
    • 21.1 Any notice to be given by any party to the other must be given by delivery to the other partys address as shown in this Agreement or as notified by any party to the other in writing as the address for notices.
    • +
    • 21.2 This Agreement will be governed by, and construed in accordance with, the Laws in force in the State of Victoria, Australia and the parties agree to submit to the non-exclusive jurisdiction of the courts in the State of Victoria, Australia.
    • +
    • 21.3 The obligations and liabilities imposed and the rights and benefits conferred on the parties under this Agreement will be binding upon and enure in favour of the respective parties and each of their respective successors in title, legal personal representatives and permitted assigns.
    • +
    • 21.4 No variation or waiver of any provision of this Agreement, nor consent to any departure by any party from its terms, will be of any effect unless it is confirmed in writing and then such variation, waiver or consent will be effective only to the extent for which it is made or given.
    • +
    • 21.5 No failure, delay or indulgence on the part of any party in exercising any power or right conferred upon such party under this Agreement will operate as a waiver of such power or right, nor will any single or partial exercise of any such power or right preclude any other future exercise of it, or the exercise of any other power or right under this Agreement.
    • +
    • 21.6 The Supplier shall have no liability to the Client under this Agreement if the Supplier is prevented from or delayed in performing its obligations, or from carrying on its business, or from providing the RoyalPay Cross-border Payment Services by virtue of a Force Majeure Event.
    • +
    • 21.7 If any provision of this Agreement will be invalid or unenforceable in accordance with its terms, it will be read down to the extent of such invalidity or unenforceability or, if incapable of such construction, it will be severed and all other provisions, which are self-sustaining and capable of separate enforcement without regard to the invalid or unenforceable provisions, will be and continue to be valid and enforceable in accordance with their terms.
    • +
    • 21.8 The parties will bear their own costs in connection with the preparation, negotiation and execution of this Agreement.
    • +
    • 21.9 Time is of the essence in this Agreement.
    • +
    • 21.10 This Agreement may be executed in any number of counterparts including by way of a countersigned PDF scanned and emailed between the parties. All counterparts will be taken to constitute one Agreement.
    • +
    +

    22.Glossary

    +
      +
    • Definitions and Interpretation
    • +
    • Definitions: In this Agreement, unless the context otherwise dictates:
    • +
    • +
    • 22.1 Acceptance Testingmeans testing of the RoyalPay Cross-Border Payment Services by the Client in order to determine whether the RoyalPay Cross-Border Payment Services meet the Clients needs and expectations.
    • +
    • 22.2 Agreementmeans these terms and conditions and the Schedules.
    • +
    • 22.3 Affiliatemeans, in respect of a party:
    • +
        +
      • (a)the partys related bodies corporate (as that term is defined in the Corporations Act 2001 (Cth)) or any other bodies corporate associated with the party (including anybody corporate one of whose directors is also a director of the party); and
      • +
      • (b)Without limiting paragraph (a) of this definition, if the Client is a franchisee, its franchisor and each of the franchisors related bodies corporate.
      • +
      +
    • 22.4 BusinessDaymeans a day that falls between Monday and Friday (inclusive) and is not public holiday in bothAustraliaand China.
    • +
    • 22.5 Change of Controlmeans a change in the ownership or control (either directly or indirectly) of more than 50% of the voting share capital of the relevant undertaking or the ability to direct casting of more than 50% of the votes exercisable at general meetings of the relevant undertaking;
    • +
    • 22.6 CommencementDatemeans the date of this Agreement.
    • +
    • 22.7 ConfidentialInformationmeans all information related to the Supplier or the Client and includes, but is not limited to, this Agreement , any alliances, artwork, budgets, business affairs, client details, colour schemes, concepts, copyrightable work, customer list, data, designs, drawings, expertise, financials, formulae, functionality, human resources, ideas, information, Intellectual Property, intentions, inventions, joint ventures, know-how, layout, logos, literary works, market opportunities, methodologies, operations, partners, passwords, patent, personnel, plans, policies, procedures, processes, production information, projections, questions/answers, research, schematics, shareholders, slogans, software code, specifications, statistics, studies, supplier details, systems, trademarks (registered or otherwise), trade secrets, web developments, written materials, in any form or media, whether in writing or oral.
    • +
    • 22.8 ConsequentialLossmeans:
    • +
        +
      • (a)any loss of revenue, loss of profits, loss of anticipated savings or business, pure economic loss, loss of data, loss of value of equipment (other than cost of repair), loss of opportunity or expectation loss even if any of that Loss arises naturally (according to the usual course of things) from the fact, matter or circumstance giving rise to the Loss;
      • +
      • (b)any Loss that is suffered or incurred by a party as a result of a fact, matter or circumstance which does not arise naturally (that is, according to the usual course of things) from the fact, matter or circumstance giving rise to the Loss;
      • +
      • (c)any penalties imposed by a Government Agency; but
      • +
      • (d)this definition does not include PCI Fines.
      • +
      +
    • 22.9 ContractManageris as defined in clause 2.1.
    • +
    • 22.10 Feesmean the fees payable in respect of the RoyalPay Cross-Border Payment Services as detailed in Schedule B.
    • +
    • 22.11 ForceMajeureEventmeans a circumstance beyond a parties reasonable control, including acts or omissions of third party network operators or service providers, other than the partys Affiliates, fire, flood, earthquake, elements of nature or acts of God, acts of war, terrorism, riots, civil disorders, rebellions or revolutions, strikes or lockouts other than industrial action, howsoever described of the party or partys Personnel.
    • +
    • 22.12 GovernmentAgencymeans any governmental, semi-governmental, administrative, fiscal, statutory, judicial or quasi-judicial body, department, commission, authority, tribunal, agency or entity having operation or jurisdiction within Australia or anywhere else in the world.
    • +
    • 22.13 GSTmeans goods and services tax or any similar tax imposed by reason of a supply of goods or services under or in connection with this Agreement;
    • +
    • 22.14 Initial Termis as set out in Schedule B.
    • +
    • 22.15 IntellectualPropertymeans registered and unregistered business names, copyright, patents, trade marks, trade names, domain names, social media handles, know how, designs, plans, ideas, trade secrets, concepts, discoveries, inventions, brands, specifications and similar industrial and commercial intellectual property in Australia and worldwide, whether capable of registration or otherwise, both subsisting now or coming into existence at any time after the Commencement Date and includes the Confidential Information.
    • +
    • 22.16 IntellectualPropertyRightsmeans all and any patents, trademarks, service marks, trade names, registered designs, unregistered design rights, copyrights and rights in confidential information, and all and any other intellectual property rights, whether registered or unregistered, and including all applications and rights to apply for any of the same.
    • +
    • 22.17 Lawincludes any statute, rule, regulation, proclamation, order, ordinance or by-law (whether federal, state, territorial or local), codes of practice, Australian Standards and compliance codes.
    • +
    • 22.18 Lossincludes all loss, damage or liability (including liability to a third party) of any kind.
    • +
    • 22.19 PCI Finemeans any fine or other financial penalty imposed on the Supplier by a bank as a result of the Supplier being non-compliant with the then-current version of the Payment Card Industry Data Security Standard.
    • +
    • 22.20 PaymentTermsis defined in clause Error! Reference source not found..
    • +
    • 22.21 Personnelmeans in respect of a party, the employees, agents, contractors and Affiliates of the party and the employees, agents, contractors of the partys Affiliates and includes, in the case of the Client, the Clients end users of the RoyalPay Cross-Border Payment Services.
    • +
    • 22.22 RenewedTermis defined in clause 3.2.
    • +
    • 22.23 RoyalPay Cross-Border Payment Services mean the services to be supplied by the Supplier to the Client in accordance with this Agreement, namely the services described in Schedule B.
    • +
    • 22.24 Schedulesmean the schedules and appendices to this Agreement.
    • +
    • 22.25 SLAmeans the service level agreement separately provided to the Client by the Supplier and which may be amended by the Supplier in its absolute discretion from time to time.
    • +
    • 22.26 Tax Invoicehas the same meaning as that given in section 195-1 of A New Tax System (Goods and Services Tax) Act 1999 (Cth).
    • +
    • 22.27 Termis defined in clause 3.1.
    • +
    • 22.28 Third Party Facilitiesis defined in clause 9.6.
    • +
    • Interpretation: In this Agreement, unless the context otherwise dictates:
    • +
    • +
    • 22.29 Includes:the words includes, include, including, for example or such as must be read to mean includes, but is not limited to.
    • +
    • 22.30 SingularandPlural:words importing the singular (where appropriate) mean and include the plural and vice versa.
    • +
    • 22.31 Gender:words importing any one gender (where appropriate) mean and include the other genders and vice versa.
    • +
    • 22.32 NaturalPersons:words importing natural persons (where appropriate) mean and include corporations and unincorporated associations and vice versa.
    • +
    • 22.33 Headings:the headings are for convenience of reference only and are not to be construed as affecting the meaning or interpretation of this Agreement.
    • +
    • 22.34 StatutoryEnactments:all references in this Agreement to Law mean and are to be construed as references to that Law as amended or modified, consolidated, re-enacted or replaced from time to time and to the corresponding provisions of any similar Law of any other relevant jurisdiction.
    • +
    • 22.35 ReferencestopartsofthisAgreement:all references in this Agreement to sections, articles, clauses, sub-clauses, paragraphs and schedules mean, and are to be construed as, references to the sections, articles, clauses, sub-clauses, paragraphs and schedules of this Agreement.
    • +
    • 22.36 Party:references to any party to this Agreement or any other document or agreement include its successors, permitted assigns, heirs or legal personal representatives and includes company administrators, liquidators and trustees in bankruptcy.
    • +
    • 22.37 Covenants:any covenants, representations, warranties, indemnities or guarantees made or given by more than one party to another party hereunder are, unless specified otherwise, made or given jointly and severally.
    • +
    • 22.38 Wholeincludespart:a reference to anything includes the whole or any part of that thing and a reference to a group of things or persons includes each thing or person in that group.
    • +
    • 22.39 Partsofspeech:where any word or phrase is given a defined meaning, any other part of speech or other grammatical form of that word or phrase has a corresponding meaning.
    • +
    • 22.40 BusinessDay:Where any obligation under this Agreement falls to be performed on a day other than a Business Day, this Agreement must be construed as requiring that obligation to be performed on the next Business Day.
    • +
    • 22.41 Currency:References to $ or Dollars are references to Australian dollars, unless expressly indicated otherwise.
    • +
    • 22.42 Calendar:a reference to a day, week, month or a year is a reference to a calendar day, week, month or a year.
    • +
    • 22.43 Contraproferentemexcluded:a provision must not be construed against a party merely because that party was responsible for preparing this Agreement or that provision.
    • +
    • 22.44 Pre-Authorisation: refers to system functions which allow the placement of funds on hold for a pre-determined time, giving the Client the full control to exercise the option of completing the transaction at a later date.
    • +
    +
    +
    + + + +
      +
    • CONTRACTING PARTY DETAILS
    • +
    + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +

    +
    +

    THE SUPPLIER

    +
    +

    THE CLIENT

    +
    +

    Name:

    +
    +

    Tunnel Show Pty Ltd trading as RoyalPay

    +
    +

    +
    +

    ACN:

    +
    +

    601 619 685

    +
    +

    +
    +

    Address:

    +
    +

    Level 11

    +

    15 William Street
    Melbourne Vic 3000

    +
    +

    {{aggregateFileInfo.address}}

    +

    {{aggregateFileInfo.address_sub}}
    {{aggregateFileInfo.suburb}} {{aggregateFileInfo.state}} {{aggregateFileInfo.postcode}}

    +
    +

    Contact Person:

    +
    +

    +
    +

    +
    +

    Position:

    +
    +

    +
    +

    +
    +

    Phone:

    +
    +

    +
    +

    +
    +

    Fax:

    +
    +

    +
    +

    +
    +

    Email:

    +
    +

    +
    +

    +
    +
    + +

    +
      +
    • PARTICULARSOF THE ROYALPAY CROSS-BORDER PAYMENT SERVICES
    • +
    +

    + + + + + + + + + + + + + + + + + + + + + + + +
    +

    Contract Manager (cl. 2)

    +
    +

    +
    +

    Initial Term (cl. 3)

    +
    +

    Twelve(12) months (unless terminated earlier in accordance with the terms of this Agreement)

    +
    +

    Description of the RoyalPay Cross-Border Services

    +
    +

    A software application and platform developed by the Supplier which provides Clients (who have web-based gateway APIs (being application programming interfaces) and point-of-sale payment processing and settlement capabilities in the Chinese currency) with a payment integration system, which:

    +

    (a)utilises the WeChat Pay payment technology that is owned by Tencent Holdings Limited (China) and that is licensed to the Supplier to facilitate third party payments in Australia;

    +

    (b)utilises the Alipay payment technology that is owned by Alipay.com Co., Ltd (China) and is licensed to the Supplier to facilitate third party payments in Australia;

    +

    (c)utilises the BestPaypayment technology that is owned by China Telecom Orange Finance and is licensed to the Supplier to facilitate third party payments in Australia;

    +

    (d)utilises the JDpaypayment technology that is owned by Chinabank Payment (Beijing) Technology Co.,Ltd.and is licensed to the Supplier to facilitate third party payments in Australia;

    +

    (e)utilises CB BankPay Payment technology licensed to the Supplier by Lakala Payment Co., Ltd, Yeepay Co., Ltd, PnR Data Service Co.,Ltdand other companies to facilitate third party payments in Australia;

    +

    (f)processes payments from users of Chinese bank cards and the WeChat Pay/Alipay/BestPay/JDpay/CB BankPay payment technology in Chinese Yuan, converts such payments to the Australian Dollar equivalent and then sends such payments to the Client by utilising settlement functions forming part of the software application;

    +

    (g)allows the Client to access the merchant management portal feature of the software application which facilitates transaction management, reporting and provides transactional analytics; and

    +

    (h)provides the Client with access to additional unique WeChat Pay marketing features including WeChat Lucky Money (an incentive program where payers randomly receive cashbacks) and WeChat Moment (being social media advertising aimed at enhancing a Clients brand awareness).

    +
    +

    Commencement Date

    +
    +

    The date of this Agreement

    +
    +

    Fees

    +
    +

    An integration fee and ongoing maintenance fee for the RoyalPay Cross-Border Payment Services shall be AUD $0, plus GST.

    +

    In addition, the Supplier shall charge a Merchant Service Fee (MSF) to the Client, per transaction, which shall be calculated in a percentage amount. The calculation schedule is as follows:

    +

    ·the MSF is {{aggregateFileInfo.wechat_rate}}%(GST exclusive) of payments made using the RoyalPay Cross-Border Payment Services; (WeChat Pay)

    +

    ·the MSF is {{aggregateFileInfo.alipay_rate}}%(GST exclusive) of payments made using the RoyalPay Cross-Border Payment Services; (AlipayOffline)

    +

    ·the MSF is {{aggregateFileInfo.alipay_online_rate}}%(GST exclusive) of payments made using the RoyalPay Cross-Border Payment Services; (AlipayOnline)

    +

    ·the MSF is {{aggregateFileInfo.bestpay_rate}}%( GST exclusive) of payments made using the RoyalPay Cross-Border Payment Services; (Bestpay)

    +

    ·the MSF is {{aggregateFileInfo.jd_rate}}%(GST exclusive) of payments made using the RoyalPay Cross-Border Payment Services; (JDpay)

    +

    ·the MSF is {{aggregateFileInfo.cbbank_rate}}%(GST exclusive) of payments made using the RoyalPay Cross-Border Payment Services; (CBBankPay)

    +

    ·the Supplier reserves the right to adjust the MSF rate mentioned above without prior consent from the Client. The Supplier will notify the Client of such change through the Suppliers website, portal and/or by email thirty (30) calendar days in advance before the MSF rate is adjusted;

    +

    ·the parties agree that, when calculating the MSF, the figure shall be rounded to two decimal places; and

    +

    ·the Client may elect to pass on the MSF to its customers or to bear the MSF itself, as per system configuration.

    +
    +

    + + + + + + + + + + + +
    +

    Settlement Method

    +
    +

    The Supplier shall, after deducting all agreed service charges payable to the Supplier from the corresponding transaction, remit the balance to the Client by ways of Electronic Fund Transfer to the Client's nominated bank account in the currency of Australian Dollars (AUD). If the Client changes the bank account for any reason, it shall promptly give a valid notification to the Supplier of such change seven (7) calendar days in advance, and shall provide any other information required by the Supplier. Any Loss arising from an un-notified change of bank account details shall be solely borne by the Client.

    +

    Particulars of the bank account nominated by the Client:

    +
      +
    • ·Name of the bank:{{aggregateFileInfo.bank}}
    • +
    • ·Country where the bank is located:Australia
    • +
    • ·BSB:{{aggregateFileInfo.bsb_no}}
    • +
    • ·Account number:{{aggregateFileInfo.account_no}}
    • +
    • ·Account name:{{aggregateFileInfo.account_name}}
    • +
    +
    +

    Settlement Terms

    +
    +

    Settlement Cycle: {{aggregateFileInfo.clean}}. Funds will be settled on or before{{aggregateFileInfo.clean_days}}Business Day after the transaction has occurred.

    +

    For Settlement Cycle T+1: the provision of the above Settlement Cycle notwithstanding, the transaction occurred on Friday will be settled on the following Monday, and the transaction occurred on Saturday and Sunday will be settled on the following Tuesday.

    +

    +

    Settlement Amount Calculation:

    +

    ·If the Client elects to pass on the MSF to its customers, then the Client shall receive the full face-value settlement amount (e.g. the Client shall invoice its customers $100AUD and the Client shall receive $100AUD); or

    +

    ·If the Client elects to bear the MSF itself, then the Client shall receive the full face-value settlement amount minus the MSF applied to the face value (e.g. the Client shall invoice its customers $100AUD and the Client shall receive $100AUD - $100 x MSF)

    +

    The Client acknowledges that the actual time of receipt of the payment is based on the notice that provided by Supplier.

    +

    The Client shall not be affected by any foreign exchange movement regarding payment processing and settlement, as indicated above in the Settlement Amount Calculation. During the payment process, the Clients customers will be prompted with daily AUD/CNY spot rate on the payment page.

    +

    The Client shall not be subject to any chargebacks in the event of transaction fraud, technical error or administrative error unless caused or contributed to by the Client. Any and all investigative and transactional duties in such events will be carried out by Tencent Holdings Limited (China), the provider of WeChat Pay technology; Alipay.com Co., Ltd. (China), the provider of Alipay payment technology;China Telecom Orange Finance, the provider of BestPaypayment technology; Chinabank Payment Technology, the provider of JDpay payment technology; andLakala Payment Co., Ltd, Yeepay Co., Ltd, PnR Data Service Co.,Ltdand other companies, the providers of CB BankPay technology.

    +
    +

    + + + + + + + +
    +

    Refund Process

    +
    +

    Any authorisation-only or non-delivery or disputes or defects or warranty issues etc., arising from the Client's services rendered to its customers may incur refunds in the normal course of business after a transaction has been completed, which shall be dealt with in accordance with the following procedures:

    +

    ·the Client shall be solely responsible for investigating and initiating refunds to its customers;

    +

    ·if the related refund amount has been settled to the Client by the Supplier, the Client shall be solely responsible for processing the refund amount as per the Client's refund policy with its customers;

    +

    ·if the related refund amount has not been settled to the Client by the Supplier, the Client can either: a) refund the amount as per the Client's refund policy to its customers, using its own funds prior to receiving the settlement amount; or b) the Client may make a refund request to the Supplier and the Supplier shall debit directly the refund amount from the unsettled amount payable to Client in order to initiate the refund;

    +

    ·Due to settlement turnarounds and bank operations, it is possible that the unsettled funds with the Client may be insufficient to cover the actual refund amount, in which case the Client shall make the refund request to the Supplier at a later time when the required amount becomes available, given that the Client continues accumulating further unsettled transactions or alternatively, the Client may resort to a combination of method a) and b) above;

    +

    ·except in the event that the Client ceases its business functions and is unable to refund its customers, the Supplier may request during the application process, a refundable security deposit to be paid by the Client upfront in order to cover such refund scenarios;

    +

    ·the Client shall reserve the right to investigate the transaction before the refund is given and shall ultimately decide whether or not the refund is to be given; and

    +

    ·the Supplier shall not impose any handling fee for any refund request.

    +
    +


    Service Level Agreement

    +

    Key contact details:

    + + + + + + + +
    +

    Client Support

    +
    +

    Email: info@royalpay.com.au

    +

    Contact: Customer Service Officer

    +
    +

    + + + + + + + +
    +

    Commercial Support

    +
    +

    Email: Mona.Zhang@royalpay.com.au

    +

    Contact: MonaZhang

    +
    +

    + + + + + + + +
    +

    Technical Support

    +
    +

    Email: leo.huang@royalpay.com.au

    +

    Contact: Leo Huang

    +
    +

    + + + + + + + +
    +

    Financial Support

    +
    +

    Email: accountofficer@royalpay.com.au

    +

    Contact: Account Officer

    +
    +

    + + + + + + + +
    +

    Compliance Support

    +
    +

    Email: Compliance@royalpay.com.au

    +

    Contact: Compliance

    +
    +

    +

    Recommended Information to be included in the support email:

    +


    - Nature of the issue
    + - Date and time of occurrence
    + - Impact on operation

    +


    Level of Support:

    +


    RoyalPay provides three (3) levels of support: Level 1, Level 2, Level 3. The Client needs to identify the nature of the issue and appropriate level of support in order for RoyalPay to achieve optimal client support experience, and Client should use always use Level 1 support as priority support option.

    +


    Level 1 support functions include:

    +


    - First level of contact for Client Service Representatives
    - General facilitation of day-to day operations
    - System monitoring, including regular checking of system mail, error logs, alarms, system performance, utilisation and capacity, user logins, etc
    - Access and interpretation of system logs and traces
    - Archiving of data and backup related to RoyalPay platform(if applicable)
    - Ensuring and maintaining remote access
    - Loading authorised software update
    - Attend to escalated problems from the Client Service Department
    - Attend to internal business support
    - System, and where practical, network reporting
    - Within reason and on a best endeavours basis, RoyalPay will provide limited reporting based fault recognition, isolation and forward to Level 2

    +


    Level 2 support functions include:

    +

    - Diagnosis of Hardware and Software faults
    + - Monitoring progress
    + - Problem resolution
    + - Where the Client has purchased hardware directly from RoyalPay
    + - RoyalPay will process hardware faults through to the original hardware vendors standard support infrastructure and leverage off standing agreements and nominated response times
    + - Escalation to Head Office/Technical Team or Level 3 support as required

    +


    Level 3 support functions include:

    +

    - Any problem as escalated from Level 2.
    - Interaction with Level 1 & 2 support and 3rd parties as relevant.
    - Acting as interface with 3rd Party software support organisations

    +

    +

    Support not included:

    +

    - Rectification of defects or errors resulting from any modification of the system not approved by RoyalPay
    + - Rectification of defects or errors resulting from use of the system in combination with anything not forming part of the original intent of the system
    + - Rectification of a fault in anything not forming part of the system
    - Any modification of the system which represents a departure from the specifications
    + - Electrical or environmental adjustment or damage caused by electrical or environmental factors
    + - Repair or damage arising out of misuse or unauthorised use of the system by or any person
    + - Furnishing accessories
    + - Any rectification arising from any substitution, alteration or relocation of the System made by any person that is not approved by RoyalPay
    + - Rectification of errors or defects which are the subject of a warranty under another agreement
    + - Any rectification arising from failing to install supplied updates or upgrades
    + - Any defect arising from the use of the system other than in accordance with applicable user ocumentation
    + - Rectification of errors or defects resulting from improper operation of the system by any person
    + - Any defect arising from misuse, negligence or abuse of the software and/or database(s)
    + - Any rectification arising from failing to comply with its obligations under these terms.
    + -Maintenance of hardware or items external to the system, or for added accessories for which maintenance charges have not been paid
    + - Engineering modifications (other than those normally performed as part of RoyalPay support)
    + - Supply and installation of consumables, or damage caused by use of incompatible consumables
    + - Rectification of problems caused by the connection of or use of third party products, hardware and /or software which are not maintained by RoyalPay
    + - Refurbishment of product, or damage or wear due to overuse or use outside published specifications
    + - Hardware and/or Software covered by third party maintenance agreements

    +

    +

    Support Priority and Response time:

    +

    The normal response time is 9am – 6pm, Mon – Fri (Excluding public holidaysin both Australia and China)

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +

    Criticality

    +
    +

    Description

    +
    +

    Client Notification Time

    +
    +

    Response Time

    +
    +

    Resolution

    +
    +

    1

    +
    +

    RoyalPay system is down

    +
    +

    Instantaneous

    +
    +

    2 hours

    +
    +

    5 hours

    +
    +

    2

    +
    +

    Major problem inhibiting operation

    +
    +

    Instantaneous

    +
    +

    4 hours

    +
    +

    12 hours

    +
    +

    3

    +
    +

    Minor problem causing inconvenience

    +
    +

    Instantaneous

    +
    +

    24 hours

    +
    +

    14 days

    +
    +

    4

    +
    +

    Other commercial/financial request

    +
    +

    N/A

    +
    +

    48 hours

    +
    +

    Tentative

    +
    +


    Emergency/After Hour Contact:

    +


    To ensure high availability and reliability of RoyalPays uninterrupted services, any serious issue/after hour escalation contact details might be established by agreed methods between RoyalPay and the Client. Once established, an amended SLA shall be advised to the Client.

    +

    +

    +

    Execution Page

    +

    +

    Executed as an agreement by being signed in accordance with section 127 of the Corporations Act2001 (Cth) by:

    +

    +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +

    Tunnel Show Pty Ltd (ACN 601 619 685) trading as RoyalPay

    +
    +

    Signature of Director/Secretary:

    +
    +

    +
    +

    +
    +

    Print Full Name:

    +
    +

    Yangfan Ge

    +
    +

    +
    +

    Position:

    +
    +

    Director

    +
    +

    Director/Secretary

    +
    +

    Date:

    +
    +

    +
    +

    +
    +
    + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    +

    Client Name/ACN:

    +
    +

    Signature of Director/Secretary: +

    + +
    + + +

    +
    +

    Print Full Name:

    + +
    + + +

    +
    +

    Position:

    +
    +

    Director

    +
    +

    Director/Secretary

    +
    +

    Date:

    +
    +

    +
    +

    +
    +
    + + +
    +
    + + +
    +
    +
    +
    +
    + + + + + + + + + diff --git a/src/main/ui/static/payment/partner/templates/partner_auth_files.html b/src/main/ui/static/payment/partner/templates/partner_auth_files.html index b2e069690..f0d80fc59 100644 --- a/src/main/ui/static/payment/partner/templates/partner_auth_files.html +++ b/src/main/ui/static/payment/partner/templates/partner_auth_files.html @@ -26,8 +26,25 @@ - - + + + + + + + + +
    1 + + + + +
    + +
    @@ -50,8 +67,18 @@
    - - + + + + + + +
    1 + + + +
    @@ -82,8 +109,19 @@
    - - + + + + + + + +
    1 + + + +
    @@ -115,8 +153,19 @@
    - - + + + + + + + +
    1 + + + +
    @@ -141,8 +190,19 @@
    - - + + + + + + + +
    1 + + + +
    @@ -162,4 +222,4 @@ - \ No newline at end of file + diff --git a/src/main/ui/static/payment/partner/templates/partner_compliance_detail.html b/src/main/ui/static/payment/partner/templates/partner_compliance_detail.html new file mode 100644 index 000000000..c4b48361a --- /dev/null +++ b/src/main/ui/static/payment/partner/templates/partner_compliance_detail.html @@ -0,0 +1,256 @@ +
    +

    + +

    +
    + +
    +
    +
    + + +
    +
    +
    diff --git a/src/main/ui/static/payment/partner/templates/partner_detail_for_compliance.html b/src/main/ui/static/payment/partner/templates/partner_detail_for_compliance.html new file mode 100644 index 000000000..8b8b7d8a6 --- /dev/null +++ b/src/main/ui/static/payment/partner/templates/partner_detail_for_compliance.html @@ -0,0 +1,301 @@ + + +
    +
    +
    + +
    +
    +
    + diff --git a/src/main/ui/static/payment/partner/templates/partners.html b/src/main/ui/static/payment/partner/templates/partners.html index 016b5e155..b33cea5ee 100644 --- a/src/main/ui/static/payment/partner/templates/partners.html +++ b/src/main/ui/static/payment/partner/templates/partners.html @@ -386,7 +386,7 @@ ng-click="loadPartners(1)"> Search - diff --git a/src/main/ui/static/payment/partner/templates/refuse_reason.html b/src/main/ui/static/payment/partner/templates/refuse_reason.html new file mode 100644 index 000000000..88e379158 --- /dev/null +++ b/src/main/ui/static/payment/partner/templates/refuse_reason.html @@ -0,0 +1,25 @@ + + + diff --git a/src/main/ui/static/sys/templates/partner_compliance_for_client.html b/src/main/ui/static/sys/templates/partner_compliance_for_client.html new file mode 100644 index 000000000..1f1d6a447 --- /dev/null +++ b/src/main/ui/static/sys/templates/partner_compliance_for_client.html @@ -0,0 +1,128 @@ +
    +

    商户合规文件审核

    + +
    + +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    +

    + All | + + 待审核| + 通过| + 打回 +

    +
    +
    + +
    +
    +
    +
    +
    + +
    + + +