支付宝小程序获取手机号做免登录操作流程
支付宝小程序获取手机号做免登录操作流程
需求背景:
公司启用了新的支付宝小程序,重新更换了主体。在新的主体下面创建了新的支付宝应用,刚开始可以免登录,可能是启用了老的主体的小程序做过授权操作。
新账户访问之后,跳转一键登录页面,接口无法获取到memNo,核心底层逻辑是需要授权获取到手机号,然后在系统内部根据手机号静默注册或查询得到会员号。然后继续后续的流程操作。
老的方案:
隐私申请 alipay.user.info.share mobile 可以申请获取手机号,新版本的不再支持,提交了资料被驳回。
新的方案:
关于调整支付宝小程序调用相关隐私接口申请的公告
https://open.alipay.com/portal/forum/post/190901040
之前主体的是开通过的。
现在新的需要走新的流程来做了
my.getPhoneNumber
https://opendocs.alipay.com/mini/01f46f19_my.getPhoneNumber
开发设置 需要配置应用网关, 加密方式:默认是RSA2 -- 对接的时候主要遇到了这个问题 -- 然后在设置的时候,遇到支付宝风控提示账号异常,又提交资料等解封,才可以设置成功。--
错误码
response 明文返回的异常报错信息(40001),以及 response 密文经由服务端解密后返回的异常报错信息。
结果码 结果码信息 解决方案
20000 系统繁忙 可提示用户“稍后再试”
40001 缺少加密配置 调用my.getPhoneNumber之前,需完成接口内容加密方式的配置: 进入 开放平台-开发设置 配置 接口内容加密方式 。详见:接口内容加密方式
40001 应用未设置默认签名类型 进入 开放平台-开发设置 配置 应用网关 。详见:应用网关
40002 加密异常 参考文档:接口内容加密方式,重新设置接口内容加密方式
40003 无效的授权关系 获取支付宝会员手机号需要用户进行授权,授权行为通过 <button> 组件 的 点击 动作来触发, <button> 组件open-type 的值设置为 getAuthorize 并将 scope 设为 phoneNumber。 用户点击并同意之后,可以通过 my.getPhoneNumber 接口获取到支付宝会员加密后的手机号
virtualUser/miniprogram/userScopeAuth
virtualUser/miniprogram/userScopeAuthGy
接口新增参数:
//加密手机号
private String encryptedDataPhone;
原来的接口,新增这个加密参数,获取到的手机号后端做静默注册。
数据格式
解密前
String ss = "{\"response\":\"SLp7yh2g3ywqQ4kEoGoO3yiskY8NVh25rajms4eiDBH8cxEd6XUUcu4UDODMc5dJGScM54l0+20feqdnU7stuw==\",\"sign\":\"ud7K8TATqPTThXGbeZLKBTHwqr3JYKE7CiorMbJsFol4Jx371akxHlyH1C6nqmkPh5WFL3yjIvydDRg9qCjL8+adInvh734mJlefavxvSro3MEPrk0peHWhOmmyHqt0+rxzuH3hd6oaaZy1a/br4hpmpP+KX5h1qEgLnTqmewrkfMWwvtI6tpw03aJVPx6GSbiyl8j+Gc/gbpF2y7IwJGxQlMUzrz5rR77JQpQWtBEwPUxyxEU50++M05wbyWwZonBfX2ZgJaydMTEjQsH1yld544Zo31bnB6H2N0LWohCsFe5hOx2SkP0vTtgT8lY9aBvW1R0GpkWI/taYfXxqp2g==\"}";
解密后
{"code":"10000","msg":"Success","mobile":"13764001198"}
1.核心代码(解密,不带签名的解密)
/** * 支付宝小程序解密被加密的手机号 * @param req */ public DecryptContentRepsVo decryptContent(DecryptContentRequestVo req) throws Exception{ String appType = req.getAppType(); String aliPublicKeyConfig = ""; String autoContenAesKey = ""; if("mini".equals(appType)) {//小程序主号 aliPublicKeyConfig = miniAliPublicKey; autoContenAesKey = miniContenAesKey; }else { throw new BusinessException("调用者传递的appType无法识别"); } String encryptedContent = req.getEncryptedContent(); DecryptContentRepsVo decryptContentRepsVo = new DecryptContentRepsVo(); Map<String, String> openapiResult = JSON.parseObject(encryptedContent, new TypeReference<Map<String, String>>() { }, Feature.OrderedField); String signType = StringUtils.defaultIfBlank(openapiResult.get("signType"), "RSA2"); String charset = StringUtils.defaultIfBlank(openapiResult.get("charset"), "UTF-8"); String encryptType = StringUtils.defaultIfBlank(openapiResult.get("encryptType"), "AES"); String sign = openapiResult.get("sign"); String content = openapiResult.get("response"); //如果密文的 boolean isDataEncrypted = !content.startsWith("{"); boolean signCheckPass = false; //2. 验签 String signContent = content;//用于验签的内容 //如果是加密的报文则需要在密文的前后添加双引号 if (isDataEncrypted) { signContent = "\"" + signContent + "\""; } try { signCheckPass = AlipaySignature.rsaCertCheck(content, sign, cerFilePathPrefix+File.separator+alipayCertPublicKeyPath, charset, signType); //signCheckPass = AlipaySignature.rsaCheck(signContent, sign, aliPublicKeyConfig, charset, signType); } catch (AlipayApiException e) { logger.error("小程序解密数据验签异常:"+e.getErrMsg(),e); throw new BusinessException(e.getErrCode(),"小程序解密数据验签异常:"+e.getErrMsg()); } if(!signCheckPass) { //验签不通过(异常或者报文被篡改),终止流程(不需要做解密) logger.error("小程序解密数据异常"); throw new Exception("验签失败"); } //3. 解密 String plainData = null; if (isDataEncrypted) { try { plainData = AlipayEncrypt.decryptContent(content, encryptType, autoContenAesKey, charset); } catch (AlipayApiException e) { //解密异常, 记录日志 logger.error("小程序解密数据异常", e); throw new Exception("小程序解密数据异常"); } } else { plainData = content; } decryptContentRepsVo.setPlainData(plainData); return decryptContentRepsVo; } /** * 支付宝小程序解密被加密的手机号(不验签,仅做解密) * @param req */ public DecryptContentRepsVo decryptContentNoSign(DecryptContentRequestVo req) throws Exception{ String appType = req.getAppType(); String autoContenAesKey = ""; if("mini".equals(appType)) {//小程序主号 autoContenAesKey = miniContenAesKey; }else { throw new BusinessException("调用者传递的appType无法识别"); } String encryptedContent = req.getEncryptedContent(); DecryptContentRepsVo decryptContentRepsVo = new DecryptContentRepsVo(); Map<String, String> openapiResult = JSON.parseObject(encryptedContent, new TypeReference<Map<String, String>>() { }, Feature.OrderedField); String charset = StringUtils.defaultIfBlank(openapiResult.get("charset"), "UTF-8"); String encryptType = StringUtils.defaultIfBlank(openapiResult.get("encryptType"), "AES"); String content = openapiResult.get("response"); // //判断是否为加密报文 boolean isDataEncrypted = !content.startsWith("{"); //跳过验签,直接解密 String plainData = null; if (isDataEncrypted) { try { plainData = AlipayEncrypt.decryptContent(content, encryptType, autoContenAesKey, charset); } catch (AlipayApiException e) { //解密异常, 记录日志 logger.error("小程序解密数据异常", e); throw new Exception("小程序解密数据异常"); } } else { plainData = content; } decryptContentRepsVo.setPlainData(plainData); return decryptContentRepsVo; }
2.核心代码(调用发起方)
String mobile = authUserVo.getMobile(); // 解析密文的手机号获取,兼容方案: // 弹窗用户授权未返回明文手机号时,使用客户端上报的加密手机号调用网关解密接口兼容获取 if(StringUtils.isEmpty(mobile)){ String encMobile = req.getEncryptedDataPhone(); if(StringUtils.isEmpty(encMobile)){ logger.warn("解析密文手机号兼容方案,密文手机号为空,aliUid:{}", aliUid); }else { String authUrl2 = alipayAuthGwUrl + "/alipayAuth/miniprogram/decryptContentNoSign"; if("1".equals(alipayAuthGwUrlSignFlag)){ authUrl2 = alipayAuthGwUrl + "/alipayAuth/miniprogram/decryptContent"; } DecryptContentRequestVo decryptReq = new DecryptContentRequestVo(); decryptReq.setAppType("mini"); decryptReq.setEncryptedContent(encMobile); try { Response<DecryptContentRepsVo> repsObj2 = restTemplate.postForObject(authUrl2, decryptReq, Response.class); logger.info("解析密文手机号兼容方案,网关返回值为:{},远程请求地址为:{}", JsonUtil.toJson(repsObj2), authUrl2); if (repsObj2 == null || "000000".equals(repsObj2.getResCode()) == false) { logger.error("解析密文手机号兼容方案,网关返回异常,返回值为:{},远程请求地址为:{}", JsonUtil.toJson(repsObj2), authUrl2); Cat.logError("解析密文手机号兼容方案网关异常,返回值为:" + JsonUtil.toJson(repsObj2), new BusinessException(ErrorCode.SYS_ERROR, "解析密文手机号兼容方案网关异常")); } else { DecryptContentRepsVo decryptContentRepsVo = GsonUtils.convertObj(GsonUtils.toJson(repsObj2.getData()), DecryptContentRepsVo.class); if (decryptContentRepsVo != null && org.apache.commons.lang3.StringUtils.isNotEmpty(decryptContentRepsVo.getPlainData())) { LinkedHashMap<String, Object> plainPhoneDataMap = GsonUtils.convertObj(decryptContentRepsVo.getPlainData(), LinkedHashMap.class); if (plainPhoneDataMap != null && plainPhoneDataMap.containsKey("mobile")) { String decryptMobile = "" + plainPhoneDataMap.get("mobile"); if (NumberUtils.isNumber(decryptMobile)) { mobile = decryptMobile; } } } } } catch (Exception e) { logger.error("解析密文手机号兼容方案异常,authUrl2:{}", authUrl2, e); Cat.logError("解析密文手机号兼容方案异常", e); } } } VirtualUserVo virtualUserVo = new VirtualUserVo(); virtualUserVo.setAliUid(aliUid); virtualUserVo.setSourceTypeEnum(SourceTypeEnum.ALI_MINI.name()); if(NumberUtils.isNumber(mobile)) {//如果获取用户信息 申请了敏感信息,能拿到手机号 //拿得到手机号,则登录,或注册 -- 关键操作 -- BindPhoneNoAliVo bindPhoneNoAliVo = new BindPhoneNoAliVo(); bindPhoneNoAliVo.setToken(token); bindPhoneNoAliVo.setPhoneNo(mobile); bindPhoneNoAliVo.setUtmCampaign(req.getUtmCampaign()); bindPhoneNoAliVo.setUtmMedium(req.getUtmMedium()); bindPhoneNoAliVo.setUtmSource(req.getUtmSource()); bindPhoneNoAliVo.setUtmTerm(req.getUtmTerm()); this.LoginOrRegistByPhoneNo(bindPhoneNoAliVo,virtualUserInfo); }
浙公网安备 33010602011771号