7 支付系统¶
7.1 开发环境要求¶
本文档主要针对Android开发工程师,已经安装Eclipse+ADT或者Android studio 的Android 开发环境,ADB 驱动和对应SDK 版本,此处环境配置不再赘述。
7.1.1 Android开发环境要求¶
软件名称 | 版本信息 |
---|---|
JDK | jdk 1.7.0_01 及以上 |
Android SDK | API Level 19 及以上 |
7.1.2 引入SDK的JAR包¶
将SDK包里提供的jar包放到Eclipse或者AndroidStudio libs文件夹下,如图所示,jar包名称以实际为准:

Eclipse或者AndroidStudio中,右键加入到工程的library中。
7.1.3 获取KEY¶
如果您还不是一个Pico的开发者,进入Pico官网开发者中心注册开发者账号,注册后通过申请,获取相应游戏或者应用的APP ID、APP KEY和APP SECRET以及商户ID,详情请在管理中心查看。
这里需要注意的是,您需要确认您所开发的APP,是应用类型还是游戏类型,这样所对应的支付方式也不一样,详情请查看 7.3.4 进行支付.
7.2 SDK包介绍¶
7.2.1 支付Demo目录结构¶
可以在工程中看到如 图7.2.1所示的层级目录

图 7.2.1 目录结构
整个工程是一个实现SDK功能的Demo。
“libs”目录为该SDK编译成的JAR,为Demo工程所依赖。
“src”目录下存放Demo程序的源码。
开发者可以导入工程到AndroidStudio中进行运行。
7.3 使用支付¶
7.3.1 相应参数及名称解释¶
Pico为开发者提供基于Oauth2.0模式的认证授权,使第三方应用或游戏无需用于登陆即可进行授权登陆操作,提供客户端授权模式。在授权之后,使用Pico提供的开放接口,可以获取用户的基本信息,方便第三方开发者进行集成。
参数名称 | 参数应用 | 参数来源 |
---|---|---|
APP_ID | 分配给每个第三方应用的AppId,用于鉴权身份 | 开发者平台 |
SCOPE | 申请可授权的内容或范围 | 开发者平台 (现暂定:get_user_info) |
APP_KEY | 分配给每个第三方应用的appkey | 开发者平台 |
DEVELOPER_ID | 开发者ID | 开发者平台 |
App_SECRET | 支付KEY | 开发者平台 |
支付简介:
1.Pico支付,是基于Pico账户体系进行的游戏币支付系统,在使用相关接口前,需要您先进行登陆,然后您就可以使用支付功能了。
2.支付结算方式,以现行的Pico公司下的游戏货币单位为准(P币)。
3.账户充值请到 Pico用户中心充值
7.3.2 使用SDK的相关配置¶
>申请应用程序的APPKEY,APPID、SCOPE、DEVELOPERID、APP SECRET
第三方开发者要去Pico开发者平台上对应用或游戏进行注册,获取APPKEY,APPID和SCOPE,DEVELOPERID, APP SECRET,请查看 7.1.3 获取KEY
>AndroidManifest.xml配置开发参数
将申请下的APP KEY,APP ID,SCOPE,DEVELOPER ID,APP SECRET参数写到AndroidManifest.xml文件中
>完整的AndroidManifest.xml配置如下
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="your package">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
. . . . . .
<!--APPID-->
<meta-data
android:name="pico_app_id"
android:value="APP ID"/>
<!--APPKEY-->
<meta-data
android:name="pico_app_key"
android:value="APPK EY"/>
<!--授权范围-->
<meta-data
android:name="pico_scope"
android:value="SCOPE"/>
<!—开发者ID-->
<meta-data
android:name="pico_merchant_id"
android:value="DEVELOPER ID"/>
<!--支付Key-->
<meta-data
android:name="pico_pay_key"
android:value="APP SECRET"/>
</application>
</manifest>
>导入JAR包
导入方式:直接导入loginpaysdk.jar
1.将jar包拷贝到工程目录下的lib文件夹下
2.右击jar包将其添加到工程中
具体情况分Eclipse和AndroidStudio,这里不再赘述。
7.3.3 先进行登陆¶
登陆需要实现以下几个步骤:
>使用login方法
// 1.创建授权的核心类
Login mLogin = new Login(activity);//此处参数必须传入Activity的对象,否则会导致登陆失败
// 2.使用login进行认证授权
mLogin.login(new Callback());//MloginCallback参数请看:实现回调loginCallBack
>实现回调CallBack
登陆结果将通过这个接口进行回调,isSuccess返回true表示登陆成功,false表示登录失败,reason会返回相关登陆信息。
Callback callback = new Callback() {
@Override
public void loginCallback(boolean isSuccess, String reason) {
//1.isSuccess返回true时为登陆成功
//2.isSuccess返回false时为登陆失败
//3.reason参数回显示具体登陆成功失败的信息
if (isSuccess) {//
Toast.makeText(AuthActivity.this, "登陆成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(AuthActivity.this,
reason,
Toast.LENGTH_LONG).show();
}
}
}
>复写onActivityResult
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
//发起客户登陆的 Activity 必须重写 onActivityResults,写法如下
if (mLogin != null)
{
mLogin.authorizeCallBack(requestCode, resultCode, data);
}
}
至此,一个完整的Pico认证授权流程已经完成。
登陆部分可以只登陆一次,之后直接使用支付即可,登陆过期时间约为两周,过期后支付接口会有返回码(7.5 返回码对照表),用户只需再次登陆即可。
>获取用户头像昵称等信息
同样使用核心类Login:
mLogin.getUserInfo(new RequestListener() {
@Override
public void onComplete(String paramString) {
//传回的是json数据,自行解析,请查看下面的返回结果示例
}
@Override
public void onException(PicoException paramException) {
//异常信息
}
});
返回结果示例:
openid一定回返回,其他数据视用户资料完整度与否而进行返回。
{
"ret_code": "0000",
"data": {
"aboutme": "",
"birthday": 1460476800000,
"phone": "13585455789",//手机号
"username": "北极星", //用户名
"email": "123456@163.com",//邮箱
"gender": "male",
"lastname": "",
"openid": "4f3148bdc34d9bca104927729a173b64",//唯一标识(一定返回)
"firstname": "",
"avatar": "http://172.31.83.11/upload/6dd6ee103714e967846c3d38ae48d511",
"signature": "14a25d7219d8dfc91e55f63286ae5c0a",
"country": "China",
"city": ""
},
"ret_msg": "调用成功"
}
>注销登陆
当账号推出登陆时进行调用,清除登陆数据:
mLogin.login(Context context,Callback callback);//callback参数请看:实现回调loginCallBack
注意: 由于登陆的模式为授权方式,所以如果不进行调用注销登陆的操作,在账号进行推出时,会在一定时间内还可以进行支付操作,所以建议进行调用。
7.3.4 进行支付¶
开始支付前,请确认已经进行了登陆,详情查看:7.3.3 先进行登陆。
支付部分的支付方式分为两种方式:
APP | 支付方式 |
---|---|
应用 | P币支付 |
游戏 | 商品码支付 |
注: 商品码支付,是开发者平台的新支付方式,专为游戏设计,开发者需要在开发者平台自己的游戏下创建不同的商品,并填写所属的商品码,然后在游戏的进行开发时,不必填写物品金额,直接填写对应的商品码即可调用对应的支付接口进行支付。
当你在开发者平台创建应用后,就以为你已经确定使用了上述两种支付方式的其中一种,两种支付方式是互斥的。
1. 调用支付接口:
参数说明(7.3.1 相应参数及名称解释):
- context : 上下文环境
- PayOrder : 包含订单信息的PayOrder类的对象,请查看PayOrder说明
- PayCallback : 支付回调接口
PicoPay.getInstance(context).pay(PayOrder,PayCallBack);
支付的回调信息,有上面Callback执行,详情请查看 PayCallBack支付的回调
PayOrder 为包含订单信息的Model类,因为支付方式分为两类,所以必传的参数有所不同,以下为创建方式和必传参数:
- P币支付(普通支付方式)—添加所需P币数额
//订单信息--以下为必传参数
PayOrder order = new PayOrder(); //创建订单对象
order.setBusinessOrder(订单号); //商户自己生成的订单号,32个字符内、可包含字母和数字
order.setTotalFree(money); //花费P币数额
order.setSubject("订单标题"); //订单标题
order.setBody("商品描述"); //商品描述
order.setNotifyUrl("http:www.picovr.com"); //回调地址—游戏服务器通知地址(非必填)见开发者服务端交互
- 商品码支付——————-添加所需商品码
//订单信息--以下为必传参数
PayOrder order = new PayOrder();
order.setBusinessOrder(订单号);
order.setPayCode( 商品码 ); //所需商品码
order.setSubject("订单标题");
order.setBody("商品描述");
order.setNotifyUrl("http:www.picovr.com");
PayCallBack支付的回调
class MyPaySdkCallBack implements PaySdkCallBack {
@Override
public void callback(String code, String msg) {
//支付回调接口,回调信息为Code和Msg,对应信息请查看 返回码对照表
}
@Override
public void exceptionCallBack(String msg) {
//异常信息回调
}
}
7.3.5 查询订单信息¶
参数说明(请参见 7.3.1 相应参数及名称解释):
- context : 上下文环境
- orderNum : 开发者自己生成的订单号,请参见:
PicoPay.getInstance(context).queryOrders(orderNum,new Callback());
支付的回调信息,由上面Callback执行,msg此时返回的时获取的json信息,详情请查看 7.3.4 进行支付
返回的msg参数示例为,注释为关键参数:
{
"trade_no":"22016082314719505878171324",//Pico支付订单号
"open_id":"4f3148bdc34d9bca104927729a173b64",
"ret_msg":"",
"coupon_fee":0.00,
"fee_type":"PIC",
"pay_time":1471950587000,//支付完成时间
"nonce_str":"yiUzuv4VQO1OXBAzVyZSRztOmRgIOioT",
"out_trade_no":"12345678903",//商户系统内部订单号,即支付时传入的订单号
"trade_status":"SUCCESS", //SUCCESS—支付成功
//REFUND—转入退款
//NOTPAY—未支付
//CLOSED—已关闭
//REVOKED—已撤销
//USERPAYING--用户支付中
//PAYERROR--支付失败
//FINISHED--交易结束不可退款
"trade_type":"EGG",
"result_code":"SUCCESS",
"mch_id":"company_id",
"ret_code":"SUCCESS",
"sub_msg":"OK",
"total_fee":100.00,//订单总金额
"app_id":"bf18ac2de375095d63428134e44d1867",
"sub_code":"SUCCESS",
"receipt_fee":100.00,//实收金额
"signature":"be3fae4d68fec9c444fde821659bce69",
"buyer_pay_fee":100.00//买家付款的金额
}
7.5 返回码对照表¶
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 | MCHID不存在 |
APP_ID_MCHID_NOT_MATCH | app_id和mch_id不匹配 |
LACK_PARAMS | 缺少参数 |
SIGNERROR | 签名错误 |
NO_DATA | 没有查询到数据/用户未充值 |
ORDER_EXIST | 订单已存在 |
PAY_CODE_NOT_EXIST | 消费代码不存在 |
PAY_CODE_EXIST | 用户已对商品代码消费 |
7.6 关于混淆¶
需要在app目录下的proguard-rules.pro(或者proguard-rules.txt)中,添加如下代码:
-keep class com.pico.loginpaysdk.** { *; }
7.7 开发者服务端交互¶
支付完成后,支付系统会把相关支付结果和用户信息发送给商户,商户需要接收处理,并返回应答。
对后台通知交互时,如果支付系统收到商户的应答不是成功或超时,则认为通知失败,支付系统会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但不保证通知最终能成功。
注意:同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。
推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
特别提醒:商户系统对于支付结果通知的内容一定要做签名验证,防止数据泄漏导致出现“假通知”,造成资金损失。 请查看 7.7.3 签名校验
接口链接是通过支付时,PayOrder中提交的参数notify_url,如果链接无法访问,商户将无法接收到通知。通知url必须为直接可访问的url,不能携带参数。
商户服务端需要实现下面的接口,用于接收来自Pico服务器请求,获取Pico支付系统的支付结果和用户信息
名称 | 支付结果回调接口 | |||||||||
请求类型 | POST | |||||||||
请求URL | 支付,PayOrder传入的参数notify_url | |||||||||
请求格式 | JSON | |||||||||
返回格式 | JSON | |||||||||
是否需要登录 | 是 | |||||||||
请求参数 | 详见 7.7.1 通知参数 支付结果通知中的通知参数” | |||||||||
请求参数示例 | ||||||||||
返回参数 |
|
|||||||||
返回参数实例 | { “ret_code”:”SUCCESS”, “ret_msg”:”OK” } | |||||||||
更新说明 |
7.7.1 通知参数¶
字段名 | 变量名 | 必填 | 类型 | 描述 |
---|---|---|---|---|
返回状态码 | 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 |
7.7.2 返回结果¶
字段名 | 变量名 | 必填 | 类型 | 描述 |
---|---|---|---|---|
返回状态码 | ret_code | 是 | String | SUCCESS/FAIL SUCCESS表示商户接收通知成功并校验成功 |
返回信息 | ret_msg | 否 | String | 返回信息,如非空,为错误原因:函数名失败参数格式校验错误 |
结果
举例如下:
{"ret_code":" SUCCESS","ret_msg":"OK"}
7.7.3 签名校验¶
签名校验规则是:
1.返回的参数列表,去掉signautre参数,同时添加 key = “app_secret”,value=App Secret ,然后根据key值进行自然排序,多个参数之间用&隔开,最后进行MD5加密
2.用加密后的字符串和获取到的signature进行比较
签名函数如下:
/**
* result :获取的数据的map集合
* App Secret :就是开发者平台上的App Secret
*/
public static String createSign(Map<String, Object> result, String App Secret) {
if (result ==null || result.size()==0)
return null;
result.put("app_secret", App Secret); //1.添加key = “app_secret”,value=appSecret
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 sign = "";
for (String string : tmp) {
if(m.get(string) == null)
continue;
sign += string + "=" + URLEncoder.encode(m.get(string).toString(),"utf-8") + "&";
}
if (sign.endsWith("&"))
sign = sign.substring(0, sign.length() - 1);
Log.i(TAG, "createSign: "+sign);
String localSign = MD5.MD5(sign); //5.生成MD5加密后的字符串
return localSign.equal(sign); //6.和2中的sign进行校验
}