9 支付系统

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

9.1 准备工作

9.1.1 支付需要如下的应用参数

  • APP ID : 开发者平台为应用生成的唯一标识
  • APP KEY : 开发者平台为应用生成的Key
  • APP Secret : 开发者平台为应用生成的用于支付的Key
  • Developer ID : 开发者的ID,也叫商户ID

9.1.2 申请成为Pico开发者

Pico开发者平台网址https://developer.pico-interactive.com/

开发者在接入支付SDK时,需要在开发者平台创建应用并获取 应用参数 ,对应流程如下:

  1. Pico 开发者平台页 点击「成为开发者」按钮。
  1. 在「账户」弹窗中选择您账户所在区域:中国大陆/其他地区 后,点击「注册」按钮。
_images/9.1.2.1.png
  1. 验证您的账户
  • 若您选择了「中国大陆」,您需要填写您的手机号并设置您的密码,通过短信验证您的手机号;

  • 若您选择了「其他地区」,您需要填写您的邮箱并设置您的密码,并选择您账号所在国家/地区,通过验证邮件验证您的邮箱账号;

    _images/9.1.2.2.png
  1. 勾选接受 Pico「用户许可协议」和「隐私政策」,点击「注册」按钮。
  1. 恭喜!您已经正式成为 Pico 开发者啦。

注意: Pico 开发者账户注册完全免费

9.1.3 获取应用参数

  1. 获取 Publisher ID

申请成为开发者后,进入开发者管理平台,点击「我的应用」,进入「应用」后点击「API」菜单,即可查看到开发者 ID,该 ID 将作为支付系统中商户的唯一标志。

  1. 创建应用,选择支付类型

开发者可以从管理中心进入到创建应用阶段,点击创建应用,然后进入相应平台完善应用的相关信息:

_images/9.1.3.1.png

图9.1 完善应用的相关信息

注意: 应用类型和支付相关,审核通过上架后,无法更改。一个应用只能选择一种。

  1. 选择应用 。支付时使用 P 币数量直接支付。
  2. 选择游戏 。支付是商品码支付。商品码为游戏中付费道具在开发者后台的唯一标识。

游戏类应用如果存在道具内付的情况,我们要求开发者必须采用开发者后台增加商品码的方式进行统一管理。

商品码支付配置界面如下:

再选择“游戏内支付配置”,配置游戏的内购信息:

_images/9.1.3.2.png

图9.2 游戏内购配置

注意商品码的定义方式:

  • 定义规则。首位为字母,仅允许输入字母及数字,不超过20个字符
  • 不可重复。不同道具间的商品码不能重复
  • 类型支持。目前支持可消耗道具 和 不可消耗道具 。可消耗道具为可重复购买的商品,如金币、血瓶等;不可消耗道具为一次性购买产品,如武器、解锁关卡。
  1. 获取 APP ID,APP KEY,APP Secret 参数

成功创建应用后,开发者平台会对其分配字符串,包括 APP ID、APP KEY、APP Secret

_images/9.1.3.3.png

图9.3 APP ID、APP KEY、APP Secret

至此,Developer ID ,APP ID、APP KEY、APP Secret 都已获取到了。

9.1.4 OnlinePico Settings中配置应用参数

进入Edit->Project Settings…,展开Plugins子项下的OnlinePico Settings,勾选“Enable Payment Module”,然后根据实际情况选择Region:如果需要国内上线则选择China,海外上线选择NonChina,国内海外同时上线则选择Both。

_images/9.1.4.1.png

图9.4 选择上线地域

最后将获取的商户ID(Developer ID)、APPID、APP KEY、APP secret填入以下位置:

_images/9.1.4.2.png

图9.5 填入字符串

9.2 使用支付系统

_images/9.2.1.jfif

