《订单支付通知》

订单支付流程

支付流程

  1. 游戏客户端调用 AnySDK 框架支付接口(payForProduct),AnySDK 框架请求 AnySDK 服务器生成本次交易订单号
  2. AnySDK 框架获取到服务器生成的支付订单号
  3. AnySDK 框架调用渠道 SDK 支付接口向渠道平台服务器请求支付
  4. 支付成功后,渠道支付 SDK 会返回支付成功通知 AnySDK 框架,框架再执行支付成功回调函数通知游戏客户端
    注意,此步 SDK 返回的成功回调只是指交易已经成功提交给渠道服务器,并不表示此笔订单已经支付成功,有一些渠道支付 SDK 是只要进去支付选择界面,SDK 生成订单就会返回支付成功回调,即使玩家取消支付或者支付失败,因此,订单的支付结果只能以游戏服务器是否接收到渠道服务器的支付订单回调信息为准,而不能以本地支付回调为准
  5. 渠道平台服务器完成订单支付后发送订单验证信息异步通知 AnySDK 服务器
    注:此步需要开发者在渠道后台(即开发者获取 SDK 参数的页面)将支付订单回调地址配置为 AnySDK 提供的各渠道专用的支付回调地址,此地址可以在打包工具参数配置页面查看到
  6. AnySDK 服务器对渠道服务器发送过来的订单信息做校验,并返回正确的响应
  7. AnySDK 服务器将订单支付结果信息推送到到游戏服务器提供的支付订单回调地址。此地址在打包工具配置渠道参数界面配置。
  8. 游戏服务器对 AnySDK 推送过来的信息做校验,只要游戏服务器完成本次通知处理,不论是确认订单有效发放道具或是订单无效丢弃,请务必返回正确的响应 okOK,否则 AnySDK 会重复通知,给游戏服务器带来压力
  9. 游戏服务器验证支付通知并发放道具

通知游戏服务器

编码说明

传输过程一律使用 UTF-8 编码

通知方式

POST

参数说明

参数 参数类型 说明
order_id string 订单号,AnySDK 产生的订单号
product_count string 要购买商品数量(暂不提供具体数量)
下列渠道请不要使用此金额字段作为发放道具的依据,而应该使用 product_id 作为发放道具的依据:
    GooglePlay
    Apple appstore
    心动(非越狱)
amount string 支付金额,单位元 值根据不同渠道的要求可能为浮点类型
pay_status string 支付状态,1 为成功,非1则为其他异常状态,游服请在成功的状态下发货
pay_time string 支付时间,YYYY-mm-dd HH:ii:ss 格式
user_id string 用户 ID,用户系统的用户 ID
order_type string 支付方式 详见 支付渠道标识表
game_user_id string 游戏内用户 ID,支付时传入的 Role_Id 参数
server_id string 服务器 ID,支付时传入的 Server_Id 参数
product_name string 商品名称,支付时传入的 Product_Name 参数
product_id string 商品 ID,支付时传入的 Product_Id 参数
下列渠道请使用此 product_id 作为发放道具的依据,而不要使用金额amount字段作为发放道具的依据:
    GooglePlay
    Apple appstore
    心动(非越狱)
channel_product_id string product_id 字段的值对应的渠道商品 ID,对应关系可以在 dev 后台 -> 游戏列表 -> 管理商品 页面进行配置。
private_data string 自定义参数,调用客户端支付函数时传入的EXT参数,透传给游戏服务器
channel_number string 渠道编号 渠道列表
sign string 通用签名串,通用验签参考签名算法
source string 渠道服务器通知 AnySDK 时请求的参数
enhanced_sign string 增强签名串,验签参考签名算法(有增强密钥的游戏有效)
channel_order_id string 渠道订单号,如果渠道通知过来的参数没有渠道订单号则为空。
game_id string 游戏 ID,AnySDK 服务端为游戏分配的唯一标识
plugin_id string 插件 ID,AnySDK 插件数字唯一标识 详见 支付渠道标识表
currency_type string 货币类型,只有企业版才有这个字段,通用版不会有这个字段

接收支付通知步骤

  1. 接收 AnySDK 支付通知参数
  2. 检查 AnySDK 支付通知服务 IP 白名单
  3. 验证签名及检查金额
  4. 成功响应及发放道具(失败响应及日志记录)

