微信电商收付通二级商户进件API接口调用


众所周知,腾讯的开放接口向来都对开发者不怎么友好,集中体现在接口调用示例demo少、错误信息提示不够明确、问题反馈客服回复敷衍等。

在做收付通二级商户入驻申请功能时更是深深体会到了这几点。而作为普通开发者,所能做的无非就是摇头苦笑然后默默记下,希望自己和类似的同学能够少走弯路。

闲言少叙,进入正题!

二级商户入驻申请,接口地址是https://api.mch.weixin.qq.com/v3/ecommerce/applyments/,要做的工作其实很简单,就是把二级商户的相关资质信息提交给这个接口,难点在于,对于不甚了解微信v3接口调用的同学来说,这样那样的账号和密钥、五花八门的签名和加密,实在是让人头大。因此下文我会写的尽量简单明了。

准备工作

1.去微信支付服务商平台申请账号https://pay.weixin.qq.com/partner/public/home,得到商户id,即mchId;

2.设置API证书、API密钥、APIv3密钥,可以得到商户API证书序列号mchSerialNo、商户私钥mchPrivateKey、API密钥apiV3Key等;

3.很容易被忽略的一步,我想也是对于萌新而言最坑的一步,配置通用化接口权限,这一步如果不做的话,调试接口时会提示如下信息:

{"code":"APPID_MCHID_NOT_MATCH","message":"不属于电商平台商户,暂无权限"}

可按下图示意配置开通;

4.maven项目中添加第三方工具包IJPay-WxPay的依赖;

<dependency>
  <groupId>com.github.javen205groupId>
  <artifactId>IJPay-WxPayartifactId> 
  <version>2.7.1version>
dependency>

5.获取平台证书序列号platSerialNo、平台私钥platPrivateKey,这一步也很关键,因为platSerialNo是必传字段,而platPrivateKey是敏感数据加密的必要参数,平台证书的获取改天我会单独来写;

6.实现微信图片上传功能,因为二级商户进件接口需要商家的身份证、营业执照等图片数据,接口不接收图片流,而是需要事先将相关图片通过接口上传到微信图片服务器,形成一个MediaID值来作为进件接口的参数,图片上传功能我也会另起一篇单独写。

开始调用

