积分预扣事件

接口提供方

该接口需要由开发者提供给燃豆

接口说明

如果积分俱乐部使用的是自有积分体系,则该接口必须配置,查看自有积分和平台积分的区别

使用场景

用户在积分俱乐部消耗积分时使用,如兑换商品等。

业务流程说明(以商品兑换为例)

积分预扣接口流程 该接口需要配合积分预扣结果通知一起使用。

  1. 用户在燃豆积分俱乐部中发起商品兑换请求;
  2. 燃豆服务端生成该笔兑换订单,并同时请求该接口来预扣开发者服务端的用户积分,并把该订单信息(包含一个唯一订单号)一起推送给开发者;
  3. 开发者在收到请求后务必保存好该笔订单的订单号,且扣除或冻结相应的积分数量,并给燃豆返回已处理成功的消息,同时一起返回由开发者自行生成的唯一订单号;如果开发者返回失败信息,则用户兑换失败,订单取消,本次兑换流程结束,燃豆不会再发送该笔订单的结果通知;
  4. 如果该接口返回处理成功,在订单结果出来后(发货后或中途取消),燃豆服务端会通过积分预扣异步通知接口把该笔订单的结果推送给开发者;
  5. 开发者在收到结果后需要做出对应的处理,比如订单最终失败,需要把已扣除或冻结的积分返还给用户;

参数说明

该接口除了公共必传参数外,还包括以下参数:

参数是否必须类型[长度限制]说明
uidstring[1,64]下单用户id,该id由开发者在免登接口中传给燃豆的用户唯一标志
mall_nostring[6,6]商城的唯一编号,表示用户下单的所在商城编号,如JF_001
creditsint本次行为所需要消耗的积分数,如123
orderNostring[18,20]全局唯一的预扣订单编号,如T388364710157766657
created_atdate发生时间,格式为yyyy-MM-dd HH:mm:ss,如2021-09-12 12:34:56
typestring[1,20]扣积分类型(枚举值):REDEEM(表示商品兑换),DRAWINGGAME(表示抽奖活动) ,LINKGAME(表示参与连连看小游戏)
descriptionstring[1,255]扣积分行为的描述
ipstring[0,15]用户的客户端ip地址,不保证准确性,也有可能获取不到,如8.8.8.8,获取不到返回空字符串
redeem_detailJSONObject当type为REDEEM时返回,描述跟本次兑换相关的信息,object中的参数请查看
drawinggame_detailJSONObject当type为DRAWINGGAME时返回,描述跟本次抽奖相关的信息,object中的参数请查看
linkgame_detailJSONObject当type为LINKGAME时返回,描述跟本次游戏相关的信息,object中的参数请查看

redeem_detail参数说明

参数是否必须类型[长度限制]说明
product_nostring[0,20]商品编号,若未填则为空字符串
product_typestring[1,20]商品类型(枚举值),MATERIAL(表示实物商品),COUPON(表示优惠券商品),CHARGE(表示直充商品)
product_namestring[1,255]商品标题
product_fromstring[1,20]商品来源(枚举值):TENANT(表示开发者自己上传的商品),RANDOU(燃豆严选商品)
subsidy_feeint本次兑换需要从开发者账户中扣除的费用(含邮费),单位为分
user_feeint本次兑换由用户实际支付的钱(含邮费),单位为分
shipping_feeint本次兑换所发生的快递费用,目前仅实物商品可能产生快递费用,单位为分
need_reviewboolean是否需要燃豆后台人工审核
shipping_addressstring[1,255]如果所兑商品需要通过快递公司发货,会回传收货地址,如浙江省杭州市西湖区文三路888号
shipping_receiverstring[1,20]如果所兑商品需要通过快递公司发货,会回传收货人姓名,如张三
shipping_receiver_phonestring[1,20]如果所兑商品需要通过快递公司发货,会回传收货人联系方式,如13333333333
charge_accountstring[1,128]如果所兑商品通过充值方式发货,会回传收货人所填账号,如13333333333

drawinggame_detail参数说明

参数是否必须类型[长度限制]说明
titlestring[1,64]开发者后台中本次抽奖活动名称
uniqueIDstring[6,6]开发者后台中本次抽奖活动编号

linkgame_detail参数说明

参数是否必须类型[长度限制]说明
titlestring[1,64]开发者后台中本次连连看小游戏名称
uniqueIDstring[6,6]开发者后台中本次连连看小游戏编号

返回结果

