1、前言
关于合单支付,是怎样一个合单的效果,也许在接入API之前,都会有一种疑惑。这几天利用空余时间,撸了一个后台,主要是二级商户的进件,和合单支付在线体验。
2、关于商户进件
微信支付服务商可以帮助商户申请商户号,最重要的一步就是商户进件,这里选择进件小微商户为例,关于小微商户,微信给的定义就是指无营业执照的个人商家,应该是最简单的一类商户。
这个进件没什么好说的,就按照文档传入api要求的必传参数就好了,就是有一个比较麻烦的地方,所有关于图片的字段都要先调用上传图片接口预上传获取mediaId,还有一些和隐私相关的字段,比如身份证姓名、身份证号码都需要先加密再上传。
预览:
代码实现
if (StringUtils.equals("Y", merchant.getSubmitInfo())) {
WxSftConfig config = sysConfigService.getConfigObject(WxSftConfig.WX_SFT_PARAMS_CONFIG_KEY, WxSftConfig.class);
String appName = config.getAppName();
String mchId = config.getMchId();
String mchSerialNo = config.getMchSerialNo();
String wechatSerialNo = config.getWechatSerialNo();
String mchPrivateKeyPath = config.getMchPrivateKeyPath();
String wechatPubKeyPath = config.getWechatPubKeyPath();
BaseParam baseParam = new BaseParam();
baseParam.setAppName(appName)
.setMchId(mchId)
.setMchSerialNo(mchSerialNo)
.setWechatSerialNo(wechatSerialNo)
.setMchPrivateKeyPath(mchPrivateKeyPath)
.setWechatPubKeyPath(wechatPubKeyPath);
try {
String organizationType = merchant.getOrganizationType();
if (StringUtils.isEmpty(merchant.getOutRequestNo())) {
merchant.setOutRequestNo(RandomNum.randomUtil() + Date2Utils.getCurrentTime() + System.currentTimeMillis());
}
MerchantApplyParam param = new MerchantApplyParam();
param.setAppName(appName)
.setMchId(mchId)
.setMchSerialNo(mchSerialNo)
.setWechatSerialNo(wechatSerialNo)
.setMchPrivateKeyPath(mchPrivateKeyPath)
.setWechatPubKeyPath(wechatPubKeyPath);
//业务申请编号
param.setOut_request_no(merchant.getOutRequestNo());
//主体类型
param.setOrganization_type(organizationType);
//营业执照/登记证书信息,主体为“小微/个人卖家”时,不填
BusinessLicenseInfo businessLicenseInfo = new BusinessLicenseInfo();
if (OrganizationType.ENTERPRISE.getType().equals(organizationType) || OrganizationType.INSTITUTION.getType().equals(organizationType)
|| OrganizationType.INDIVIDUAL_BUSINESS.getType().equals(organizationType) || OrganizationType.OTHER_ORGANIZATION.getType().equals(organizationType)) {
if (StringUtils.isNotEmpty(merchant.getBusinessLicenseCopy())) {
JSONObject bizLicenseMedia = Upload.image(merchant.getBusinessLicenseCopy(), baseParam);
int bizLicenseStatus = bizLicenseMedia.getInteger("requestStatus");
if (bizLicenseStatus == 200) {
businessLicenseInfo.setBusiness_license_copy(bizLicenseMedia.getString("media_id"));
} else {
return AjaxResult.error(bizLicenseMedia.getString("message"));
}
}
}
//证件注册号
businessLicenseInfo.setBusiness_license_number(merchant.getBusinessLicenseNumber());
//商户名称
businessLicenseInfo.setMerchant_name(merchant.getMerchantName());
//经营者/法定代表人姓名
businessLicenseInfo.setLegal_person(merchant.getLegalPerson());
if (OrganizationType.INSTITUTION.getType().equals(organizationType)
|| OrganizationType.OTHER_ORGANIZATION.getType().equals(organizationType)) {
businessLicenseInfo.setCompany_address(merchant.getCompanyAddress());
businessLicenseInfo.setBusiness_time(merchant.getBusinessTime());
}
param.setBusiness_license_info(businessLicenseInfo);
//经营者/法人证件类型,默认身份证
merchant.setIdDocType("IDENTIFICATION_TYPE_MAINLAND_IDCARD");
param.setId_doc_type("IDENTIFICATION_TYPE_MAINLAND_IDCARD");
IdCardInfo idCardInfo = new IdCardInfo();
//身份证人像面照片
if (StringUtils.isNotEmpty(merchant.getIdCardCopy())) {
JSONObject copyMedia = Upload.image(merchant.getIdCardCopy(), baseParam);
int copyStatus = copyMedia.getInteger("requestStatus");
if (copyStatus == 200) {
idCardInfo.setId_card_copy(copyMedia.getString("media_id"));
} else {
return AjaxResult.error(copyMedia.getString("message"));
}
}
//身份证国徽面照片
if (StringUtils.isNotEmpty(merchant.getIdCardNational())) {
JSONObject nationalMedia = Upload.image(merchant.getIdCardNational(), baseParam);
int nationalStatus = nationalMedia.getInteger("requestStatus");
if (nationalStatus == 200) {
idCardInfo.setId_card_national(nationalMedia.getString("media_id"));
} else {
return AjaxResult.error(nationalMedia.getString("message"));
}
}
//身份证姓名,加密姓名
String encryptName = SignUtils.rsaEncryptOAEP(merchant.getIdCardName(), CertificateUtils.getCertificate(wechatPubKeyPath));
//身份证号码,加密身份证号
String encryptIdCard = SignUtils.rsaEncryptOAEP(merchant.getIdCardNumber(), CertificateUtils.getCertificate(wechatPubKeyPath));
//该字段需进行加密处理
idCardInfo.setId_card_name(encryptName);
//该字段需进行加密处理
idCardInfo.setId_card_number(encryptIdCard);
//身份证有效期限
if ("Y".equals(merchant.getIdCardValidTimeType())) {
merchant.setIdCardValidTime("长期");
}
//身份证开始时间
idCardInfo.setId_card_valid_time_begin(merchant.getIdCardValidTimeBegin());
idCardInfo.setId_card_valid_time(merchant.getIdCardValidTime());
// 主体类型为企业时,身份证居住地址
if (OrganizationType.ENTERPRISE.getType().equals(organizationType)) {
String encryptAddress = SignUtils.rsaEncryptOAEP(merchant.getIdCardAddress(), CertificateUtils.getCertificate(wechatPubKeyPath));
idCardInfo.setId_card_address(encryptAddress);
}
param.setId_card_info(idCardInfo);
// 主体类型为企业时
if (OrganizationType.ENTERPRISE.getType().equals(organizationType)) {
param.setOwner(true);
}
//结算银行账户
AccountInfo accountInfo = new AccountInfo();
//账户类型
accountInfo.setBank_account_type(merchant.getBankAccountType());
if (StringUtils.equals("其他银行", merchant.getRemark())) {
accountInfo.setAccount_bank(merchant.getAccountBank());
accountInfo.setBank_name(merchant.getBankName());
} else {
accountInfo.setAccount_bank(merchant.getRemark());
accountInfo.setBank_name(accountInfo.getAccount_bank());
merchant.setAccountBank(merchant.getRemark());
merchant.setBankName(merchant.getRemark());
}
//该字段需进行加密处理
if (BankAccountType.PUBLIC_ACCOUNT.getType().equals(merchant.getBankAccountType())) {
accountInfo.setAccount_name(SignUtils.rsaEncryptOAEP(merchant.getMerchantName(), CertificateUtils.getCertificate(wechatPubKeyPath)));
} else if (BankAccountType.PRIVATE_ACCOUNT.getType().equals(merchant.getBankAccountType())) {
accountInfo.setAccount_name(encryptName);
}
accountInfo.setBank_address_code(merchant.getBankAddressCode());
//银行帐号,该字段需进行加密处理
accountInfo.setAccount_number(SignUtils.rsaEncryptOAEP(merchant.getAccountNumber(), CertificateUtils.getCertificate(wechatPubKeyPath)));
param.setAccount_info(accountInfo);
//店铺信息
SalesSceneInfo salesSceneInfo = new SalesSceneInfo();
//店铺名称
salesSceneInfo.setStore_name(merchant.getStoreName());
//店铺二维码
if (StringUtils.isNotEmpty(merchant.getStoreQrCode())) {
JSONObject storeMedia = Upload.image(merchant.getStoreQrCode(), baseParam);
int storeStatus = storeMedia.getInteger("requestStatus");
if (storeStatus == 200) {
salesSceneInfo.setStore_qr_code(storeMedia.getString("media_id"));
} else {
return AjaxResult.error(storeMedia.getString("message"));
}
}
param.setSales_scene_info(salesSceneInfo);
//商户简称
param.setMerchant_shortname(merchant.getMerchantShortname());
//处理特殊资质
if (StringUtils.isNotEmpty(merchant.getQualifications())) {
JSONObject qualifiMedia = Upload.image(merchant.getQualifications(), baseParam);
int qualifiStatus = qualifiMedia.getInteger("requestStatus");
if (qualifiStatus == 200) {
List<String> list = new ArrayList<>();
list.add(qualifiMedia.getString("media_id"));
param.setQualifications(JSON.toJSONString(list));
} else {
return AjaxResult.error(qualifiMedia.getString("message"));
}
}
//处理补充材料
if (StringUtils.isNotEmpty(merchant.getBusinessAdditionPicsons())) {
JSONObject picsonMedia = Upload.image(merchant.getBusinessAdditionPicsons(), baseParam);
int picsonStatus = picsonMedia.getInteger("requestStatus");
if (picsonStatus == 200) {
List<String> list = new ArrayList<>();
list.add(picsonMedia.getString("media_id"));
param.setBusiness_addition_pics(JSON.toJSONString(list));
} else {
return AjaxResult.error(picsonMedia.getString("message"));
}
}
JSONObject applyResult = ecommerceService.applyments(param);
int applyStatus = applyResult.getInteger("requestStatus");
if (applyStatus == 200) {
merchant.setApplymentId(applyResult.getString("applyment_id"));
} else {
return AjaxResult.error(applyResult.getString("message"));
}
if (merchant.getId() == null) {
merchant.setStoreNo(RandomNum.randomUtils(5) + Date2Utils.getCurrentTime());
merchant.setCreateTime(DateUtils.getNowDate());
merchantMapper.insertMerchant(merchant);
} else {
merchant.setUpdateTime(DateUtils.getNowDate());
merchantMapper.updateMerchant(merchant);
}
return AjaxResult.success();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
return AjaxResult.error(e.getMessage());
} catch (IOException e) {
e.printStackTrace();
return AjaxResult.error(e.getMessage());
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error(e.getMessage());
}
} else {
if (merchant.getId() == null) {
merchant.setStoreNo(RandomNum.randomUtils(5) + Date2Utils.getCurrentTime());
merchant.setCreateTime(DateUtils.getNowDate());
merchantMapper.insertMerchant(merchant);
} else {
merchant.setUpdateTime(DateUtils.getNowDate());
merchantMapper.updateMerchant(merchant);
}
return AjaxResult.success();
}
3、关于合单支付
因为是PC端,所以选择了native类型的合单支付,也就是扫码支付,其他类型(APP、JSAPI、小程序、H5)支付都一样的。
预览:
代码实现
WxSftConfig config = sysConfigService.getConfigObject(WxSftConfig.WX_SFT_PARAMS_CONFIG_KEY, WxSftConfig.class);
String appName = config.getAppName();
String mchId = config.getMchId();
String appId = config.getAppId();
String notifyUrl = config.getNotifyUrl();
String mchSerialNo = config.getMchSerialNo();
String wechatSerialNo = config.getWechatSerialNo();
String mchPrivateKeyPath = config.getMchPrivateKeyPath();
String wechatPubKeyPath = config.getWechatPubKeyPath();
String remoteAddr = IpUtils.getIpAddr(request);
PayParam payParam = new PayParam();
payParam.setCombine_appid(appId);
payParam.setCombine_mchid(mchId);
payParam.setCombine_out_trade_no(System.currentTimeMillis() + "");
SceneInfo sceneInfo = new SceneInfo();
sceneInfo.setDevice_id("POS1:12");
sceneInfo.setPayer_client_ip(remoteAddr);
payParam.setScene_info(sceneInfo);
List<PaySubOrder> jsapiSubOrders = new ArrayList<>();
List<Merchant> list = this.list(Wrappers.<Merchant>lambdaQuery().in(Merchant::getId, Arrays.asList(ids)));
list.forEach(item ->{
PaySubOrder paySubOrder = new PaySubOrder();
paySubOrder.setMchid(mchId);
paySubOrder.setAttach(item.getId() + "&" + item.getMchId());
Amount amount = new Amount();
amount.setTotal_amount(50);
amount.setCurrency("CNY");
paySubOrder.setAmount(amount);
paySubOrder.setOut_trade_no(RandomNum.randomUtils(5) + Date2Utils.getCurrentTime() + System.currentTimeMillis());
paySubOrder.setSub_mchid(item.getMchId());
paySubOrder.setDescription("合单支付");
SettleInfo settleInfo = new SettleInfo();
settleInfo.setProfit_sharing(false);
paySubOrder.setSettle_info(settleInfo);
jsapiSubOrders.add(paySubOrder);
});
payParam.setSub_orders(jsapiSubOrders);
payParam.setNotify_url(notifyUrl);
BaseParam baseParam = new BaseParam();
baseParam.setAppName(appName)
.setMchId(mchId)
.setMchSerialNo(mchSerialNo)
.setWechatSerialNo(wechatSerialNo)
.setMchPrivateKeyPath(mchPrivateKeyPath)
.setWechatPubKeyPath(wechatPubKeyPath);
JSONObject result = transactionsService.nativePay(payParam, baseParam);
int applyStatus = result.getInteger("requestStatus");
if (applyStatus == 200) {
BuildQrCode bc = new BuildQrCode("", result.getString("code_url"));
String qrCode = QrCode.encoderQRCode(bc);
return AjaxResult.success("操作成功", qrCode);
} else {
return AjaxResult.error(result.getString("message"));
}
4、小结
微信电商收付通支付一站式解决方案其实还是给广大商户带来了很多的便捷,最明显的就是资金不再需要先结算到微信支付服务商那边,而是直接付给自己的商户号,还有分账等功能。