眾所周知,騰訊的開放接口向來都對開發者不怎么友好,集中體現在接口調用示例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.javen205</groupId> <artifactId>IJPay-WxPay</artifactId> <version>2.7.1</version> </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開放供業務層調用即可。
