8 支付系统

Pico支付,是基于Pico账户体系进行的游戏币支付系统,结算方式以现行的Pico公司下的游戏货币单位为准(P币)。

8.1 准备工作

8.1.1 获取支付所用字符串

开发者在接入支付SDK时,需要在开发者平台创建应用并获取相应字符串。申请流程如下:

> 1. 登录开发者平台并注册Pico会员(http://developer.pico-interactive.com/

> 2. 申请成为开发者

开发者分为个人开发者和企业开发者,请根据实际情况进行申请。审核提交后,我们会在3个工作日内进行反馈,请及时查看开发者平台状态。

> 3. 查看商户ID

申请成为开发者后,点击右上角昵称可以查看到开发者ID,该ID将作为支付系统中商户的唯一标志:
_images/7.1.png

图8.1 商户ID

注:在申请开发者前,需要先成为Pico会员。

> 4. 获取相应字符串

开发者可以从“应用管理”进入到“创建应用”阶段:
_images/7.2.png

图8.2 创建应用

点击创建应用后,需要选择要发布的平台:

_images/7.3.png

图8.3 选择应用的发布平台

选择平台后即可完善应用的相关信息,包括应用是免费还是付费,付费时需要支付多少P币:

_images/7.4.png

图8.4 完善应用的相关信息

注:请重点注意上图标红位置,请谨慎填写应用类型,一经填写是无法进行修改的!成功创建应用后,开发者平台会对其分配字符串,包括APPKEY,APPID、APP SECRET:

_images/7.5.png

图8.5 APP ID、APP KEY、APP Secret

游戏类应用如果存在道具内付的情况,我们要求开发者必须采用开发者后台增加商品码的方式进行统一管理。请选择“游戏内支付配置”,配置游戏的内购信息:

_images/7.6.png

图8.6 游戏内购配置

注意,商品码的规则定义为‘首位为字母,仅允许输入字母及数字,不超过20位字符’。不同道具间的商品码不能重复。道具类型分为可消耗道具和不可消耗道具。可消耗道具为可重复购买的商品,如金币、血瓶等;不可消耗道具为一次性购买产品,如武器、解锁关卡。

> 5. 填入字符串

进入Edit->Project Settings…,展开Plugins子项下的PicoMobile,勾选“Enable Payment Module”,然后根据实际情况勾选“Is Foreign”,如果需要同时在国内和国外使用,则上述两套需要同时配置,相应的appid等参数以实际为准。最后将获取的商户(开发者)ID、APPID、APP KEY、APP secret填入以下位置:
_images/7.8.png

图8.7 填入字符串

8.1.2 设置回调代理事件

使用支付之前,首先要设置回调代理事件,这样您便可以获取回调函数输出的参数,并设置后续执行流程。这里请使用我们提供的PicoPaymentSetCallbackDelegates节点:

_images/unsigned_7121.png

其中,On Pico Payment Exception Callback为支付产生各种异常的回调,至于其余各回调函数参数的确切含义,将在下一节介绍其相关的主调函数时介绍。

8.2 其他相关接口

8.2.1 登录

Pico为开发者提供基于Oauth2.0模式的认证授权,故用户支付前需要先进行登录操作,这里使用我们提供的PicoPaymentLogin节点:

_images/unsigned_71310.png

> 回调函数:OnPicoLogInOutCallback,其参数如下:

_images/unsigned_7132.png

> IsSuccess:登录、登出是否成功(boolean),true表示成功,false表示失败

> Reason:登录、登出成功或失败的原因

登陆部分可以只登陆一次,之后直接使用支付即可,登陆过期时间约为两周,过期后支付接口回有返回码(登陆过期码),用户只需再次登陆即可。

8.2.2 登出

_images/unsigned_72110.png

> 函数功能:登出

> 其回调函数:OnPicoLogInOutCallback已在上文介绍。

8.2.3 支付

_images/unsigned_7231.png
  • 函数功能:使用P币支付
  • 输入参数Order:
_images/unsigned_7232.png
  • OrderNumber:商户自己定义的订单号,32个字符内、可包含字母和数字;
  • OrderTitle:订单标题;
  • ProductDetail:商品描述;
  • Notify Url:欲通知的URL(非必填),必须为直接可访问的url,不能携带参数;
  • PicoCoinCount:花费P币数额。
  • 回调函数OnPicoPayOrderCallback:
_images/unsigned_7233.png

Code及Msg如下:

ret_code ret_msg
00000 网络异常
10000 登录成功
10001 用户未登陆
10002 请输入正确金额
10003 登陆过期,请重新登陆
11000 商户验证成功
11001 商户验证失败
11002 用户验证参数错误或请求过期
11003 商户未验证
12000 支付成功
12001 支付失败
12003 P币不足
12004 余额可用
13000 生成订单
13001 获取数据失败
13002 生成订单失败
14000 查询订单成功
14001 订单不存在/有误
14002 用户取消支付操作
15000 未输入商品信息
15001 未输入预付ID
15002 请输入Pico支付订单号或商户订单号
NOAUTH 商户无此接口权限
SYSTEMERROR 系统错误
APP_ID_NOT_EXIST APP_ID不存在
MCHID_NOT_EXIST MCHID 不存在
APP_ID_MCHID_NOT_MATCH app_id和mch_id不匹配
LACK_PARAMS 缺少参数
SIGNERROR 签名错误
NO_DATA 没有查询到数据/用户未充值
ORDER_EXIST 订单已存在
PAY_CODE_NOT_EXIST 消费代码不存在
PAY_CODE_EXIST 用户已对商品代码消费
_images/unsigned_7241.png

> 函数功能:使用支付码支付

> 输入参数Order:

_images/unsigned_7242.png

> OrderNumber:商户自己生成的订单号,32个字符内、可包含字母和数字;

> OrderTitile:订单标题;

> Product Detail:商品描述;

> Notify Url:欲通知的URL(非必填),必须为直接可访问的url,不能携带参数;

> PicoPayCode:即商品代码,用户通过8.1.1游戏内支付配置获取。

> 回调函数:OnPicoPayOrderCallback,同P币支付。

注:商品码支付是开发者平台的专为游戏设计的新支付方式,开发者需要在开发者平台自己的游戏下创建不同的商品,并填写所属的商品码。在游戏的进行开发时,不必填写物品金额,直接填写对应的商品码即可调用对应的支付接口进行支付。

8.2.4 查询订单

_images/unsigned_7251.png

> 函数功能:查询订单

> 输入参数OrderNumber:订单号(String)

> 回调函数OnPicoQueryOrderCallback:

_images/unsigned_7252.png

> 参数含义同OnPicoPayOrderCallback

8.2.5 获取用户信息

_images/8.0.png

> 函数功能:获取用户信息

> 回调函数:OnPicoGetUserInfoCallback:

_images/unsigned_7222.png

> Info:一个未经处理的Json串(string),查询成功范例如下: .. code-block:: java

{“ret_code”:”0000”, “data” : {

“aboutme”:””,
“birthday” : 1460476800000, “phone” : “13100000000”, “username” : “Admin”, “email” : “”, “gender” : “male”, “lastname” : “”, “openid” : “4f3148bdc34d9bca104927729a173b64”, “firstname” : “”, “avatar” : “http://172.31.83.11/upload/6dd6ee103714e967846c3d38ae48d511”, “signature” : “14a25d7219d8dfc91e55f63286ae5c0a”, “country” : “China”, “city” : “”

}, “ret_msg”:”调用成功” }

查询失败范例如下:

{
"ret_code":"00003000",
"ret_msg" : "签名验证失败"
}

其他ret_code码及ret_msg一览:

表8.1 OnPicoGetUserInfoCallback输出参数ret_code码及ret_msg一览

ret_code ret_msg
0000 请求成功
00020000 数据库操作失败
9999 系统错误
00001000 参数错误
00002000 数据解析失败
00003000 签名验证失败
00003001 时间验证失败
00060000 用户未找到
00060001 用户密码错误
00060002 用户登录未知错误
00061000 用户token未找到失败
00061001 用户token验证失败
00061002 用户token未知错误
00070001 应用验证失败
00071001 应用密钥验证失败
00080001 OAUTH_CODE验证失败
00090001 REFRESH_TOKEN验证失败
00100001 ACCESS_TOKEN验证失败
00110001 SCOPE验证失败

8.3 开发者服务端交互

支付完成后,支付系统会把相关支付结果和用户信息发送给商户,商户需要接收处理,并返回应答。

对后台通知交互时,如果支付系统收到商户的应答不是成功或超时,则认为通知失败,支付系统会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但不保证通知最终能成功。

同样的通知可能会多次发送给商户系统,商户系统必须能够正确处理重复的通知。推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。

商户服务端需要实现下面的接口,用于接收来自Pico服务器请求,获取Pico支付系统的支付结果和用户信息:

表8.2 商户服务器需要实现的接口

名称 支付结果回调接口
请求类型 POST
请求URL 支付,PayOrder传入的参数notify_url
请求格式 JSON
返回格式 JSON
是否需要登录
请求参数 详见下面“表8.3 支付结果通知中的通知参数”
请求参数示例  
返回参数
Parameter Type Description
ret_code string Error code
ret_msg string Error message
详见下面“表8.4 返回结果”
返回参数实例 { “ret_code”:”SUCCESS”, “ret_msg”:”OK” }
更新说明  

表8.3 支付结果通知中的通知参数

字段名 变量名 必填 类型 描述
返回状态码 ret_code String SUCCESS/FAIL此字段是通信标识,非交易标识,交易是否成功需要查看result_code来判断
返回信息 ret_msg String 返回信息,如非空,为错误原因:函数名失败参数格式校验错误
错误代码 sub_code String 错误码
错误代码描述 sub_msg String 错误返回的信息描述
Pico支付订单号 trade_no String Pico支付订单号
商户订单号 out_trade_no String 商户系统内部的订单号
应用ID app_id String 平台审核通过的应用APP_ID
商户ID mch_id String 支付分配的商户号
用户标识 open_id String 用户在商户appid下的唯一标识
设备号 device_id String 终端设备号
随机字符串 nonce_str String 随机字符串,不长于32位。推荐随机数生成算法
函数名 signature String 函数名,详见函数名生成算法
业务结果 result_code String SUCCESS/FAIL
交易类型 trade_type String 支付类型
货币类型 fee_type String 货币类型
总金额 total_fee String 订单总金额
实收金额 receipt_fee String 实收金额
买家付款的金额 buyer_pay_fee String 买家付款的金额
代金券或立减优惠金额 coupon_fee String 代金券或立减优惠金额
商家数据包 attach String 商家数据包,原样返回
支付完成时间 pay_time String 支付完成时间,格式为yyyy-MM-dd HH:mm:ss

表8.4 返回结果

字段名 变量名 必填 类型 描述
返回状态码 ret_code String SUCCESS/FAIL SUCCESS表示商户接收通知成功并校验成功
返回信息 ret_msg String 返回信息,如非空,为错误原因:函数名失败参数格式校验错误

特别提醒:商户系统对于支付结果通知的内容一定要做函数名验证,防止数据泄漏导致出现“假通知”,造成资金损失。

函数名校验规则是:

1.返回的参数列表,去掉signautre参数,同时添加key=“app_secret”,value=paykey,然后根据key值进行自然排序,多个参数之间用&隔开,最后进行MD5加密

2.用加密后的字符串和获取到的signature进行比较

函数名函数如下:

/**
* result :获取的数据的map集合
* paykey :就是开发者平台上的paykey
*/
public static String createSign(Map<String, Object> result, String paykey)
{
    if (result == null || result.size() == 0)
        return null;
    result.put("app_secret", paykey); //1.添加key = “app_secret”,value=payke
    String sign = result.get("signature");//2.保存signature的值,用于校验
    result.remove("signature"); //3.移除signature参数
    String[] tmp = new String[result.size()];
    int i = 0;
    for (String key : result.keySet())
    {
        tmp[i++] = key;
    }
    Arrays.sort(tmp); //4.自然排序
    String signTemp = "";
    for (String string : tmp)
    {
        if (result.get(string) == null)
            continue;
        signTemp += string + "=" + URLEncoder.encode(result.get(string).toString()
            , "utf-8") + "&";
    }
    if (signTemp.endsWith("&"))
        signTemp = signTemp.substring(0, signTemp.length() - 1);
    Log.i(TAG, "createSign: " + signTemp);
    String localSign = MD5.MD5(sign); //5.生成MD5加密后的字符串
    return localSign.equal(sign);//6.和2中的sign进行校验
}