1.以一个小微商户为例,先构建主体信息类结构,主类是SubmitInfo,关联类包括BusinessLicenseInfo、OrganizationCertInfo、IdCardInfo、IdDocInfo、AccountInfo、ContactInfo、SalesSceneInfo;

 1 /**
 2  * 进件信息
 3  */
 4 @Data
 5 public class SubmitInfo implements Serializable {
 6     private static final long serialVersionUID=1L;
 7     /**业务申请编号*/
 8     private String out_request_no;
 9     /**主体类型*/
10     private String organization_type;
11     /**营业执照/登记证书信息*/
12     private BusinessLicenseInfo business_license_info;
13     /**组织机构代码证信息*/
14     private OrganizationCertInfo organization_cert_info;
15     /**经营者/法人证件类型*/
16     private String id_doc_type;
17     /**经营者/法人身份证信息*/
18     private IdCardInfo id_card_info;
19     /**经营者/法人其他类型证件信息*/
20     private IdDocInfo id_doc_info;
21     /**是否填写结算银行账户*/
22     private Boolean need_account_info;
23     /**结算银行账户*/
24     private AccountInfo account_info;
25     /**超级管理员信息*/
26     private ContactInfo contact_info;
27     /**店铺信息*/
28     private SalesSceneInfo sales_scene_info;
29     /**商户简称*/
30     private String merchant_shortname;
31     /**特殊资质*/
32     private String qualifications;
33     /**补充材料*/
34     private String business_addition_pics;
35     /**补充说明*/
36     private String business_addition_desc;
37 }
 1 /**
 2  * 营业执照/登记证书信息
 3  */
 4 @Data
 5 public class BusinessLicenseInfo implements Serializable {
 6     private static final long serialVersionUID=1L;
 7     /**证件扫描件*/
 8     private String business_license_copy;
 9     /**证件注册号*/
10     private String business_license_number;
11     /**商户名称*/
12     private String merchant_name;
13     /**经营者/法定代表人姓名*/
14     private String legal_person;
15     /**注册地址*/
16     private String company_address;
17     /**营业期限*/
18     private String business_time;
19 }
 1 /**
 2  * 组织机构代码证信息
 3  */
 4 @Data
 5 public class OrganizationCertInfo implements Serializable {
 6     private static final long serialVersionUID=1L;
 7     /**组织机构代码证照片*/
 8     private String organization_copy;
 9     /**组织机构代码*/
10     private String organization_number;
11     /**组织机构代码有效期限*/
12     private String organization_time;
13 }
 1 /**
 2  * 经营者/法人身份证信息
 3  */
 4 @Data
 5 public class IdCardInfo implements Serializable {
 6     private static final long serialVersionUID=1L;
 7     /**身份证人像面照片*/
 8     private String id_card_copy;
 9     /**身份证国徽面照片*/
10     private String id_card_national;
11     /**身份证姓名*/
12     private String id_card_name;
13     /**身份证号码*/
14     private String id_card_number;
15     /**身份证有效期限*/
16     private String id_card_valid_time;
17 }
 1 /**
 2  * 经营者/法人其他类型证件信息
 3  */
 4 @Data
 5 public class IdDocInfo implements Serializable {
 6     private static final long serialVersionUID=1L;
 7     /**证件姓名*/
 8     private String id_doc_name;
 9     /**证件号码*/
10     private String id_doc_number;
11     /**证件照片*/
12     private String id_doc_copy;
13     /**证件结束日期*/
14     private String doc_period_end;
15 }
 1 /**
 2  * 结算银行账户
 3  */
 4 @Data
 5 public class AccountInfo implements Serializable {
 6     private static final long serialVersionUID=1L;
 7     /**账户类型*/
 8     private String bank_account_type;
 9     /**开户银行*/
10     private String account_bank;
11     /**开户名称*/
12     private String account_name;
13     /**开户银行省市编码*/
14     private String bank_address_code;
15     /**开户银行联行号*/
16     private String bank_branch_id;
17     /**开户银行全称 (含支行)*/
18     private String bank_name;
19     /**银行帐号*/
20     private String account_number;
21 }
 1 /**
 2  * 超级管理员信息
 3  */
 4 @Data
 5 public class ContactInfo implements Serializable {
 6     private static final long serialVersionUID=1L;
 7     /**超级管理员类型*/
 8     private String contact_type;
 9     /**超级管理员姓名*/
10     private String contact_name;
11     /**超级管理员身份证件号码*/
12     private String contact_id_card_number;
13     /**超级管理员手机*/
14     private String mobile_phone;
15     /**超级管理员邮箱*/
16     private String contact_email;
17 }
 1 /**
 2  * 店铺信息
 3  */
 4 @Data
 5 public class SalesSceneInfo implements Serializable {
 6     private static final long serialVersionUID=1L;
 7     /**店铺名称*/
 8     private String store_name;
 9     /**店铺链接*/
10     private String store_url;
11     /**店铺二维码*/
12     private String store_qr_code;
13     /**小程序AppID*/
14     private String mini_program_sub_appid;
15 }

2.实例化数据,可以根据官方提供的进件参数文档,给必要的参数赋值,得到一个submitInfo实体对象;

