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包名称以实际为准:

_images/7.1.png

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所示的层级目录

_images/7.2.1.png

图 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类,因为支付方式分为两类,所以必传的参数有所不同,以下为创建方式和必传参数:

  1. P币支付(普通支付方式)—添加所需P币数额
//订单信息--以下为必传参数
PayOrder order = new PayOrder();               //创建订单对象
order.setBusinessOrder(订单号);                //商户自己生成的订单号,32个字符内、可包含字母和数字
order.setTotalFree(money);                     //花费P币数额
order.setSubject("订单标题");                  //订单标题
order.setBody("商品描述");                     //商品描述
order.setNotifyUrl("http:www.picovr.com");   //回调地址—游戏服务器通知地址(非必填)见开发者服务端交互
  1. 商品码支付——————-添加所需商品码
//订单信息--以下为必传参数
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.4 其他设置

7.4.1 关于Debug

开启Debug,可以查看部分数据信息和返回的Log,开启方式:

LogUtils.enableLog();//需要在授权初始化之前调用

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 string 错误代码
ret_msg string 错误信息字符串
详情查看:7.7.2 返回结果
返回参数实例 { “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进行校验
 }