支付宝小程序扩展能力 AntBuilder 会员接入指南

2020-09-19 10:44 更新

操作手册

本产品暂为定向输出,若有对外输出诉求或需求洽谈事项,请联系合作伙伴技术组:partner-booster@service.alipay.com

业务系统配置

不打通 CRM(默认)

只需要配置卡管系统的公网 URL(就是小程序后端 web-mini 的公网 URL)。

image.png

打通 CRM

配置项:

请前往 AntBuilder 安装目录 antbuilder-installer/application/web-mini/config 以及 antbuilder-installer/application/web-management/config,编辑 application-prod.yml 文件,参考下列实例底部配置如下内容。

image.png

前置条件:

CRM 提供以下接口,接口格式参考下面技术接入手册:

  1. 开卡信息接口:用户领卡时调用,传入用户 alipay UID + 领卡表单信息,换取用户会员卡号、积分、等级信息。
  2. 卡信息变动接口:信息变更(新增、删除)时调用,以同步信息,如果不需要删除,接口中可以不做操作。

为了保证接口调用安全性,CRM 和web-mini调用之间有安全验证

  1. web-mini 调用 CRM(开卡信息接口和卡信息变动接口),在 HEADER 中加入 token,CRM 可以验证 token 是否一致。
  2. CRM 调用 mini-core(更新积分和模板):使用 RSA2 加签验签,CRM 使用私钥加签,mini-core 使用公钥验签。

image.png

配置会员卡

  1. 配置会员卡应用。会员卡应用建议使用 WEB 应用,方便后续会员卡单独对外漏出。image.png image.png

  1. 创建会员卡模板。 image.png image.png

  1. 配置基本信息。为创建的模板取名字,选择会员卡有效期,适用门店列表。image.png

  1. 配置卡样式。主要配置卡的图片,显示的名称,以及字体颜色,卡码类型(动态码需配置刷新时间)。 说明:这一步可以验证支付宝应用配置是否正确,如果上传失败,请前往文档最后的常见问题。image.png

  1. 配置栏位。分为标准栏位和自定义栏位。标准栏位,目前仅支持积分。 image

  1. 行动点配置。行动点可以支持跳小程序,而且支持在卡包列表中显示。image

  1. 开卡表单配置。配置需要获取到的用户信息。 image

配置会员卡组件

  1. 在模板中添加一个会员卡组件,然后编辑中选择模板。

image.png

  1. 选择需要配置的模板,然后将组件上架即可。

image.png

附录:准备支付宝应用

  1. 登录 支付宝开放平台,根据需求创建网页&移动应用。image

  1. 添加会员卡功能image