开发者需以json格式返回响应内容,且不论成功失败,http响应码必须为200,返回码非200的情况下燃豆会认为本次请求失败,并告知用户兑换失败:

json对象中的响应参数说明

参数是否必须类型[长度限制]说明
statusstring[4,7]是否成功(枚举值),success(表示预扣成功),fail(表示预扣失败)
messagestring[0,255]错误提示,如果成功传空字符串,如果失败,该错误信息会展示给用户
bizNostring[10,32]由开发者自行生成的本地预扣订单号,status为success时必传,后续通知接口中会原样返回,只能包含数字、大小写字母、_和-,且在该预扣积分业务中唯一,不能重复。

返回示例

  • 如果本次积分预扣成功,请返回如下格式的内容:
{
  status: "success",
  message: "",
  bizNo: "2021091533333"  // 由开发者自行生成的本地订单号
}
  • 如果预扣失败,请返回如下格式的内容:
{
  status: "fail",
  message: "失败原因", //燃豆会把该失败原因显示给用户
}

注意事项

  1. 由于网络和系统存在不确定性,有可能存在接口调用超时或失败的情况,燃豆在调用该接口时,设置了超时时间为5s,如果接口响应时间超过了5s,燃豆会认为该次调用失败,并且终止整个流程;如果此时开发者已经扣除或冻结了用户的积分,此时需要把这些积分返回给用户;
  2. 当预扣积分失败时,用户兑换失败订单取消,此时由于预扣积分失败,该类情况不会发起后续通知事件;
  3. 如果预扣积分成功,开发者需要返回由开发者自行生成的该笔订单的订单号,请确保唯一性,后续通知接口中会把该订单号原样返回,开发者可使用该订单来查找自己本地的订单;
  4. 若预扣成功,请务必确保返回值中bizNo全局唯一,不仅只是兑换业务中唯一,所有预扣相关的业务都必须确保返回的bizNo唯一。

代码示例

PHP


use Randou\Exception\RdException;
use Randou\RdClient;
use Randou\RdClientBuilder;


$appid = 'aaaaaaaaaaaaaaaaaaaaaaaa';
$appsecret = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb';

// 获取全部请求数据
$params = $request->all();

try {
    $client = RdClientBuilder::getClient($appid, $appSecret);

//    校验并且提取订单数据
    $event = $client->withHolding($params);
} catch (RdException $e) {
  print_r($e);
  
//    校验失败,返回认证错误
    return response()->json([
        'status'  => 'fail',
        'message' => '出错了',
    ], 400);
}

// 获取订单全部数据,处理自身业务逻辑
$data = $event->all();
save($data);

//    成功样例
return response()->json([
    'status'  => 'success',
    'message' => '',
    'bizNo'   => sprintf("No%s", time() . ''),
]);

JAVA


import com.randou_tech.ClientBuilder;
import com.randou_tech.RdClient;
import com.randou_tech.RdException;
import com.randou_tech.event.WithHoldingEvent;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.RequestMapping;

@RequestMapping("/withholding")
public Map<String, String> withHolding(HttpServletRequest request) {
    String appid = "aaaaaaaaaaaaaaaaaaaaaaaa";
    String appsecret = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";

    RdClient client = ClientBuilder.build(appid, appsecret);
    Map<String, String> map = new HashMap<>();
    try {
        // 解析数据
        WithHoldingEvent event = client.withHolding(request);

        // 如果项目中没有javax.servlet.http.HttpServletRequest,可从request中自行获取出全部参数,以下为从jakarta.servlet.http.HttpServletRequest对象中获取参数
        // Enumeration<String> parameterNames = request.getParameterNames();
        // Map<String, String> params = new HashMap<>();
        // // 遍历参数名并获取对应的值
        // while (parameterNames.hasMoreElements()) {
        //     String paramName = parameterNames.nextElement();
        //     String paramValue = request.getParameter(paramName); // 只取第一个值
        //     params.put(paramName, paramValue);
        // }
        // WithHoldingEvent event = client.withHoldingWithParams(params);

        System.out.println(event.toString());

        // 处理开发者本地的预扣积分逻辑
        todo();

        // 处理成功后
        map.put("status", "success");
        map.put("message", "");
        map.put("bizNo", String.valueOf((System.currentTimeMillis() / 1000L)));
    } catch (RdException e) {
        map.put("status", "fail");
        map.put("message", e.getErrorMessage());
    }
    
    return map;
}