游戏服务器响应

应用在接收到通知消息后,需回应 okOK (不带任何空白字符、隐藏字符、控制字符的 2 个字节长度的纯字符串),表示通知已经接收. 如果回应其他值或者不回应,则被认为通知失败,AnySDK 支付通知服务器会尝试多次通知。
注意:游戏服务器对 AnySDK 推送过来的信息做校验,只要游戏服务器完成本次通知处理,校验结果为数据正常,校验通过,请务必返回正确的响应 okOK(不带任何空白字符、隐藏字符、控制字符的 2 个字节长度的纯字符串),否则 AnySDK 会重复通知,给游戏服务器带来压力。

支付通知验证注意事项

  1. AnySDK 支付通知到游戏服务器中的 amount 为人民币金额(单位:元),目前安卓 SDK 为用户实际支付金额,即渠道平台实际通知金额或渠道币严格比例转后的金额,如有特殊 SDK 支付通知无法获取支付的实际人民币金额,将从 AnySDK 订单中通知给游戏服务器,并在此注意事项中提醒开发者,请注意游戏服务器接收支付通知后金额的验证。
  2. 如游戏服务器有需要对 AnySDK 支付通知服务进行 IP 验证或加入防火墙,可随时关注 AnySDK 支付通知服务 IP 列表;建议游戏服务器支付通知验证时加入 IP 验证。

支付通知服务 IP 列表

211.151.20.126
211.151.20.127
117.121.57.82

签名算法及示例

增强签名算法:
1. 准备验签密钥 增强密钥增强密钥 可以在 开发者后台 游戏列表 界面获取,并进行增强验签(为安全起见强烈推荐实现增强验签,进行增强验签可以不进行通用验签)
2. 对所有不为空的参数按照参数名字母升序排列,sign 与 enhanced_sign 参数不参与签名;
3. 将排序后的参数名对应的参数值字符串方式按顺序拼接在一起(所有参数);
4. 做一次 md5 处理并转换成小写,得到的加密串 1;
5. 在加密串1末尾追加增强密钥,做一次 md5 加密并转换成小写,得到的字符串就是签名 enhanced_sign 的值
通用签名算法:
1. 准备验签密钥 private_key
2. 对所有不为空的参数按照参数名字母升序排列,sign 参数不参与签名;
3. 将排序后的参数名对应的参数值字符串方式按顺序拼接在一起(所有参数);
4. 做一次md5处理并转换成小写,得到的加密串 1;
5. 在加密串 1 末尾追加 private_key,做一次 md5 加密并转换成小写,得到的字符串就是签名 sign 的值
注意:只有值参与签名,参数名不参与签名

例如收到的数据为: a=test&c=hello&b=2 则拼接后的string=test2hello 【实际串中没有 + 号,+ 只是连接符】

sign=md5(md5(string)+private_key)

php验签示例:

/**
 * 支付通知验签demo
 */
$data = $_POST;
/**
 * 注意:$_POST数据如果服务器没有自动处理urldecode,请做一次urldecode(参考rfc1738标准)处理
 */
/**
  foreach ($data as $key => $value) {
  $data[$key] = urldecode($value);
  }
 * */
$privateKey = "481946CEC51BEDE79ED72391F42B4CAF";
$enhancedKey = 'OGM3ODFkNDRhYjUzYjM4ZmUzZjk';
//注意:如果没有增强密钥的游戏只需要通用验签即可,即只需要checkSign
//if (checkSign($data, $privateKey)) {
if (checkSign($data, $privateKey) && checkEnhancedSign($data, $enhancedKey)) {
        // @todo 验证成功,游戏服务器处理逻辑
        echo "ok";
} else {
        //@todo
        echo "failed";
}

/**
 * 通用验签
 * @param array $data 接收到的所有请求参数数组,通过$_POST可以获得。注意data数据如果服务器没有自动解析,请做一次urldecode(参考rfc1738标准)处理
 * @param array $privateKey AnySDK分配的游戏privateKey
 * @return bool
 */
function checkSign($data, $privateKey) {
        if (empty($data) || !isset($data['sign']) || empty($privateKey)) {
                return false;
        }
        $sign = $data['sign'];
        //sign 不参与签名
        unset($data['sign']);
        $_sign = getSign($data, $privateKey);
        if ($_sign != $sign) {
                return false;
        }
        return true;
}