SubmitInfo submitInfo=new SubmitInfo();
submitInfo.setOut_request_no("test0001");
submitInfo.setOrganization_type("2401");
submitInfo.setId_doc_type("IDENTIFICATION_TYPE_MAINLAND_IDCARD");
IdCardInfo idCardInfo=new IdCardInfo();
idCardInfo.setId_card_copy("bOCC_G-WcsHmpLxRKpict56tZPY5w07MsiAY-7xZbFV1S6OTYVNGCDh1PKhyHU_CMagKLvE-aK8t1nxIE93EvFS_PFuw-xNh9KYcdCBclA0");
idCardInfo.setId_card_national("bOCC_G-WcsHmpLxRKpict-TBIP1EB0xGCt621AA7M_GqXp5i-wFkhcNHohyXSPrUlsrfKPnV6wbYkWg-rB4fBPRvjzuZPlmzPSX7AI1UoQw");
idCardInfo.setId_card_name("李霞");
idCardInfo.setId_card_number("410711197103031527");
idCardInfo.setId_card_valid_time("2028-06-21");
submitInfo.setId_card_info(idCardInfo);
ContactInfo contactInfo=new ContactInfo();
contactInfo.setContact_type("65");
contactInfo.setContact_name("李霞");
contactInfo.setContact_id_card_number("410711197103031527");
contactInfo.setMobile_phone("13708772087");
submitInfo.setContact_info(contactInfo);
SalesSceneInfo salesSceneInfo=new SalesSceneInfo();
salesSceneInfo.setStore_name("朝阳绽放花卉店");
salesSceneInfo.setStore_url("http://www.hymng.com/");
submitInfo.setSales_scene_info(salesSceneInfo);
submitInfo.setMerchant_shortname("朝阳绽放花卉店");
submitInfo.setNeed_account_info(false);
submitInfo.setBusiness_addition_desc("该商户已持续从事电子商务经营活动满6个月,且期间经营收入累计超过20万元。");

3.加密submitInfo对象中的敏感数据,比如姓名、身份证号码、手机号码、邮箱等,这里可以封装一个工具方法,将submitInfo传入即可;

private static String convertToStr(SubmitInfo submitInfo) throws Exception {
  rsaEncryptSubmitInfo(submitInfo);
  return JSONObject.toJSONString(submitInfo);
}

private static void rsaEncryptSubmitInfo(SubmitInfo submitInfo) throws Exception {
  IdCardInfo idCardInfo=submitInfo.getId_card_info();
  if(idCardInfo!=null){
    idCardInfo.setId_card_name(rsaEncryptByCert(idCardInfo.getId_card_name()));
    idCardInfo.setId_card_number(rsaEncryptByCert(idCardInfo.getId_card_number()));
  }
  ContactInfo contactInfo=submitInfo.getContact_info();
  if(contactInfo!=null){
    contactInfo.setContact_name(rsaEncryptByCert(contactInfo.getContact_name()));
    contactInfo.setContact_id_card_number(rsaEncryptByCert(contactInfo.getContact_id_card_number()));
    contactInfo.setMobile_phone(rsaEncryptByCert(contactInfo.getMobile_phone()));
    if(!StringUtils.isEmpty(contactInfo.getContact_email())){
      contactInfo.setContact_email(rsaEncryptByCert(contactInfo.getContact_email()));
    }
  }
}

private static String rsaEncryptByCert(String content) throws Exception {
  InputStream inStream=new ByteArrayInputStream(platPrivateKey.getBytes(StandardCharsets.UTF_8));
  CertificateFactory cf = CertificateFactory.getInstance("X.509");
  X509Certificate certificate = (X509Certificate)cf.generateCertificate(inStream);
  PublicKey publicKey=certificate.getPublicKey();
  Cipher ci=Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
  ci.init(Cipher.ENCRYPT_MODE,publicKey);
  return Base64.getEncoder().encodeToString(ci.doFinal(content.getBytes(StandardCharsets.UTF_8)));
}

4.编写一个公开的工具方法,将加密后的submitInfo信息发送给微信接口,这里用到了第3步中的方法,以及第三方工具包WxPayApi提供的方法调用,这样可以节省掉微信接口签名等工作,对于新手来说不可谓不省心;

public static String apply(SubmitInfo submitInfo) throws Exception {
  String bodyStr=convertToStr(submitInfo);
  IJPayHttpResponse response=WxPayApi.v3(RequestMethod.POST, WxDomain.CHINA.getType(), WxApiType.E_COMMERCE_APPLY.getType(), mchId, mchSerialNo, platSerialNo, mchKeyPath, bodyStr);
  if(response.getStatus()== HttpStatus.OK.value()){
    JSONObject json=JSONObject.parseObject(response.getBody());
    return json.getString("applyment_id");
  }else{
    throw new Exception();
  }
}

5.使用以上步骤就可以成功得到最终的进件申请编号applyment_id,后续可以使用它查询入驻申请的审核结果。以上静态方法可统一编写到一个工具类中,只需要将第4步中的apply开放供业务层调用即可。