图9.6 支付系统使用流程

  • 需要确保首先调用登录接口(Pico Login SDK或Pico Payment Login)返回成功后,再调用支付(Pico Payment Pay with Coin、Pico Payment Pay with Pay Coin)、查询订单(Pico Payment Query)、获取用户信息(Pico Payment Get User Info、Pico SDKGet User Info)等接口。
  • 登陆成功后,如需登出操作,则调用登出接口(Pico Logout SDK、Pico Payment Logout)。
  • 以上接口在使用过程中,均需要绑定回调事件获取调用结果,具体各个接口的详细用法见9.3 接口功能说明。

9.3 接口功能说明

9.3.1 登录

Pico为开发者提供基于Oauth2.0模式的认证授权,故用户支付前需要先进行登录操作。注:登陆部分可以只登陆一次,之后直接使用支付即可,登陆过期时间约为两周,过期后支付接口回有返回码(登陆过期码),用户只需再次登陆即可。

这里使用我们提供的PicoPaymentLogin节点或者PicoLoginSDK节点:

Pico Login SDK

通过该蓝图接口可以进行登录,并且通过绑定回调事件获取登录结果。

蓝图

_images/9.3.1.1.png

输入:

  • delegate类型:

    • Log in Delegate:绑定Login回调事件,如下图所示:

      _images/9.3.1.2.png
      • 回调事件参数:
        • bool类型:
          • IsSucceed:
            • true:成功
            • false:失败
        • FString类型:
          • Reason: 通过字符串输出登录失败原因,见表 9-1 Reason Code对照表

输出:

返回值:

Pico Payment Login

通过该蓝图接口可以进行登录,并且通过绑定回调事件获取登录结果。

蓝图

_images/9.3.1.3.png

输入:

  • delegate类型:

    • Log in Delegate:绑定Login回调事件,如下图所示:

      _images/9.3.1.4.png
      • 回调事件参数:
        • bool类型:
          • IsSucceed:
            • true:成功
            • false:失败
        • FString类型:
          • Reason: 通过字符串输出登录失败原因,见表 9-1 Reason Code对照表

输出:

返回值:

9.3.2 登出

登出操作可以使用PicoLogoutSDK或者PicoPaymentLogout节点:

Pico Logout SDK

通过该蓝图接口可以进行登出,并且通过绑定回调事件获取登出结果。

蓝图

_images/9.3.2.1.png

输入:

  • delegate类型:

    • Log in Delegate:绑定Logout回调事件,如下图所示:

      _images/9.3.2.2.png
      • 回调事件参数:
        • bool类型:
          • IsSucceed:
            • true:成功
            • false:失败
        • FString类型:
          • Reason: 通过字符串输出登录失败原因,见表 9-1 Reason Code对照表

输出:

返回值:

Pico Payment Logout

通过该蓝图接口可以进行登出,并且通过绑定回调事件获取登出结果。

蓝图

_images/9.3.2.3.png

输入:

  • delegate类型:

    • Log in Delegate:绑定Logout回调事件,如下图所示:

      _images/9.3.2.4.png
    • 回调事件参数:

      • bool类型:

        • IsSucceed:
          • true:成功
          • false:失败
      • FString类型:

        • Reason:通过字符串输出登录失败原因,见表 9-1 Reason Code对照表

输出:

返回值:

表 9-1 Reason Code对照表

错误信息 说明 原因
TIME_ERROR 时间戳错误 本地时间和服务器时间相差超过8分钟
SYSTEM_USER_NOT_FIND_ERROR 用户未找到 用户中心token对应的用户未找到
SYSTEM_USER_TOEKN_NOT_FIND_ERROE 用户token寻找失败 用户中心token未找到
SYSTEM_USER_TOKEN_CHECK_FAILURE_ERROR 用户token验证失败 用户中心token失效
SYSTEM_USER_TOKEN_UNKNOWN_ERROR 用户token验证失败 用户中心token检查失败
APP_CHECK_ERROR 应用验证失败 APP ID等参数填写错误

9.3.3 支付

Pico Payment Pay with Coin

通过该蓝图接口可以使用P币进行支付。

蓝图

_images/9.3.3.1.png

输入:

_images/9.3.3.2.png
  • FString类型:

    • OrderNumber:商户自己定义的订单号,32个字符内、可包含字母和数字
    • OrderTitle:订单标题
    • ProductDetail:商品描述
    • Notify Url:欲通知的URL(非必填),必须为直接可访问的url,不能携带参数
  • int类型:

    • PicoCoinCount:花费P币数额
  • delegate类型:

    • Pay Order with Coin Delegate:绑定支付回调事件,如下图所示:

      _images/9.3.3.3.png
    • 回调事件参数:
      • FString类型:
        • Code:见表9-2 支付Code与Message对应表
        • Message:见表9-2 支付Code与Message对应表

输出:

返回值:

Pico Payment Pay with Code

通过该蓝图接口可以使用支付码支付。

蓝图

_images/9.3.3.4.png

输入:

_images/9.3.3.5.png
  • FString类型:

    • OrderNumber:商户自己生成的订单号,32个字符内、可包含字母和数字
    • OrderTitile:订单标题
    • Product Detail:商品描述
    • Notify Url:欲通知的URL(非必填),必须为直接可访问的url,不能携带参数
    • PicoPayCode:即商品代码,用户通过9.1.1游戏内支付配置获取
  • delegate类型:

    • Pay Order with Coin Delegate:绑定支付回调事件,如下图所示:

      _images/9.3.3.6.png
      • 回调事件参数:
        • FString类型:
          • Code:见表 9-2 支付Code与Message对应表
          • Message:见表 9-2 支付Code与Message对应表

输出:

返回值:

表 9-2 Code与Message对应表

Code Message 信息
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 用户已对商品代码消费

9.3.4 查询订单

Pico Payment Query Order

通过该蓝图接口可以查询订单。

蓝图

_images/9.3.4.1.png

输入:

  • FString类型:

    • Order Number:订单号(String)
  • delegate类型:

    • Query Order Delegate:绑定订单查询回调事件,如下图所示:

      _images/9.3.4.2.png
    • 回调事件参数: - FString类型:

      • Code:见表 9-2 支付Code与Message对应表
      • Message:见表 9-2 支付Code与Message对应表

输出:

返回值:

9.3.5 获取用户信息

Pico Payment Get User Info

蓝图

_images/9.3.5.1.png

输入:

  • delegate类型:

    • User Info Delegate:绑定获取用户信息回调事件,如下图所示:

      _images/9.3.5.2.png
    • FString类型:
      • Info:一个未经处理的Json串(string),见查询结果范例与表 9-3 OnPicoGetUserInfoCallback输出参数ret_code码及ret_msg一览

输出:

返回值:

Pico SDKGet User Info

蓝图

_images/9.3.5.3.png

输入:

  • delegate类型:

    • User Info Delegate:绑定获取用户信息回调事件,如下图所示:

      _images/9.3.5.4.png
    • FString类型:
      • Info:一个未经处理的Json串(string),见查询结果范例与表 9-3 OnPicoGetUserInfoCallback输出参数ret_code码及ret_msg一览

输出:

返回值:

查询结果范例

Info:一个未经处理的Json串(string),查询成功范例如下:

{"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一览:

表 9-3 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验证失败

9.4 开发者服务端交互

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

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

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

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

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

名称 支付结果回调接口
请求类型 POST
请求URL 支付,PayOrder传入的参数notify_url
请求格式 JSON
返回格式 JSON
是否需要登录
请求参数 详见下面“表9.3 支付结果通知中的通知参数”
请求参数示例  
返回参数 参数名 类型及范围 说明 ret_code string 错误代码。 ret_msg string 错误信息字符串。 详见“表9.4 返回结果”
返回参数实例 { “ret_code”:”SUCCESS”, “ret_msg”:”OK” }
更新说明  

表9-5 支付结果通知中的通知参数

字段名 变量名 必填 类型 描述
返回状态码 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位。推荐随机数生成算法(https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_3
签名 signature String 签名,详见签名生成算法(https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_3
业务结果 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

表9.6 返回结果

字段名 变量名 必填 类型 描述
返回状态码 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进行校验
}