/**
 * 增强验签
 * @param type $data
 * @param type $enhancedKey
 * @return boolean
 */
function checkEnhancedSign($data, $enhancedKey) {
        if (empty($data) || !isset($data['enhanced_sign']) || empty($enhancedKey)) {
                return false;
        }
        $enhancedSign = $data['enhanced_sign'];
        //sign及enhanced_sign 不参与签名
        unset($data['sign'], $data['enhanced_sign']);
        $_enhancedSign = getSign($data, $enhancedKey);
        if ($_enhancedSign != $enhancedSign) {
                return false;
        }
        return true;
}

/**
 * 计算签名
 * @param array $data
 * @param string $key
 * @return string
 */
function getSign($data, $key) {
        //数组按key升序排序
        ksort($data);
        //将数组中的值不加任何分隔符合并成字符串
        $string = implode('', $data);
        //做一次md5并转换成小写,末尾追加游戏的privateKey,最后再次做md5并转换成小写
        return strtolower(md5(strtolower(md5($string)) . $key));
}

更多验签 Demo

更多服务端 Demo 托管在 GitHub,请前往自取。包括 PHP、Java、Python、NodeJS 等语言。
Sample_Server

模拟支付通知

开发者管理后台提供了模拟支付通知,开发者可以填写需要通知的地址和通知的数据,AnySDK 会输出生成签名每个步骤的数据,开发者可以自己比对每一步的差异,及时调整验签算法:
模拟支付通知
点击执行,会在页面上输出签名过程:

原始参数(即不包括sign及enhanced_sign参数): 
Array
(
    [order_id] => PB79002016100812025535755
    [channel_order_id] => 15442016100812025541467
    [product_count] => 1
    [amount] => 1.0
    [pay_status] => 1
    [pay_time] => 2016-10-08 12:02:55
    [user_id] => 44169
    [order_type] => 115
    [game_user_id] => 87746
    [game_id] => 1477
    [server_id] => 7
    [product_name] => gold
    [product_id] => 2639
    [channel_product_id] => 6110
    [private_data] => buy100gold
    [channel_number] => 000023
    [source] => {"amount":"100","app_id":"89230","cp_order_id":"","ext1":"100\u5143\u5b9d","ext2":"","trans_id":"4123870","trans_status":"1","user_id":"11332303","sign":""}
)

计算增强签名:

排序之后的参数: Array ( [amount] => 1.0 [channel_number] => 000023 [channel_order_id] => 15442016100812025541467 [channel_product_id] => 6110 [game_id] => 1477 [game_user_id] => 87746 [order_id] => PB79002016100812025535755 [order_type] => 115 [pay_status] => 1 [pay_time] => 2016-10-08 12:02:55 [private_data] => buy100gold [product_count] => 1 [product_id] => 2639 [product_name] => gold [server_id] => 7 [source] => {"amount":"100","app_id":"89230","cp_order_id":"","ext1":"100\u5143\u5b9d","ext2":"","trans_id":"4123870","trans_status":"1","user_id":"11332303","sign":""} [user_id] => 44169 )

拼接的字符串: 1.0000023154420161008120255414676110147787746PB7900201610081202553575511512016-10-08 12:02:55buy100gold12639gold7{"amount":"100","app_id":"89230","cp_order_id":"","ext1":"100\u5143\u5b9d","ext2":"","trans_id":"4123870","trans_status":"1","user_id":"11332303","sign":""}44169

第一次MD5计算的值: 0a246fcf030bbcfab671600627a6561d

加入增强密钥: 0a246fcf030bbcfab671600627a6561dZmVhZGI2MmJlOWRlNzc3ZGViNmY

第二次MD5计算的值(即增强签名): 35660d1400db46715406eec106dec425

计算通用签名:

排序之后的参数: Array ( [amount] => 1.0 [channel_number] => 000023 [channel_order_id] => 15442016100812025541467 [channel_product_id] => 6110 [enhanced_sign] => 35660d1400db46715406eec106dec425 [game_id] => 1477 [game_user_id] => 87746 [order_id] => PB79002016100812025535755 [order_type] => 115 [pay_status] => 1 [pay_time] => 2016-10-08 12:02:55 [private_data] => buy100gold [product_count] => 1 [product_id] => 2639 [product_name] => gold [server_id] => 7 [source] => {"amount":"100","app_id":"89230","cp_order_id":"","ext1":"100\u5143\u5b9d","ext2":"","trans_id":"4123870","trans_status":"1","user_id":"11332303","sign":""} [user_id] => 44169 )

拼接的字符串: 1.000002315442016100812025541467611035660d1400db46715406eec106dec425147787746PB7900201610081202553575511512016-10-08 12:02:55buy100gold12639gold7{"amount":"100","app_id":"89230","cp_order_id":"","ext1":"100\u5143\u5b9d","ext2":"","trans_id":"4123870","trans_status":"1","user_id":"11332303","sign":""}44169

第一次MD5计算的值: e525bb35be6084de3423ef45ed0d5e3e

加入Private Key: e525bb35be6084de3423ef45ed0d5e3e757F4680F81591D3561AC4D1D8D52B2C

第二次MD5计算的值(即通用签名): f9e3430b49b8f08d7e996ba6542d9fa5

目标服务器响应: ok

在线验签

在线核对增强验签

  1. 首先在 chrome 浏览器上安装扩展 POSTMAN 或者 Firefox 安装插件 HttpRequester
  2. 核对增强验签地址:http://inner.anysdk.com/tools/check/enhanced_sign/
  3. Content Type:x-www-form-urlencoded
  4. 选择 POST 方式
  5. 参数 param 值为 AnySDK 开发者管理后台 -> 支付管理 -> 支付通知详情 -> 通知游服详情 -> 通知参数 ,取回调地址之后的参数字符串(即问号之后的参数字符串)
    如:
    order_id=PB500415062414453311028&channel_order_id=&product_count=1&amount=1.00&pay_status=1&pay_time=2015-06-24+14%3A51%3A14&order_type=87&source=%7B%22appid%22%3A%22402991013%22%2C%22channel_id%22%3A%22000023%22%2C%22coco%22%3A%2230766705%22%2C%22product_name%22%3A%22%25E5%2582%25BB%25E7%2593%259C10%22%2C%22total_fee%22%3A%221.00%22%2C%22payment_type%22%3A%22alipay_mobile%22%2C%22status%22%3A%221%22%2C%22create_time%22%3A%220000-00-00+00%3A00%3A00%22%2C%22pay_time%22%3A%222015-06-24+14%3A51%3A14%22%2C%22transaction_id%22%3A%22c62fc4444082bbeb%22%2C%22misc%22%3A%22PB500415062414453311028%22%2C%22sign%22%3A%22199c0c1b64a5e27b9890578148c872a6%22%7D&user_id=30766705&game_user_id=1&server_id=13&product_name=%E5%82%BB%E7%93%9C10&product_id=616&private_data=&channel_number=110003&enhanced_sign=ca4aeaa1c53684777f6214d39a687979&sign=64f237f1c9f8a24d369a3bf0e35a9615
    
  6. 参数 enhanced_key 值为 AnySDK 提供给游戏的增强密钥
  7. 示例:
    chrome 浏览器 POSTMAN 扩展使用:
    示例发送:
    在线验签
    示例结果:
降序排列:
Array
(
    [amount] => 1.00
    [channel_number] => 110003
    [channel_order_id] => 
    [game_user_id] => 1
    [order_id] => PB500415062414453311028
    [order_type] => 87
    [pay_status] => 1
    [pay_time] => 2015-06-24 14:51:14
    [private_data] => 
    [product_count] => 1
    [product_id] => 616
    [product_name] => 傻瓜10
    [server_id] => 13
    [source] => {"appid":"402991013","channel_id":"000023","coco":"30766705","product_name":"%E5%82%BB%E7%93%9C10","total_fee":"1.00","payment_type":"alipay_mobile","status":"1","create_time":"0000-00-00 00:00:00","pay_time":"2015-06-24 14:51:14","transaction_id":"c62fc4444082bbeb","misc":"PB500415062414453311028","sign":"199c0c1b64a5e27b9890578148c872a6"}
    [user_id] => 30766705
)