注意:配置应用网关(http(s)://web-mini的域名地址)和回调地址(http(s)://web-mini的域名地址/aliCallback)。

image

附录:常见问题

Q:生成的领卡链接的回调地址错误,导致支付宝不能访问,如何解决?

A:领卡链接中的回调地址的 IP 是支付宝不能访问的地址,比如:localhost,域内 IP,导致领卡异常。系统,访问卡管系统,不能用 localhost 和内网地址,要用公网地址访问。

Q:卡管系统不能访问到业务系统,如何解决?

A:在业务系统配置页面,配置业务系统。

Q:会员卡领卡报错 ERR010,如何解决?

A:校验授权回调地址失败,请检查 callback 参数和应用授权回调地址是否一致。进入开放平台,将应用的回调地址修改为 http://卡管系统 HOST:卡关系统 PORT/aliCallback。

image

领卡失败

系统服务商(ISV)权限不足,建议在开发者中心检查对应功能是否已经添加。 问题原因:账号没有卡包权限。 解决方案:添加会员分类下的权限。

imageimage.png

适用门店配置后效果

门店列表配置后再会员卡首页最下方添加 适用门店 选项,门店列表按照 LBS 聚力显示。

image.pngimage.png

查询门店 ID

登录 商家中心,进入门店管理,查看门店信息,复制门店 ID。

image.png

技术接入手册

工作 描述 是否必须
提供用户信息接口(含会员数据同步+查询) 传入用户提交的会员信息,CRM 系统保存或更新会员信息,同时业务系统返回会员卡号和积分等会员信息。
卡状态变更接口 支付宝会员卡新增或删除时,将变更的会员卡信息发送给业务系统,
获取领卡链接 获取支付宝领卡表单的链接地址
更新会员卡积分 调用卡管更新支付宝会员卡积分

业务系统提供接口给卡管系统(必须)

开卡信息接口

示例代码,注意看注释。

/**     * 开卡信息接口     * token:在管理系统配置的访问业务系统TOKEN,可以用来防止被攻击     *      * <p>     * HTTP,POST请求,入参放在BODY中,入参和出参都是MAP     * <p>     * 入参中包含以下参数:     * name(姓名)     * mobile(手机号)     * certNo(身认证)     * gender(性别)     * templateId(支付宝模板ID)     * outString(outString)     * alipayUserId(支付宝USERID, 2088开头)     * 说明:上面的参数alipayUserId肯定存在,其他参数根据配置的领卡表单获取,可能没有值     * <p>     * 出参,要求包含以下参数     * point(当前用户积分,必填,整数,如果没有就传0)     * bizCardNo(业务系统卡号,二维码显示这个卡号,必填,字符串,更新积分等接口都使用业务系统卡号,不用保存支付宝用户ID)     * templateId(用户等级对应的卡模板不是开卡链接中的模板时,将真正的模板ID传回来)     * @param params     * @return     */@PostMapping("/card/openCardInfo")
public Map<String, Object> openCardInfo(@RequestHeader("token") String token, @RequestBody Map<String, Object> params ) throws Exception {
    LogUtil.info(log,"token==" + token);
    String bizCardNo = String.valueOf( params.get("bizCardNo"));
    if(StringUtils.isEmpty(bizCardNo)){
        //如果没有传会员卡号,走注册逻辑        LogUtil.info(log, "走注册逻辑");
        //TODO: 根据map中传的身份证、手机号、姓名、支付宝UID等匹配业务系统的用户,返回业务系统用户ID和积分信息        String name = params.get("name")!= null ? String.valueOf(params.get("name")) : null;
        String mobile = params.get("mobile")!= null ? String.valueOf(params.get("mobile")) : null;
        String certNo = params.get("certNo")!= null ? String.valueOf(params.get("certNo")) : null;
        String gender = params.get("gender")!= null ? String.valueOf(params.get("gender")) : null;
        String templateId = String.valueOf(params.get("templateId"));
        String outString = String.valueOf(params.get("outString"));
        String alipayUserId = String.valueOf(params.get("alipayUserId"));
        name =  DESUtils.encrypt(name);
        mobile =  DESUtils.encrypt(mobile);
        certNo =  DESUtils.encrypt(certNo);
        gender =  DESUtils.encrypt(gender);
        LogUtil.info(log, "开卡信息:name:{}, mobile:{},certNo:{},gender:{},templateId:{},outString:{}",
                name, mobile, certNo, gender, templateId, outString);
        Map<String, Object> result = new HashMap<>();
        // 必填,业务系统用户卡号,有两个场景使用,参数有点不一致        result.put("bizCardNo", "xxx");
        result.put("cardNo", "xxx");
        // 非必填,用户已有积分,如果没有就传0        result.put("point", "yyyy");
        // 非必填,用户登记        result.put("level", "zzzz");
        // 非必填,用户余额        result.put("balance", "nnnn");
        // 非必填,如果用户等级对应的模板和开卡对应的模板不一致,则重新传一个模板ID        //        result.put("templateId", "20200227000000002181302000300947");        return result;
    }else{
        //如果传了会员卡,走会员查询逻辑        LogUtil.info(log, "走查询逻辑");
        // 必填,业务系统用户卡号,有两个场景使用,参数有点不一致        Map<String, Object> result = new HashMap<>();
        result.put("bizCardNo", bizCardNo);
        result.put("cardNo", bizCardNo);
        // 非必填,用户已有积分,如果没有就传0        result.put("point", "yyyy");
        // 非必填,用户登记        result.put("level", "zzzz");
        // 非必填,用户余额        result.put("balance", "nnnn");
        return result;
    }
}

使用以下 shell 命令,测试是否能够正常访问。

curl -H "Content-Type:application/json" 
-XPOST http://localhost:8082/card/openCardInfo -d '{"alipayUserId":"2088"}'

卡信息变更接口

示例代码,注意看注释。

/** * 卡信息变更接口 * token:在管理系统配置的访问业务系统TOKEN,可以用来防止被攻击 * * 入参中包含以下参数: * type(变动类型): ADD\DEL * templateId(支付宝模板ID) * alipayUserId(支付宝USERID, 2088开头):ADD、DEL类型会传入 * bizCardNo(开卡时返回的业务系统卡号):ADD、DEL类型会传入 * alipayCardNo(支付宝会员卡号):ADD、DEL类型会传入 * * @param params * @return */@PostMapping("/card/cardChange")
public boolean cardChange(@RequestHeader("token") String token, @RequestBody 
                          Map<String, Object> params) {
    LogUtil.info(logger, "卡信息变更:token:{} ",token);
    //TODO: 业务系统根据自己需求    String type = String.valueOf(params.get("type"));
    String templateId = String.valueOf(params.get("templateId"));
    String alipayUserId = String.valueOf(params.get("alipayUserId"));
    String bizCardNo = String.valueOf(params.get("bizCardNo"));
    String alipayCardNo = String.valueOf(params.get("alipayCardNo"));
    LogUtil.info(logger, "卡信息变更:type:{},templateId:{},alipayUserId:{},                 bizCardNo:{},alipayCardNo:{}", type,templateId, alipayUserId,                  bizCardNo, alipayCardNo);
    return true;
}

image

商户动态码获取接口

示例代码,注意看注释。

 /**     * 商户动态卡码值查询接口     * token:在管理系统配置的访问业务系统TOKEN,可以用来防止被攻击     * <p>     * 入参中包含以下参数:     * appId     * templateId     * alipayUserId     *     * @param params     * @return     */    @PostMapping("/card/mdCodeChange")
    public Map<String, Object> mdCodeChange(@RequestHeader("token") String token, @RequestBody Map<String, Object> params) {
        LogUtil.info(log, "卡信息变更:token:{} , {}", token, JSON.toJSONString(params));
        String appId = String.valueOf(params.get("appId"));
        String templateId = String.valueOf(params.get("templateId"));
        String alipayUserId = String.valueOf(params.get("alipayUserId"));
        Integer expireTime = 120;
        if (params.containsKey("expireTime")) {
            expireTime = Integer.valueOf(String.valueOf(params.get("expireTime")));
        }
        Map<String, Object> result = new HashMap<String, Object>();
        result.put("alipayUserId", alipayUserId);
        //TODO: 业务系统根据自己需求,生成用户动态码值        result.put("codeValue", System.currentTimeMillis() / 1000L + "_" + expireTime);
        //TODO: 业务系统根据自己需求,设置码值过期时间, params.get("expireTime") 是卡管系统中配置的过期时间(秒)        result.put("codeExpire", DateUtil.format(new Date(System.currentTimeMillis() + expireTime * 1000), "yyyy-MM-dd HH:mm:ss"));
        return result;
    }

业务系统调用卡管系统(可选)

在业务代码中更新用户积分

/**     * 更新积分     * <p>     * 模拟调用卡管系统更新用户积分     * <p>     * 真实场景应该放在业务层中调用     *     * @param templateId 模板ID     * @param bizCardNo     业务系统卡号     * @param point      用户最先积分     * @param changeReason 积分更新原因     * @return     */@PostMapping("/card/update")
public HttpResult updateCard(String templateId, String bizCardNo, String point, String changeReason)
    throws Exception {
    Map<String, String> map = new HashMap<>();
    map.put("templateId", templateId);
    map.put("bizCardNo", bizCardNo);
    map.put("point", point);
    map.put("changeReason", changeReason);
    MapUtils.removeNullValue(map);
    try {
        String signature = SignHelper.signStringParam(map, sdkConfig.getPrivateKey());
        map.put(SignHelper.SIGNATURE_PARAM_KEY, signature);
    } catch (Exception e) {
        throw new Exception("加签失败", e);
    }
    String result = restTemplate.postForEntity(sdkConfig.getListOfServers() + 
                                       "/card/update", map, String.class).getBody();
    Map parseResult = (Map) JSON.parse(result);
    if (parseResult.get("status").equals("OK")) {
        return HttpResult.newCorrectResult("积分更新成功");
    }
    return HttpResult.newErrorResult(parseResult.get("errmsg").toString());
}

在业务代码中更新用户模板

用户登记发生变化,需要替换成不同的模板(可能包含不同的权益说明)。

/**     * 更新模板     * <p>     * 模拟调用卡管系统更新用户模板     * <p>     * 真实场景应该放在业务层中调用     *     * @param templateId 模板ID     * @param bizCardNo     业务系统卡号     * @param targetTemplateId    目标模板ID     * @param changeReason 积分更新原因     * @return     */    @PostMapping("/card/changeTemplate")
    public HttpResult changeTemplate(String templateId, String bizCardNo, String targetTemplateId, String changeReason)
            throws Exception {
        Map<String, String> map = new HashMap<>();
        map.put("templateId", templateId);
        map.put("bizCardNo", bizCardNo);
        map.put("targetTemplateId", targetTemplateId);
        map.put("changeReason", changeReason);
        MapUtils.removeNullValue(map);
        try {
            String signature = SignHelper.signStringParam(map, sdkConfig.getPrivateKey());
            map.put(SignHelper.SIGNATURE_PARAM_KEY, signature);
        } catch (Exception e) {
            throw new Exception("加签失败", e);
        }
        String result = restTemplate.postForEntity(sdkConfig.getListOfServers() + 
                                "/card/changeTemplate", map, String.class).getBody();
        Map parseResult = (Map) JSON.parse(result);
        if (parseResult.get("status").equals("OK")) {
            return HttpResult.newCorrectResult("模板更新成功");
        }
        return HttpResult.newErrorResult(parseResult.get("errmsg").toString());
    }
以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号