8 支付系统¶
Pico支付,是基于Pico账户体系进行的游戏币支付系统,结算方式以现行的Pico公司下的游戏货币单位为准(P币)。
8.1 准备工作¶
8.1.1 获取支付所用字符串¶
开发者在接入支付SDK时,需要在开发者平台创建应用并获取相应字符串。申请流程如下:
> 1. 登录开发者平台并注册Pico会员(http://developer.pico-interactive.com/)
> 2. 申请成为开发者
开发者分为个人开发者和企业开发者,请根据实际情况进行申请。审核提交后,我们会在3个工作日内进行反馈,请及时查看开发者平台状态。
> 3. 查看商户ID
申请成为开发者后,点击右上角昵称可以查看到开发者ID,该ID将作为支付系统中商户的唯一标志:
图8.1 商户ID
注:在申请开发者前,需要先成为Pico会员。
> 4. 获取相应字符串
开发者可以从“应用管理”进入到“创建应用”阶段:
图8.2 创建应用
点击创建应用后,需要选择要发布的平台:
图8.3 选择应用的发布平台
选择平台后即可完善应用的相关信息,包括应用是免费还是付费,付费时需要支付多少P币:
图8.4 完善应用的相关信息
注:请重点注意上图标红位置,请谨慎填写应用类型,一经填写是无法进行修改的!成功创建应用后,开发者平台会对其分配字符串,包括APPKEY,APPID、APP SECRET:
图8.5 APP ID、APP KEY、APP Secret
游戏类应用如果存在道具内付的情况,我们要求开发者必须采用开发者后台增加商品码的方式进行统一管理。请选择“游戏内支付配置”,配置游戏的内购信息:
图8.6 游戏内购配置
注意,商品码的规则定义为‘首位为字母,仅允许输入字母及数字,不超过20位字符’。不同道具间的商品码不能重复。道具类型分为可消耗道具和不可消耗道具。可消耗道具为可重复购买的商品,如金币、血瓶等;不可消耗道具为一次性购买产品,如武器、解锁关卡。
> 5. 填入字符串
进入Edit->Project Settings…,展开Plugins子项下的PicoMobile,勾选“Enable Payment Module”,然后根据实际情况勾选“Is Foreign”,如果需要同时在国内和国外使用,则上述两套需要同时配置,相应的appid等参数以实际为准。最后将获取的商户(开发者)ID、APPID、APP KEY、APP secret填入以下位置:
图8.7 填入字符串
8.1.2 设置回调代理事件¶
使用支付之前,首先要设置回调代理事件,这样您便可以获取回调函数输出的参数,并设置后续执行流程。这里请使用我们提供的PicoPaymentSetCallbackDelegates节点:
其中,On Pico Payment Exception Callback为支付产生各种异常的回调,至于其余各回调函数参数的确切含义,将在下一节介绍其相关的主调函数时介绍。
8.2 其他相关接口¶
8.2.1 登录¶
Pico为开发者提供基于Oauth2.0模式的认证授权,故用户支付前需要先进行登录操作,这里使用我们提供的PicoPaymentLogin节点:
> 回调函数:OnPicoLogInOutCallback,其参数如下:
> IsSuccess:登录、登出是否成功(boolean),true表示成功,false表示失败
> Reason:登录、登出成功或失败的原因
登陆部分可以只登陆一次,之后直接使用支付即可,登陆过期时间约为两周,过期后支付接口回有返回码(登陆过期码),用户只需再次登陆即可。
8.2.3 支付¶
- 函数功能:使用P币支付
- 输入参数Order:
- OrderNumber:商户自己定义的订单号,32个字符内、可包含字母和数字;
- OrderTitle:订单标题;
- ProductDetail:商品描述;
- Notify Url:欲通知的URL(非必填),必须为直接可访问的url,不能携带参数;
- PicoCoinCount:花费P币数额。
- 回调函数OnPicoPayOrderCallback:
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 | 用户已对商品代码消费 |
> 函数功能:使用支付码支付
> 输入参数Order:
> OrderNumber:商户自己生成的订单号,32个字符内、可包含字母和数字;
> OrderTitile:订单标题;
> Product Detail:商品描述;
> Notify Url:欲通知的URL(非必填),必须为直接可访问的url,不能携带参数;
> PicoPayCode:即商品代码,用户通过8.1.1游戏内支付配置获取。
> 回调函数:OnPicoPayOrderCallback,同P币支付。
注:商品码支付是开发者平台的专为游戏设计的新支付方式,开发者需要在开发者平台自己的游戏下创建不同的商品,并填写所属的商品码。在游戏的进行开发时,不必填写物品金额,直接填写对应的商品码即可调用对应的支付接口进行支付。
8.2.4 查询订单¶
> 函数功能:查询订单
> 输入参数OrderNumber:订单号(String)
> 回调函数OnPicoQueryOrderCallback:
> 参数含义同OnPicoPayOrderCallback
8.2.5 获取用户信息¶
> 函数功能:获取用户信息
> 回调函数:OnPicoGetUserInfoCallback:
> 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 支付结果通知中的通知参数” | |||||||||
请求参数示例 | ||||||||||
返回参数 |
|
|||||||||
返回参数实例 | { “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进行校验
}