连接成的字符串:
1.001100031PB5004150624144533110288712015-06-24 14:51:141616傻瓜1013{"appid":"402991013","channel_id":"000023","coco":"30766705","product_name":"%E5%82%BB%E7%93%9C10","total_fee":"1.00","payment_type":"alipay_mobile","status":"1","create_time":"0000-00-00 00:00:00","pay_time":"2015-06-24 14:51:14","transaction_id":"c62fc4444082bbeb","misc":"PB500415062414453311028","sign":"199c0c1b64a5e27b9890578148c872a6"}30766705
第一次MD5值:
788624055f24ce48267f1460623987f9
加入privatekey或增强密钥字符串:
788624055f24ce48267f1460623987f9YThiMWUyMTk5ZTU1OTQ0ZTFhOGU
重新计算的增强签名:
ca4aeaa1c53684777f6214d39a687979
AnySDK提供增强签名:
ca4aeaa1c53684777f6214d39a687979
ok(签名一致)

在线核对通用验签

  1. 首先在 chrome 浏览器上安装扩展 POSTMAN 或者 Firefox 安装插件 HttpRequester
  2. 核对增强验签地址:http://inner.anysdk.com/tools/check/sign
  3. Content Type:x-www-form-urlencoded
  4. 选择 POST 方式
  5. 参数 param 值为 AnySDK 开发者管理后台 -> 支付管理 -> 支付通知详情 -> 通知游服详情 -> 通知参数,取回调地址之后的参数字符串(即问号之后的参数字符串)
    如:
    order_id=PB500415062414453311028&channel_order_id=&product_count=1&amount=1.00&pay_status=1&pay_time=2015-06-24+14%3A51%3A14&order_type=87&source=%7B%22appid%22%3A%22402991013%22%2C%22channel_id%22%3A%22000023%22%2C%22coco%22%3A%2230766705%22%2C%22product_name%22%3A%22%25E5%2582%25BB%25E7%2593%259C10%22%2C%22total_fee%22%3A%221.00%22%2C%22payment_type%22%3A%22alipay_mobile%22%2C%22status%22%3A%221%22%2C%22create_time%22%3A%220000-00-00+00%3A00%3A00%22%2C%22pay_time%22%3A%222015-06-24+14%3A51%3A14%22%2C%22transaction_id%22%3A%22c62fc4444082bbeb%22%2C%22misc%22%3A%22PB500415062414453311028%22%2C%22sign%22%3A%22199c0c1b64a5e27b9890578148c872a6%22%7D&user_id=30766705&game_user_id=1&server_id=13&product_name=%E5%82%BB%E7%93%9C10&product_id=616&private_data=&channel_number=110003&enhanced_sign=ca4aeaa1c53684777f6214d39a687979&sign=64f237f1c9f8a24d369a3bf0e35a9615
    
  6. 参数 private_key 值为 AnySDK 提供给游戏的 private_key

重复通知

开发商服务器应做好接收到多次通知的准备,防止多次加钱。
同时,需要特别注意的是,回应 okOK不带任何空白字符、隐藏字符、控制字符的 2 个字节长度的纯字符串)表示应用已经正常接到消息,无需继续发送通知,它不表示订单成功与否,或者处理成功与否。
对于重复的通知, 可能发现订单已经成功处理完毕,无需继续处理,也要返回 okOK不带任何空白字符、隐藏字符、控制字符的 2 个字节长度的纯字符串),否则,AnySDK 支付通知服务会认为未成功通知,会继续发送通知。

重复通知间隔为:2 分钟,10 分钟,10 分钟,1 小时,2 小时,6 小时,15 小时共 7 次,直到中间有收到接收成功的反馈消息,或 7 次发送完,结束周期性发送。

后台订单状态

成功:AnySDK 服务端与渠道服务端验签成功(跟通知游服结果无关)。
失败:渠道服务端通知过来的订单状态为失败。
充值中:
1. 收到渠道的通知,但是验签错误,一般是由于 SDK 参数配置错误。
2. 并没有收到渠道的通知,可能是 1. 用户打开支付界面后并没有支付直接关掉;2. 渠道后台配置的支付通知地址并不是正确的 AnySDK 的地址;3. 渠道出问题没通知或延迟通知。可先跟渠道那边确认是否有通知,通知到哪个地址。

评论区