关闭

Android:基于Bmob实现应用付费购买专业版(一)

标签: androidbmob支付广告购买
2647人阅读 评论(0) 收藏 举报
分类:

【转载请注明出处】
作者:DrkCore
原文:http://blog.csdn.net/drkcore/article/details/51556658

如果你能看到这篇博客说明你像笔者一样也是个开发者,并且或多或少都有些自己的小应用,都想靠自己的应用赚点小外快。

既然如此那么在应用中植入广告这个套路想必大家一定都知道了。普通的 Banner 广告的平均点击率在 1% 左右,除非你的应用用的人很多否则大部分时候一天能买包辣条就不错了。为了提升效益我们就需要另一套变现的机制——付费去除广告——俗称“专业版”。

免费版带广告且有功能限制,付费版移除广告并激活所有功能,对于工具类的应用而言这算是非常全面的变现方式了。唯一的缺点就是——个人开发者无法接入支付宝和微信支付。好在国内主流的后端云服务提供商 Bmob 给我们提供了一个不错的解决方案。

BMOB功能截图

这不是软文推广,
这不是软文推广,
这不是软文推广,
重要的事情说三遍。

第三方SDK的文档想必你们都会自己看,这里笔者只是简要总结一下在实现过程中遇到的一些问题和解决方案,不一定适用于你的需求,仅作参考。

为了方便论述,在开始之后的正文中笔者假定你已经对 Bmob 提供的服务有所了解。如未曾了解,请参见下方文档:

Bmob数据服务文档
Bmob支付文档

一、 支付凭证的保存

首先“专业版”针对的是单个设备而不是用户本身,因而我们需要一个唯一标志来区分设备,Bmob 已经为我们提供了相关的逻辑。

因而我们需要用来保存支付凭证的表如下:

public class ProDevice extends BmobObject {

    private String installationId;//设备唯一标识码
    private Boolean pro;//是否为专业版
    private String orderId;//购买的订单id

    //一堆set和get不再列出
}

在 Bmob 中创建如下表:

其中 installationId 是唯一的。

你可能会问我为什么不直接在 Bmob 提供的 _Installation 表上新增对应的字段来保存专业的注册信息,这个问题很简单——因为那是一个坑。

一开始笔者就是这么做的,但是在 Bmob 中 _Installation 是表特殊的表,在 Node.js 的后端云中笔者无法对该表进行相应的处理,索性新建一张表算了。

我们可以通过 BmobInstallation.getInstallationId(Context) 来获得installationId,之后只需要查找该表中是否存在对应的数据即可判断当前是否为专业版了。

这里有第二个坑就是:

InstallationId 的计算逻辑放在了 Bmob 提供的 so 文件中,不同平台的 so 文件的实现是不一样的!

如果你一开始为 APK 提供了所有的 so,后期开发中请不要试着去精简这部分的文件。笔者的设备 S7E 在默认情况下会使用 arm64-v8a 的 so 文件,但是精简了之后加载的 so 就变成了 armeabi-v7a,结果就是因为 installationId 的改变导致已付费的设备被判定为未激活状态。

我猜测 Bmob 的开发人员是将 so 对应的平台计入了设备唯一码的算法中,而不是按照设备实际的 CPU 类型来计算。

二、 支付成功的回调接口

Bmob中允许开发者设置支付成功后的回调指定的接口。

从文档中我们可以看到回调中传递参数如下图:

支付回调的参数

你会发现回调所能提供的参数十分有限,好在 Bmob 还提供了订单查询的API,如下:

查询订单

其中的 body 字段是自定义的,用户从支付宝账单还是微信支付的交易详情里面都看不到,换言之我们可以认为这个 body 就是让我们开发者携带数据的。

然而这里有第三个坑:

body 会过滤掉特殊的标点符号,比如 JSON 中必不可少的冒号和括号,并且长度限制在 255 个字符

笔者写下这篇文章的初版还是在16年6月,当时是没有标点符号的限制,后来 Bmob 支付在经历了被举报和重新上线之后这个坑就出现了。在官方的开发群中问相关人员得到的回复就是:这个锅应该让支付宝来背的!

也罢,我们工程师就是为了解决问题而存在的。

新建表

我们创建一张新的表 PayDetail 用于携带数据:

public class PayDetail extends BmobObject{

    //购买专业版的标志,和后面支付的云端回调逻辑对应上即可
    //如果后续有其他需要付费的功能的话,新增一个标志即可
    public static final int FOR_PRO = 0;

    //支付目标,笔者这里的逻辑是为0表示购买专业版
    private Integer payFor;
    //设备唯一码
    private String installationId;
    //用户的邮箱,可以为空
    private String contact;
    //携带的信息数据,视需求使用
    private String msg;

    //一堆set和get不再列出
}

在发起支付前先在服务器创建一条新的数据,并将这条数据的 objectId 作为支付的 body。

接着我们使用 Bmob 提供的云逻辑的服务作为支付完成的回调,笔者的云逻辑的名称是 onPayResult ,代码如下:

function onRequest(request, response, modules) {
    //获取支付订单的参数
    var orderId;
    if ('get' == request.method || "GET" == request.method) { //get
        orderId = request.query.out_trade_no;
    } else { //post
        orderId = request.body.out_trade_no;
    }

    //查询订单详情
    modules.oHttp({
            url: 'https://api.bmob.cn/1/pay/' + orderId,
            headers: {
                'X-Bmob-Application-Id': '这里填写你的ApplicationId',
                'X-Bmob-REST-API-Key': '这里填写你的RestApiId'
            }
        },
        function (err, res, body) {
            if (err || res.statusCode != 200) {
                response.send(JSON.stringify(err));
                return;
            }

            body = JSON.parse(body);
            if (!(body.trade_state == 'SUCCESS')) { //如果支付失败就呵呵了
                response.send("支付未完成");
                return;
            }

            //检测支付结果
            var payDetailId = body.body;
            modules.oData.find(/*检索对应字段是否已存在*/ {
                "table": "PayDetail",
                "where": {
                    "objectId": payDetailId
                }
            }, function (err, data) {
                if (data != undefined && typeof(data) != 'object') {
                    data = JSON.parse(data);
                }

                if (err) { //访问数据库发生错误
                    response.send(JSON.stringify(err));
                } else if (data.results == undefined || data.results.length == 0) { //数据不存在
                    response.send('No data');
                } else { //道路畅通,可以开车!
                    //获取实际的支付详情
                    var payDetail = data.results[0];
                    //获取支付的金额
                    var total_fee = Number(body.total_fee);

                    //这里的索引位置和定义在 PayDetail 类中的静态常量一一对应
                    var payForFuncs = [
                        //0:购买专业版
                        'onPayForPro'
                    ];

                    //将支付成功的逻辑分发到对应的云端逻辑中
                    var functions = modules.oFunctions;
                    functions.run({
                        "name": payForFuncs[payDetail.payFor],
                        "data": {
                            "payDetail": JSON.stringify(payDetail),
                            "total_fee": total_fee,
                            "orderId":orderId
                        }
                    }, function (err, data) {
                        if (err) {
                            response.send(JSON.stringify(err));
                        } else {
                            //返回对应接口的处理结果
                            response.send(data);
                        }
                    });
                }
            });
        });
}                                                      

你可以注意到笔者在这里将支付的回调做了分发处理,这样有利于以后的拓展,比如你还可以添加一个 向开发者捐赠 之类的功能。

接下来让我们来看 onPayForPro 接口该怎么实现:

function onRequest(request, response, modules) {
    var payDetail;
    var total_fee;
    var orderId;
    if ('get' == request.method || "GET" == request.method) { //get
        payDetail = request.query.payDetail;
        total_fee = request.query.total_fee;
        orderId = request.query.orderId;
    } else { //post
        payDetail = request.body.payDetail;
        total_fee = request.body.total_fee;
        orderId = request.body.orderId;
    }

    //将数据转化成最终使用的类型
    if (typeof(payDetail) == 'string') {
        payDetail = JSON.parse(payDetail);
    }
    if(typeof(total_fee) != 'string'){
        total_fee = String(total_fee);
    }

    var db = modules.oData;
    db.find(/*检索对应字段是否已存在*/ {
        "table": "ProDevice",
        "where": {
            "installationId": payDetail.installationId
        }
    }, function (err, data) {
        if (err) {//请求数据库错误
            response.send(JSON.stringify(err));
            return;
        }

        //检查数据是否存在
        var findData = JSON.parse(data).results;
        findData = ( findData && findData.length >= 1) ? findData[0] : undefined;

        if (findData) {//更新已有数据
            db.update({
                "table": "ProDevice",
                "objectId": findData.objectId,
                "data": {
                    "pro": true,
                    "price": total_fee,
                    "orderId": orderId
                }
            }, function (err, data) {
                if (err) {
                    response.send(JSON.stringify("失败了" + err));
                    return;
                }

                //更新成功了
                response.send("success");
            });
        } else {
            db.insert({
                "table": "ProDevice",
                "data": {
                    "installationId": payDetail.installationId,
                    "pro": true,
                    "price": total_fee,
                    "orderId": orderId
                }
            }, function (err, data) {
                if (err) {
                    response.send(JSON.stringify(err));
                    return;
                }

                //插入成功了
                response.send("success");
            });
        }
    });
}                                                                                    

可能你已经注意到笔者返回了 "success"字符串,这是官方文档要求的:

程序执行完后必须打印输出“success”(不包含引号)。如果商户反馈给Bmob的字符不是success这7个字符,Bmob服务器会不断重发通知,直到超过24小时。

Bmob支付回调文档

文档

客户端相关的配置请参阅笔者的下一章节:

Android:基于Bmob实现应用付费购买专业版(二) (待更新)

1
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:37897次
    • 积分:534
    • 等级:
    • 排名:千里之外
    • 原创:18篇
    • 转载:0篇
    • 译文:0篇
    • 评论:17条
    文章分类
    最新评论