codecamp

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

操作手册

本产品暂为定向输出,若有对外输出诉求或需求洽谈事项,请联系合作伙伴技术组: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());
    }
支付宝小程序扩展能力 AntBuilder 用户信息对接指南
支付宝小程序扩展能力 消息组件使用手册
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

支付宝小程序开发文档

支付宝小程序 快速示例

支付宝小程序 小程序快速示例

支付宝小程序 框架

支付宝小程序 组件

支付宝小程序组件 基础组件

支付宝小程序组件 无障碍访问

支付宝小程序 扩展组件

支付宝小程序扩展组件 UI组件

支付宝小程序 API

支付宝小程序 开发工具

支付宝小程序 云服务

支付宝小程序 Serverless

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }