支付宝小程序获取手机号做免登录操作流程

支付宝小程序获取手机号做免登录操作流程

需求背景:
公司启用了新的支付宝小程序,重新更换了主体。在新的主体下面创建了新的支付宝应用,刚开始可以免登录,可能是启用了老的主体的小程序做过授权操作。

新账户访问之后,跳转一键登录页面,接口无法获取到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);
                
            }

 

posted on 2026-09-18 17:26  oktokeep  阅读(9)  评论(0)    收藏  举报