余额提现
接入 Ping++ 发起余额提现,仅需要 Ping++ Server SDK 即可。服务器端需要做的就是向 Ping++ 请求 withdrawals 接口,并且监听和获取 Webhooks 通知,具体步骤如下:
- 设置 API-Key
- SDK 验证签名设置
- 服务端发起提现请求获取 withdrawal 对象
- 更新 withdrawal 对象
- 接收 Webhooks 通知
- 验证 Webhooks 签名
第一步:设置 API-Key
Ping++ API 交易时需要设置 API-Key,Server SDK 提供了设置的方法。如果你直接使用 API ,需要在 header 中加入 Authorization,格式是 Authorization: Bearer API-Key。
\Pingpp\Pingpp::setApiKey('sk_test_ibbTe5jLGCi5rzfH4OqPW9KC');
Pingpp.apiKey = "sk_test_ibbTe5jLGCi5rzfH4OqPW9KC";
var pingpp = require('pingpp')('sk_test_ibbTe5jLGCi5rzfH4OqPW9KC');
pingpp.api_key = 'sk_test_ibbTe5jLGCi5rzfH4OqPW9KC'
Pingpp.api_key = "sk_test_ibbTe5jLGCi5rzfH4OqPW9KC"
pingpp.Key = "sk_test_ibbTe5jLGCi5rzfH4OqPW9KC"
Pingpp.Pingpp.SetApiKey("sk_test_ibbTe5jLGCi5rzfH4OqPW9KC");
第二步:SDK 验证签名设置
为了进一步增强交易请求的安全性,Ping++ 交易接口针对所有的 POST 和 PUT 请求已经新增 RSA 加密验签功能。该接口必须使用该签名验证功能,你需要生成密钥,然后将私钥配置到你的代码中,公钥上传至 Ping++ 管理平台并启用验签开关。首先你需要本地生成 RSA 公钥和私钥,生成方法请参考:如何获取 RSA 公钥和私钥?
设置请求签名密钥
你需要在代码中设置请求签名的私钥(rsa_private_key.pem),可以读取配置私钥文件的路径或者直接定义变量。你如果通过 API 接口校验的话,需要生成 RSA 签名(SHA256)并在请求头中添加 Pingplusplus-Signature,如果使用 SDK 的话只需要配置私钥即可。
\Pingpp\Pingpp::setPrivateKeyPath(__DIR__ . '/your_rsa_private_key.pem');
Pingpp.privateKeyPath = "/path/to/your_rsa_private_key.pem";
pingpp.setPrivateKeyPath(__dirname + "/your_rsa_private_key.pem");
pingpp.private_key_path = 'your_rsa_private_key.pem'
Pingpp.private_key_path = File.dirname(__FILE__) + '/your_rsa_private_key.pem'
privateKey, err := ioutil.ReadFile("your_rsa_private_key.pem")
Pingpp.Pingpp.SetPrivateKeyPath(@"../../your_rsa_private_key.pem");
上传公钥至 Ping++ 管理平台
设置完代码中的私钥,你需要将已经生成的公钥(rsa_public_key.pem)填写到 Ping++ 管理平台上。 配置路径: 登录 Ping++ 管理平台->点击右上角公司名称->企业面板->开发参数->商户 RSA 公钥->将你的公钥复制粘贴进去并且保存->先启用 Test 模式进行测试->测试通过后启用 Live 模式

注意: 一旦上传公钥至 Ping++ 管理平台并启用 Live 模式,则验证签名功能即时生效,Ping++ 会立即验证你的真实线上交易验签请求。如果私钥为空或错误,则会交易失败,所以请确保测试模式正常后再启用 Live 开关。
第三步:服务端发起提现请求获取 withdrawal 对象
调用 Ping++ Server SDK 发起余额提现请求,发起请求所需参数具体可参考 API 文档。通过调用该接口发起一笔用户提现申请,提现申请表示用户提现意图,不会真实发起提现行为,需要后续确认。 提现支持的渠道有:银联(unionpay)、支付宝(alipay)、微信 App(wx)、微信JSAPI(wx_pub) 、通联(allinpay) 、京东(jdpay)。
$params = [    'user' => 'user_test_01',    // 用户 ID    'amount' => 200,             // 转账金额    'channel' => 'unionpay',     //提现使用渠道。    'user_fee' => 10,            //用户需要承担的手续费    'description' => 'Your Description',           //描述。    'order_no' => time() . mt_rand(100000, 999999), //提现订单号    'extra' => array(    	    'account' => '6225210207078888', //必填项 收款人银行卡号或者存折号。		'name' => '张三',             //必填项 收款人姓名。		'open_bank_code' => '0308',  //选填项 开户银行编号,open_bank_code 和 open_bank 必须填写一个,优先推荐填写 open_bank_code。		'open_bank' => '招商银行',  //选填项 开户银行,open_bank_code 和 open_bank 必须填写一个,优先推荐填写 open_bank_code。		'prov' => '上海',   //选填项 省份。		'city' => '上海',   //选填项 城市。		'sub_bank' => '徐家汇支行'			)$withdrawal = \Pingpp\Withdrawal::create($params);echo $withdrawal;
Pingpp.apiKey = "sk_test_ibbTe5jLGCi5rzfH4OqPW9KC";Map<String, Object> params = new HashMap<String, Object>();params.put("user", "test_user_001"); // 用户 ID, 必传params.put("channel", "unionpay"); // 提现使用渠道。params.put("amount", 100);  // 转账金额, 必传params.put("description", "custom description");    // 描述, 可选params.put("order_no", "1010" + System.currentTimeMillis()); // 提现订单号, 必传Map<String, String> extra = new HashMap<String, String>();extra.put("name", "NAME");extra.put("account", "6225210207078888");params.put("extra", extra);params.put("user_fee", 5);  // 用户需要承担的手续费, 必传params.put("settle_account", "test_user_001"); // 使用结算账户提现,不需要填写 extra 参数,同时填写时,结算账号不生效Withdrawal withdrawal = Withdrawal.create(params); // 创建 Withdrawal 对象
var order_no = new Date().getTime().toString().substr(0, 10);var channel = 'unionpay';var extra = withdrawal_extra(channel);var params = {  'user': 'test_user_001',  'amount': 20, // 金额  'channel': channel, // 渠道  'user_fee': 10, // 用户需要承担的手续费  'order_no': order_no, // 提现订单号  'description': 'test232description', // 转账描述  'extra': extra};                      
request_info = {    "amount": 20000,    "user_fee": 50,    'order_no': '1234567890',    "description": "Your description",    'channel': 'unionpay',     "extra": {        "account": "622123456789",          "name": "姓名",        "open_bank_code": "0102",        "prov": "上海",        "city": "上海"    },new_wd = pingpp.Withdrawal.create(user="user_id_001", **request_info)print(new_wd)
params = {	:user => existed_user_id,	:amount => 10,	:user_fee => 0,	:description => "用户提现",	:channel => "unionpay",	:extra => {		# 收款账号 		:account => "6201112223333",		# 姓名  conditional|string		:name => "姓名",	},	# 结算账号 ID  conditional|string; 与 user 绑定的结算账号 ID	# :settle_account => "SETTLE_ACCOUNT",	# metadata  optional|hash	:metadata => {		:custom_key => "custom_value",		# ...	}}if params[:channel] == "allinpay"	params[:order_no] = "301002#{Time.now.to_i.to_s}#{rand(999999).to_s.rjust(6, "0")}"else	params[:order_no] = "#{Time.now.to_i.to_s}#{rand(9999).to_s.rjust(4, "0")}"end o = Pingpp::Withdrawal.create(	params,	{		# URL 中的 {app_id}  ; 用于覆盖 Pingpp.app_id		:app => get_app_id	})
func (c *WithdrawalDemo) Setup(app string) {	c.demoAppID = app	c.demoChannel = "unionpay"} // 余额提现申请func (c *WithdrawalDemo) New() (*pingpp.Withdrawal, error) {	params := &pingpp.WithdrawalParams{		User:        "user_001",		Amount:      20000,		User_fee:    0,		Channel:     c.demoChannel,		Description: "Your description",		Order_no:    "20160829133002",		Extra:       common.WithdrawExtra[c.demoChannel],	}	return withdrawal.New(c.demoAppID, params)}
public static Withdrawal unionpayWithdrawal(String appId) {	var createParams = new Dictionary<string, object>     {    	{"order_no", "2016111000039"}, //提现订单号        {"amount" , 1}, //体现金额。        {"user_fee", 0}, //用户需要承担的手续费。        {"channel", "unionpay"}, //提现使用渠道。        {"user", "user_001"},   //用户 ID。        {"description", "Your description"}, //描述        {"extra", new Dictionary<string, object>{        	{"account", "6214850210294648"}, //收款人银行卡号或者存折号。            {"name", "张三"}, //收款人姓名。            {"open_bank_code", "0308"}, //开户银行编号,open_bank_code 和 open_bank 必须填写一个,优先推荐填写 open_bank_code            {"open_bank", "招商银行"}, //开户银行,open_bank_code 和 open_bank 必须填写一个,优先推荐填写 open_bank_code。            {"prov", "上海"}, //省份。            {"city", "上海"}, //城市。            {"sub_bank","徐家汇支行"} // 开户支行名称。         }},         //{"settle_account", "320217081417114300000501"}    //使用结算账户提现,不需要填写 channel 和 extra 相关参数,同时填写时,结算账号不生效      };      return Withdrawal.Request(appId,createParams);}
$params = [    'user' => 'user_test_01',  // 用户 ID    'amount' => 200,           // 转账金额    'channel' => 'alipay',     //提现使用渠道。    'user_fee' => 10,          //用户需要承担的手续费    'description' => 'Your Description',   //描述。    'order_no' => time() . mt_rand(100000, 999999), //提现订单号    'extra' => array(    		    'account' => 'user@gmail.com',    //必填项 支付宝账号。			'name' => '张三'                   //必填项 收款人姓名。 			)$withdrawal = \Pingpp\Withdrawal::create($params);echo $withdrawal;
Pingpp.apiKey = "sk_test_ibbTe5jLGCi5rzfH4OqPW9KC";Map<String, Object> params = new HashMap<String, Object>();params.put("user", "test_user_001"); // 用户 ID, 必传params.put("channel", "alipay"); // 提现使用渠道。params.put("amount", 100);  // 转账金额, 必传params.put("description", "custom description");    // 描述, 可选params.put("order_no", "1010" + System.currentTimeMillis()); // 提现订单号, 必传Map<String, String> extra = new HashMap<String, String>();extra.put("name", "NAME");extra.put("account", "test_user_001@gmail.com");params.put("extra", extra);params.put("user_fee", 5);  // 用户需要承担的手续费, 必传params.put("settle_account", "test_user_001"); // 使用结算账户提现,不需要填写 extra 参数,同时填写时,结算账号不生效Withdrawal withdrawal = Withdrawal.create(params); // 创建 Withdrawal 对象
var order_no = new Date().getTime().toString().substr(0, 10);var channel = 'alipay';var extra = withdrawal_extra(channel);var params = {  'user': 'test_user_001',  'amount': 20, // 金额  'channel': channel, // 渠道  'user_fee': 10, // 用户需要承担的手续费  'order_no': order_no, // 提现订单号  'description': 'test232description', // 转账描述  'extra': extra};                      
request_info = {    "amount": 20000,    "user_fee": 50,    'order_no': '1234567890',    "description": "Your description",    'channel': 'alipay',      "extra": {        "account": "622123456789",          "name": "姓名"    },new_wd = pingpp.Withdrawal.create(user="user_id_001", **request_info)print(new_wd)
params = {	:user => existed_user_id,	:amount => 10,	:user_fee => 0,	:description => "用户提现",	:channel => "alipay",	:extra => {		# 收款账号  		:account => "user@gmail.com",		# 姓名  		:name => "姓名",	},	# 结算账号 ID ; 与 user 绑定的结算账号 ID	# :settle_account => "SETTLE_ACCOUNT",	# metadata  optional|hash	:metadata => {		:custom_key => "custom_value",		# ...	}}if params[:channel] == "allinpay"	params[:order_no] = "301002#{Time.now.to_i.to_s}#{rand(999999).to_s.rjust(6, "0")}"else	params[:order_no] = "#{Time.now.to_i.to_s}#{rand(9999).to_s.rjust(4, "0")}"end o = Pingpp::Withdrawal.create(	params,	{		# URL 中的 {app_id}  ; 用于覆盖 Pingpp.app_id		:app => get_app_id	})
func (c *WithdrawalDemo) Setup(app string) {	c.demoAppID = app	c.demoChannel = "alipay"} // 余额提现申请func (c *WithdrawalDemo) New() (*pingpp.Withdrawal, error) {	params := &pingpp.WithdrawalParams{		User:        "user_001",		Amount:      20000,		User_fee:    0,		Channel:     c.demoChannel,		Description: "Your description",		Order_no:    "20160829133002",		Extra:       common.WithdrawExtra[c.demoChannel],	}	return withdrawal.New(c.demoAppID, params)}
public static Withdrawal unionpayWithdrawal(String appId) {	var createParams = new Dictionary<string, object>     {    	{"order_no", "2016111000039"}, //提现订单号        {"amount" , 1}, //体现金额。        {"user_fee", 0}, //用户需要承担的手续费。        {"channel", "alipay"}, //提现使用渠道。        {"user", "user_001"},   //用户 ID。        {"description", "Your description"}, //描述        {"extra", new Dictionary<string, object>{			{"account", "user@example.com"}, //收款人的支付宝账号。            {"name", "张三"},  //收款人姓名。            {"account_type", "ALIPAY_USERID"} //收款方账户类型         }},         //{"settle_account", "320217081417114300000501"}    //使用结算账户提现,不需要填写 channel 和 extra 相关参数,同时填写时,结算账号不生效      };      return Withdrawal.Request(appId,createParams);}
$params = [    'user' => 'user_test_01', // 用户 ID    'amount' => 200,         // 转账金额    'channel' => 'wx',   //提现使用渠道。    'user_fee' => 10,        //用户需要承担的手续费    'description' => 'Your Description',  //描述。    'order_no' => time() . mt_rand(100000, 999999), //提现订单号    'extra' => array(    	    'openid' => 'open_id',    //必填项 微信开放平台下对应获取的用户 open_id。		'name' => '张三'    			)$withdrawal = \Pingpp\Withdrawal::create($params);echo $withdrawal;
  Pingpp.apiKey = "sk_test_ibbTe5jLGCi5rzfH4OqPW9KC";  Map<String, Object> params = new HashMap<String, Object>();  params.put("user", "test_user_001"); // 用户 ID, 必传  params.put("channel", "wx"); // 提现使用渠道。  params.put("amount", 100);  // 转账金额, 必传  params.put("description", "custom description");    // 描述, 可选  params.put("order_no", "1010" + System.currentTimeMillis()); // 提现订单号, 必传  Map<String, String> extra = new HashMap<String, String>();  extra.put("name", "NAME");  extra.put("openid", "open_id");  params.put("extra", extra);  params.put("user_fee", 5);  // 用户需要承担的手续费, 必传  params.put("settle_account", "test_user_001"); // 使用结算账户提现,不需要填写 extra 参数,同时填写时,结算账号不生效  Withdrawal withdrawal = Withdrawal.create(params); // 创建 Withdrawal 对象
var order_no = new Date().getTime().toString().substr(0, 10);var channel = 'wx';var extra = withdrawal_extra(channel);var params = {  'user': 'test_user_001',  'amount': 20, // 金额  'channel': channel, // 渠道  'user_fee': 10, // 用户需要承担的手续费  'order_no': order_no, // 提现订单号  'description': 'test232description', // 转账描述  'extra': extra};                      
request_info = {    "amount": 20000,    "user_fee": 50,    'order_no': '1234567890',    "description": "Your description",    'channel': 'wx',      "extra": {        "openid": "open_id",          "name": "姓名"    },new_wd = pingpp.Withdrawal.create(user="user_id_001", **request_info)print(new_wd)
params = {	:user => existed_user_id,	:amount => 10,	:user_fee => 0,	:description => "用户提现",	:channel => "wx",	:extra => {		# 微信开放平台下对应获取的用户 open_id  		:openid => "open_id",		# 姓名  		:name => "姓名"	},	# 结算账号 ID ; 与 user 绑定的结算账号 ID	# :settle_account => "SETTLE_ACCOUNT",	# metadata  optional|hash	:metadata => {		:custom_key => "custom_value",		# ...	}}if params[:channel] == "allinpay"	params[:order_no] = "301002#{Time.now.to_i.to_s}#{rand(999999).to_s.rjust(6, "0")}"else	params[:order_no] = "#{Time.now.to_i.to_s}#{rand(9999).to_s.rjust(4, "0")}"end o = Pingpp::Withdrawal.create(	params,	{		# URL 中的 {app_id}  ; 用于覆盖 Pingpp.app_id		:app => get_app_id	})
func (c *WithdrawalDemo) Setup(app string) {	c.demoAppID = app	c.demoChannel = "wx"} // 余额提现申请func (c *WithdrawalDemo) New() (*pingpp.Withdrawal, error) {	params := &pingpp.WithdrawalParams{		User:        "user_001",		Amount:      20000,		User_fee:    0,		Channel:     c.demoChannel,		Description: "Your description",		Order_no:    "20160829133002",		Extra:       common.WithdrawExtra[c.demoChannel],	}	return withdrawal.New(c.demoAppID, params)}
public static Withdrawal unionpayWithdrawal(String appId) {	var createParams = new Dictionary<string, object>     {    	{"order_no", "2016111000039"}, //提现订单号        {"amount" , 1}, //体现金额。        {"user_fee", 0}, //用户需要承担的手续费。        {"channel", "wx"}, //提现使用渠道。        {"user", "user_001"},   //用户 ID。        {"description", "Your description"}, //描述        {"extra", new Dictionary<string, object>{			{"openid", "open_id"}, //微信开放平台下对应获取的用户 open_id            {"name", "张三"}  //收款人姓名。         }},         //{"settle_account", "320217081417114300000501"}    //使用结算账户提现,不需要填写 channel 和 extra 相关参数,同时填写时,结算账号不生效      };      return Withdrawal.Request(appId,createParams);}
$params = [    'user' => 'user_test_01', // 用户 ID    'amount' => 200,         // 转账金额    'channel' => 'wx_pub',   //提现使用渠道。    'user_fee' => 10,        //用户需要承担的手续费    'description' => 'Your Description',  //描述。    'order_no' => time() . mt_rand(100000, 999999), //提现订单号    'extra' => array(    	    'openid' => 'open_id',    //必填项 微信公众平台下对应获取的用户 open_id。		'name' => '张三'    			)$withdrawal = \Pingpp\Withdrawal::create($params);echo $withdrawal;
  Pingpp.apiKey = "sk_test_ibbTe5jLGCi5rzfH4OqPW9KC";  Map<String, Object> params = new HashMap<String, Object>();  params.put("user", "test_user_001"); // 用户 ID, 必传  params.put("channel", "wx_pub"); // 提现使用渠道。  params.put("amount", 100);  // 转账金额, 必传  params.put("description", "custom description");    // 描述, 可选  params.put("order_no", "1010" + System.currentTimeMillis()); // 提现订单号, 必传  Map<String, String> extra = new HashMap<String, String>();  extra.put("name", "NAME");  extra.put("openid", "open_id");  params.put("extra", extra);  params.put("user_fee", 5);  // 用户需要承担的手续费, 必传  params.put("settle_account", "test_user_001"); // 使用结算账户提现,不需要填写 extra 参数,同时填写时,结算账号不生效  Withdrawal withdrawal = Withdrawal.create(params); // 创建 Withdrawal 对象
var order_no = new Date().getTime().toString().substr(0, 10);var channel = 'wx_pub';var extra = withdrawal_extra(channel);var params = {  'user': 'test_user_001',  'amount': 20, // 金额  'channel': channel, // 渠道  'user_fee': 10, // 用户需要承担的手续费  'order_no': order_no, // 提现订单号  'description': 'test232description', // 转账描述  'extra': extra};                      
request_info = {    "amount": 20000,    "user_fee": 50,    'order_no': '1234567890',    "description": "Your description",    'channel': 'wx_pub',      "extra": {        "openid": "open_id",          "name": "姓名"    },new_wd = pingpp.Withdrawal.create(user="user_id_001", **request_info)print(new_wd)
params = {	:user => existed_user_id,	:amount => 10,	:user_fee => 0,	:description => "用户提现",	:channel => "wx_pub",	:extra => {		# 微信公众平台下对应获取的用户 open_id  		:openid => "open_id",		# 姓名  		:name => "姓名"	},	# 结算账号 ID ; 与 user 绑定的结算账号 ID	# :settle_account => "SETTLE_ACCOUNT",	# metadata  optional|hash	:metadata => {		:custom_key => "custom_value",		# ...	}}if params[:channel] == "allinpay"	params[:order_no] = "301002#{Time.now.to_i.to_s}#{rand(999999).to_s.rjust(6, "0")}"else	params[:order_no] = "#{Time.now.to_i.to_s}#{rand(9999).to_s.rjust(4, "0")}"end o = Pingpp::Withdrawal.create(	params,	{		# URL 中的 {app_id}  ; 用于覆盖 Pingpp.app_id		:app => get_app_id	})
func (c *WithdrawalDemo) Setup(app string) {	c.demoAppID = app	c.demoChannel = "wx_pub"} // 余额提现申请func (c *WithdrawalDemo) New() (*pingpp.Withdrawal, error) {	params := &pingpp.WithdrawalParams{		User:        "user_001",		Amount:      20000,		User_fee:    0,		Channel:     c.demoChannel,		Description: "Your description",		Order_no:    "20160829133002",		Extra:       common.WithdrawExtra[c.demoChannel],	}	return withdrawal.New(c.demoAppID, params)}
public static Withdrawal unionpayWithdrawal(String appId) {	var createParams = new Dictionary<string, object>     {    	{"order_no", "2016111000039"}, //提现订单号        {"amount" , 1}, //体现金额。        {"user_fee", 0}, //用户需要承担的手续费。        {"channel", "wx_pub"}, //提现使用渠道。        {"user", "user_001"},   //用户 ID。        {"description", "Your description"}, //描述        {"extra", new Dictionary<string, object>{			{"openid", "open_id"}, //微信公众平台下对应获取的用户 open_id            {"name", "张三"}  //收款人姓名。         }},         //{"settle_account", "320217081417114300000501"}    //使用结算账户提现,不需要填写 channel 和 extra 相关参数,同时填写时,结算账号不生效      };      return Withdrawal.Request(appId,createParams);}
$params = [    'user' => 'user_test_01', // 用户 ID    'amount' => 200,          // 转账金额    'channel' => 'wx_lite',   //提现使用渠道。    'user_fee' => 10,         //用户需要承担的手续费    'description' => 'Your Description',  //描述。    'order_no' => time() . mt_rand(100000, 999999), //提现订单号    'extra' => array(    	    'openid' => 'open_id', //必填项 微信公众平台下对应获取的用户 open_id。		'name' => '张三'    			)$withdrawal = \Pingpp\Withdrawal::create($params);echo $withdrawal;
Pingpp.apiKey = "sk_test_ibbTe5jLGCi5rzfH4OqPW9KC";Map<String, Object> params = new HashMap<String, Object>();params.put("user", "test_user_001"); // 用户 ID, 必传params.put("channel", "wx_lite"); // 提现使用渠道。params.put("amount", 100);  // 转账金额, 必传params.put("description", "custom description");    // 描述, 可选params.put("order_no", "1010" + System.currentTimeMillis()); // 提现订单号, 必传Map<String, String> extra = new HashMap<String, String>();extra.put("name", "NAME");extra.put("openid", "open_id");params.put("extra", extra);params.put("user_fee", 5);  // 用户需要承担的手续费, 必传params.put("settle_account", "test_user_001"); // 使用结算账户提现,不需要填写 extra 参数,同时填写时,结算账号不生效Withdrawal withdrawal = Withdrawal.create(params); // 创建 Withdrawal 对象
var order_no = new Date().getTime().toString().substr(0, 10);var channel = 'wx_lite';var extra = withdrawal_extra(channel);var params = {  'user': 'test_user_001',  'amount': 20, // 金额  'channel': channel, // 渠道  'user_fee': 10, // 用户需要承担的手续费  'order_no': order_no, // 提现订单号  'description': 'test232description', // 转账描述  'extra': extra};                      
request_info = {    "amount": 20000,    "user_fee": 50,    'order_no': '1234567890',    "description": "Your description",    'channel': 'wx_lite',      "extra": {        "openid": "open_id",          "name": "姓名"    },new_wd = pingpp.Withdrawal.create(user="user_id_001", **request_info)print(new_wd)
params = {	:user => existed_user_id,	:amount => 10,	:user_fee => 0,	:description => "用户提现",	:channel => "wx_lite",	:extra => {		# 微信公众平台下对应获取的用户 open_id  		:openid => "open_id",		# 姓名  		:name => "姓名"	},	# 结算账号 ID ; 与 user 绑定的结算账号 ID	# :settle_account => "SETTLE_ACCOUNT",	# metadata  optional|hash	:metadata => {		:custom_key => "custom_value",		# ...	}}if params[:channel] == "allinpay"	params[:order_no] = "301002#{Time.now.to_i.to_s}#{rand(999999).to_s.rjust(6, "0")}"else	params[:order_no] = "#{Time.now.to_i.to_s}#{rand(9999).to_s.rjust(4, "0")}"end o = Pingpp::Withdrawal.create(	params,	{		# URL 中的 {app_id}  ; 用于覆盖 Pingpp.app_id		:app => get_app_id	})
func (c *WithdrawalDemo) Setup(app string) {	c.demoAppID = app	c.demoChannel = "wx_lite"} // 余额提现申请func (c *WithdrawalDemo) New() (*pingpp.Withdrawal, error) {	params := &pingpp.WithdrawalParams{		User:        "user_001",		Amount:      20000,		User_fee:    0,		Channel:     c.demoChannel,		Description: "Your description",		Order_no:    "20160829133002",		Extra:       common.WithdrawExtra[c.demoChannel],	}	return withdrawal.New(c.demoAppID, params)}
public static Withdrawal unionpayWithdrawal(String appId) {	var createParams = new Dictionary<string, object>     {    	{"order_no", "2016111000039"}, //提现订单号        {"amount" , 1}, //体现金额。        {"user_fee", 0}, //用户需要承担的手续费。        {"channel", "wx_lite"}, //提现使用渠道。        {"user", "user_001"},   //用户 ID。        {"description", "Your description"}, //描述        {"extra", new Dictionary<string, object>{			{"openid", "open_id"}, //微信公众平台下对应获取的用户 open_id            {"name", "张三"}  //收款人姓名。         }},         //{"settle_account", "320217081417114300000501"}    //使用结算账户提现,不需要填写 channel 和 extra 相关参数,同时填写时,结算账号不生效      };      return Withdrawal.Request(appId,createParams);}
$params = [    'user' => 'user_test_01', // 用户 ID    'amount' => 200,           // 转账金额    'channel' => 'allinpay',   //提现使用渠道。    'user_fee' => 10,         //用户需要承担的手续费    'description' => 'Your Description',   //描述。    'order_no' => time() . mt_rand(100000, 999999), //提现订单号    'extra' => array(    		'account' => '6214850288888888',    //必填项 收款人银行卡号或者存折号。		'name' => '张三',                    //必填项 收款人姓名。		'open_bank_code' => '0308',         //必填项 开户银行编号。		'business_code' => '09900',         //选填项 业务代码,根据通联业务人员提供,不填使用通联提供默认值 09900。		'card_type' => 0,                   //选填项 银行卡号类型,0:银行卡;1:存折。   			)$withdrawal = \Pingpp\Withdrawal::create($params);echo $withdrawal;
Pingpp.apiKey = "sk_test_ibbTe5jLGCi5rzfH4OqPW9KC";Map<String, Object> params = new HashMap<String, Object>();params.put("user", "test_user_001"); // 用户 ID, 必传params.put("channel", "allinpay"); // 提现使用渠道。params.put("amount", 100);  // 转账金额, 必传params.put("description", "custom description");    // 描述, 可选params.put("order_no", "1010" + System.currentTimeMillis()); // 提现订单号, 必传Map<String, String> extra = new HashMap<String, String>();extra.put("account", "6214850288888888");extra.put("name", "NAME");extra.put("open_bank_code", "0308"); //开户银行编号。extra.put("business_code", "09900"); //业务代码,根据通联业务人员提供,不填使用通联提供默认值 09900。extra.put("card_type", 0); //银行卡号类型,0:银行卡;1:存折。params.put("extra", extra);params.put("user_fee", 5);  // 用户需要承担的手续费, 必传params.put("settle_account", "test_user_001"); // 使用结算账户提现,不需要填写 extra 参数,同时填写时,结算账号不生效Withdrawal withdrawal = Withdrawal.create(params); // 创建 Withdrawal 对象
var order_no = new Date().getTime().toString().substr(0, 10);var channel = 'allinpay';var extra = withdrawal_extra(channel);var params = {  'user': 'test_user_001',  'amount': 20, // 金额  'channel': channel, // 渠道  'user_fee': 10, // 用户需要承担的手续费  'order_no': order_no, // 提现订单号  'description': 'test232description', // 转账描述  'extra': extra};                      
request_info = {    "amount": 20000,    "user_fee": 50,    'order_no': '1234567890',    "description": "Your description",    'channel': 'allinpay',      "extra": {        "account": "6214850288888888",          "name": "姓名",		"open_bank_code":"0308", #开户银行编号		"business_code": "09900", #业务代码,根据通联业务人员提供,不填使用通联提供默认值 09900。        "card_type": 0 #银行卡号类型,0:银行卡;1:存折。    },new_wd = pingpp.Withdrawal.create(user="user_id_001", **request_info)print(new_wd)
params = {	:user => existed_user_id,	:amount => 10,	:user_fee => 0,	:description => "用户提现",	:channel => "allinpay",	:extra => { 		:account => "6214850288888888", 		:name => "姓名",		:open_bank_code => "0308", #开户银行编号。		:business_code => "09900", #业务代码,根据通联业务人员提供,不填使用通联提供默认值 09900。		:card_type => 0 #银行卡号类型,0:银行卡;1:存折。	},	# 结算账号 ID ; 与 user 绑定的结算账号 ID	# :settle_account => "SETTLE_ACCOUNT",	# metadata  optional|hash	:metadata => {		:custom_key => "custom_value",		# ...	}}if params[:channel] == "allinpay"	params[:order_no] = "301002#{Time.now.to_i.to_s}#{rand(999999).to_s.rjust(6, "0")}"else	params[:order_no] = "#{Time.now.to_i.to_s}#{rand(9999).to_s.rjust(4, "0")}"end o = Pingpp::Withdrawal.create(	params,	{		# URL 中的 {app_id}  ; 用于覆盖 Pingpp.app_id		:app => get_app_id	})
func (c *WithdrawalDemo) Setup(app string) {	c.demoAppID = app	c.demoChannel = "allinpay"} // 余额提现申请func (c *WithdrawalDemo) New() (*pingpp.Withdrawal, error) {	params := &pingpp.WithdrawalParams{		User:        "user_001",		Amount:      20000,		User_fee:    0,		Channel:     c.demoChannel,		Description: "Your description",		Order_no:    "20160829133002",		Extra:       common.WithdrawExtra[c.demoChannel],	}	return withdrawal.New(c.demoAppID, params)}
public static Withdrawal unionpayWithdrawal(String appId) {	var createParams = new Dictionary<string, object>     {    	{"order_no", "2016111000039"}, //提现订单号        {"amount" , 1}, //体现金额。        {"user_fee", 0}, //用户需要承担的手续费。        {"channel", "allinpay"}, //提现使用渠道。        {"user", "user_001"},   //用户 ID。        {"description", "Your description"}, //描述        {"extra", new Dictionary<string, object>{        	{"account", "6214850288888888"}, //收款人银行卡号或者存折号。            {"name", "张三"},  //	收款人姓名。            {"open_bank_code", "0308"}, //开户银行编号。            {"business_code", "09900"}, //业务代码,根据通联业务人员提供,不填使用通联提供默认值 09900。            {"card_type", 0} //银行卡号类型,0:银行卡;1:存折。         }},         //{"settle_account", "320217081417114300000501"}    //使用结算账户提现,不需要填写 channel 和 extra 相关参数,同时填写时,结算账号不生效      };      return Withdrawal.Request(appId,createParams);}
$params = [    'user' => 'user_test_01',  // 用户 ID    'amount' => 200,          // 转账金额    'channel' => 'jdpay',     //提现使用渠道。    'user_fee' => 10,        //用户需要承担的手续费    'description' => 'Your Description',  //描述。    'order_no' => time() . mt_rand(100000, 999999), //提现订单号    'extra' => array(    		'account' => '6214850288888888',    //必填项 收款人银行卡号或者存折号。		'name' => '张三',                    //必填项 收款人姓名。		'open_bank_code' => '0308'        //必填项 开户银行编号。。   			)$withdrawal = \Pingpp\Withdrawal::create($params);echo $withdrawal;
Pingpp.apiKey = "sk_test_ibbTe5jLGCi5rzfH4OqPW9KC";Map<String, Object> params = new HashMap<String, Object>();params.put("user", "test_user_001"); // 用户 ID, 必传params.put("channel", "jdpay"); // 提现使用渠道。params.put("amount", 100);  // 转账金额, 必传params.put("description", "custom description");    // 描述, 可选params.put("order_no", "1010" + System.currentTimeMillis()); // 提现订单号, 必传Map<String, String> extra = new HashMap<String, String>();extra.put("account", "6214850288888888");extra.put("name", "NAME");extra.put("open_bank_code", "0308"); //开户银行编号。params.put("extra", extra);params.put("user_fee", 5);  // 用户需要承担的手续费, 必传params.put("settle_account", "test_user_001"); // 使用结算账户提现,不需要填写 extra 参数,同时填写时,结算账号不生效Withdrawal withdrawal = Withdrawal.create(params); // 创建 Withdrawal 对象
var order_no = new Date().getTime().toString().substr(0, 10);var channel = 'jdpay';var extra = withdrawal_extra(channel);var params = {  'user': 'test_user_001',  'amount': 20, // 金额  'channel': channel, // 渠道  'user_fee': 10, // 用户需要承担的手续费  'order_no': order_no, // 提现订单号  'description': 'test232description', // 转账描述  'extra': extra};                      
request_info = {    "amount": 20000,    "user_fee": 50,    'order_no': '1234567890',    "description": "Your description",    'channel': 'jdpay',      "extra": {        "account": "6214850288888888",          "name": "姓名",		"open_bank_code":"0308" #开户银行编号    },new_wd = pingpp.Withdrawal.create(user="user_id_001", **request_info)print(new_wd)
params = {	:user => existed_user_id,	:amount => 10,	:user_fee => 0,	:description => "用户提现",	:channel => "jdpay",	:extra => { 		:account => "6214850288888888", 		:name => "姓名",		:open_bank_code => "0308" #开户银行编号。	},	# 结算账号 ID ; 与 user 绑定的结算账号 ID	# :settle_account => "SETTLE_ACCOUNT",	# metadata  optional|hash	:metadata => {		:custom_key => "custom_value",		# ...	}}if params[:channel] == "allinpay"	params[:order_no] = "301002#{Time.now.to_i.to_s}#{rand(999999).to_s.rjust(6, "0")}"else	params[:order_no] = "#{Time.now.to_i.to_s}#{rand(9999).to_s.rjust(4, "0")}"end o = Pingpp::Withdrawal.create(	params,	{		# URL 中的 {app_id}  ; 用于覆盖 Pingpp.app_id		:app => get_app_id	})
func (c *WithdrawalDemo) Setup(app string) {	c.demoAppID = app	c.demoChannel = "jdpay"} // 余额提现申请func (c *WithdrawalDemo) New() (*pingpp.Withdrawal, error) {	params := &pingpp.WithdrawalParams{		User:        "user_001",		Amount:      20000,		User_fee:    0,		Channel:     c.demoChannel,		Description: "Your description",		Order_no:    "20160829133002",		Extra:       common.WithdrawExtra[c.demoChannel],	}	return withdrawal.New(c.demoAppID, params)}
public static Withdrawal unionpayWithdrawal(String appId) {	var createParams = new Dictionary<string, object>     {    	{"order_no", "2016111000039"}, //提现订单号        {"amount" , 1}, //体现金额。        {"user_fee", 0}, //用户需要承担的手续费。        {"channel", "jdpay"}, //提现使用渠道。        {"user", "user_001"},   //用户 ID。        {"description", "Your description"}, //描述        {"extra", new Dictionary<string, object>{            {"account", "6214850288888888"}, //收款人银行卡号或者存折号。            {"name", "张三"},  //	收款人姓名。            {"open_bank_code", "0308"} //开户银行编号。         }},         //{"settle_account", "320217081417114300000501"}    //使用结算账户提现,不需要填写 channel 和 extra 相关参数,同时填写时,结算账号不生效      };      return Withdrawal.Request(appId,createParams);}
Ping++ 收到提现请求后返回给你的服务器一个 withdrwal 对象,下面是 withdrwal 的一个示例:
{  "id": "1701611150302360654",  "object": "withdrawal",  "app": "app_1Gqj58ynP0mHeX1q",  "amount": 20000,  "asset_transaction": "",  "balance_transaction": "",  "channel": "unionpay",  "created": 1472648887,  "description": "申请提现",  "extra": {    "card_number": "6225210207073918",    "user_name": "姓名",    "open_bank_code": "0102",    "prov": "上海",    "city": "上海"  },  "failure_msg": null,  "fee": 200,  "livemode": true,  "metadata":{},  "operation_url": null,  "order_no": "20160829133002",  "source": null,  "status": "created",  "time_canceled": null,  "time_succeeded": null,  "user": "user_001",  "user_fee": 50,  "settle_account": null}
{  "id": "1701611150302360654",  "object": "withdrawal",  "app": "app_1Gqj58ynP0mHeX1q",  "amount": 20000,  "asset_transaction": "",  "balance_transaction": "",  "channel": "alipay",  "created": 1472648887,  "description": "申请提现",  "extra": {    "account": "user@example.com",    "user_name": "姓名",    "account_type": "ALIPAY_LOGONID"  },  "failure_msg": null,  "fee": 200,  "livemode": true,  "metadata":{},  "operation_url": null,  "order_no": "20160829133002",  "source": null,  "status": "created",  "time_canceled": null,  "time_succeeded": null,  "user": "user_001",  "user_fee": 50,  "settle_account": null}
{  "id": "1701611150302360654",  "object": "withdrawal",  "app": "app_1Gqj58ynP0mHeX1q",  "amount": 20000,  "asset_transaction": "",  "balance_transaction": "",  "channel": "wx",  "created": 1472648887,  "description": "申请提现",  "extra": {    "open_id": "wxopenid",    "name": "姓名",    "force_check": false   },  "failure_msg": null,  "fee": 200,  "livemode": true,  "metadata":{},  "operation_url": null,  "order_no": "20160829133002",  "source": null,  "status": "created",  "time_canceled": null,  "time_succeeded": null,  "user": "user_001",  "user_fee": 50,  "settle_account": null}
{  "id": "1701611150302360654",  "object": "withdrawal",  "app": "app_1Gqj58ynP0mHeX1q",  "amount": 20000,  "asset_transaction": "",  "balance_transaction": "",  "channel": "wx_pub",  "created": 1472648887,  "description": "申请提现",  "extra": {    "open_id": "wxopenid",    "name": "姓名",    "force_check": false   },  "failure_msg": null,  "fee": 200,  "livemode": true,  "metadata":{},  "operation_url": null,  "order_no": "20160829133002",  "source": null,  "status": "created",  "time_canceled": null,  "time_succeeded": null,  "user": "user_001",  "user_fee": 50,  "settle_account": null}
{  "id": "1701611150302360654",  "object": "withdrawal",  "app": "app_1Gqj58ynP0mHeX1q",  "amount": 20000,  "asset_transaction": "",  "balance_transaction": "",  "channel": "wx_lite",  "created": 1472648887,  "description": "申请提现",  "extra": {    "open_id": "wxopenid",    "name": "姓名",    "force_check": false   },  "failure_msg": null,  "fee": 200,  "livemode": true,  "metadata":{},  "operation_url": null,  "order_no": "20160829133002",  "source": null,  "status": "created",  "time_canceled": null,  "time_succeeded": null,  "user": "user_001",  "user_fee": 50,  "settle_account": null}
{  "id": "1701611150302360654",  "object": "withdrawal",  "app": "app_1Gqj58ynP0mHeX1q",  "amount": 20000,  "asset_transaction": "",  "balance_transaction": "",  "channel": "allinpay",  "created": 1472648887,  "description": "申请提现",  "extra": {    "account": "6214850288888888",    "name": "姓名",    "open_bank_code": "0308" ,	"business_code":"09900",	"card_type":0  },  "failure_msg": null,  "fee": 200,  "livemode": true,  "metadata":{},  "operation_url": null,  "order_no": "20160829133002",  "source": null,  "status": "created",  "time_canceled": null,  "time_succeeded": null,  "user": "user_001",  "user_fee": 50,  "settle_account": null}
{  "id": "1701611150302360654",  "object": "withdrawal",  "app": "app_1Gqj58ynP0mHeX1q",  "amount": 20000,  "asset_transaction": "",  "balance_transaction": "",  "channel": "allinpay",  "created": 1472648887,  "description": "申请提现",  "extra": {    "account": "6214850288888888",    "name": "姓名",    "open_bank_code": "0308"   },  "failure_msg": null,  "fee": 200,  "livemode": true,  "metadata":{},  "operation_url": null,  "order_no": "20160829133002",  "source": null,  "status": "created",  "time_canceled": null,  "time_succeeded": null,  "user": "user_001",  "user_fee": 50,  "settle_account": null}
第四步:更新 withdrawal 对象
调用 Ping++ Server SDK 发起余额提现请求状态更新,发起请求所需参数具体可参考 API 文档。确认提现,则更新状态时 status 传值为 pending ;取消提现申请,则更新状态时 status 传值为 canceled 。若提现成功,则 withdrawal 对象的 status 值变为 succeeded。若提现因用户原因失败(如提现账户有误),则 withdrawal 对象的 status 值变为 failed,若提现因非用户原因失败(如商户付款金额不足),则 withdrawal 对象的 status 值变为 created。
发起提现确认:
\Pingpp\Withdrawal::confirm($withdrawalId);
Map<String, Object> params = new HashMap<String, Object>();params.put("status", "pending");Withdrawal withdrawal = Withdrawal.update(id, params);
pingpp.withdrawals.confirm(  APP_ID, // App ID  '1701709011052380697', // 提现 ID  function(err, data) {    if (err != null){      console.log('pingpp.withdrawals.confirm fail:', err);    }    // YOUR CODE  });       
pingpp.Withdrawal.confirm("withDrawal_0002", app=app_id, user="user_id_001")
Pingpp::Withdrawal.confirm(	existed_withdrawal_id,    { :app => get_app_id } # App 信息)
withdrawal.Confirm(c.demoAppID, "1701611150302360654")
Withdrawal.Confirm(appId, wd.Id)
发起提现取消:
\Pingpp\Withdrawal::cancel($withdrawalId);
Map<String, Object> params = new HashMap<String, Object>();params.put("status", "canceled");Withdrawal withdrawal = Withdrawal.update(id, params);
pingpp.withdrawals.cancel(  APP_ID, // App ID  '1701709011053327225', // 提现 ID  function(err, data) {    if (err != null){      console.log('pingpp.withdrawals.cancel fail:', err);    }    // YOUR CODE  });       
pingpp.Withdrawal.cancel("withDrawal_0002", app=app_id, user="user_id_001")
o = Pingpp::Withdrawal.cancel(	existed_withdrawal_id,	{ :app => get_app_id } # App 信息    )
withdrawal.Cancel(c.demoAppID, "1701611150302360654")
Withdrawal.Cancel(appId, wd.Id)
Ping++ 收到提现状态更新请求后返回给你的服务器一个 withdrwal 对象,下面是 withdrwal 的一个示例:
{  "id": "1701611150302360654",  "object": "withdrawal",  "app": "app_1Gqj58ynP0mHeX1q",  "amount": 20000,  "asset_transaction": "",  "balance_transaction": "",  "channel": "unionpay",  "created": 1472648887,  "description": "提现确认",  "extra": {    "card_number": "6225210207073918",    "user_name": "姓名",    "open_bank_code": "0102",    "prov": "上海",    "city": "上海"  },  "failure_msg": null,  "fee": 200,  "livemode": true,  "metadata":{},  "operation_url": null,  "order_no": "20160829133002",  "source": "tr_testCGyHqD",  "status": "succeeded",  "time_canceled": null,  "time_succeeded": null,  "user": "user_001",  "user_fee": 50,  "settle_account": null} 
第五步:接收 Webhooks 通知
当提现完成后 Ping++ 会给你配置在 Ping++ 管理平台的 Webhooks 通知地址主动发送结果,我们称之为 Webhooks 通知。 Webhooks 通知是以 POST 形式发送的 JSON,放在请求的 body 里,内容是 Event 对象,提现成功的事件类型为 balance.withdrawal.succeeded ,提现失败的事件类型通知为balance.withdrawal.failed 。你需要监听并接收 Webhooks 通知,接收到 Webhooks 后需要返回服务器状态码 2xx 表示接收成功,否则请返回状态码 500。
$event = json_decode(file_get_contents("php://input"));// 对异步通知做处理if (!isset($event->type)) {    header($_SERVER['SERVER_PROTOCOL'] . ' 400 Bad Request');    exit("fail");}switch ($event->type) {    case "balance.withdrawal.succeeded":        // 开发者在此处加入对异步通知的处理代码        header($_SERVER['SERVER_PROTOCOL'] . ' 200 OK');        break;    case "balance.withdrawal.failed":        // 开发者在此处加入对异步通知的处理代码        header($_SERVER['SERVER_PROTOCOL'] . ' 200 OK');        break;    default:        header($_SERVER['SERVER_PROTOCOL'] . ' 400 Bad Request');        break;}
import com.pingplusplus.model.Event;import com.pingplusplus.model.Webhooks;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.BufferedReader;import java.io.IOException;public class ServletDemo extends HttpServlet {    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        request.setCharacterEncoding("UTF8");        //获取头部所有信息        Enumeration headerNames = request.getHeaderNames();        while (headerNames.hasMoreElements()) {            String key = (String) headerNames.nextElement();            String value = request.getHeader(key);            System.out.println(key+" "+value);        }        // 获得 http body 内容        BufferedReader reader = request.getReader();        StringBuffer buffer = new StringBuffer();        String string;        while ((string = reader.readLine()) != null) {            buffer.append(string);        }        reader.close();        // 解析异步通知数据        Event event = Webhooks.eventParse(buffer.toString());        if ("balance.withdrawal.succeeded".equals(event.getType())) {            response.setStatus(200);        } else if ("balance.withdrawal.failed".equals(event.getType())) {            response.setStatus(200);        } else {            response.setStatus(500);        }    }}
var http = require('http');http.createServer(function (req, res) {  req.setEncoding('utf8');  var postData = "";  req.addListener("data", function (chunk) {    postData += chunk;  });  req.addListener("end", function () {    var resp = function (ret, status_code) {      res.writeHead(status_code, {        "Content-Type": "text/plain; charset=utf-8"      });      res.end(ret);    }    try {      var event = JSON.parse(postData);      if (event.type === undefined) {        return resp('Event 对象中缺少 type 字段', 400);      }      switch (event.type) {        case "balance.withdrawal.succeeded":          // 开发者在此处加入对异步通知的处理代码          return resp("OK", 200);          break;        case "balance.withdrawal.failed":          // 开发者在此处加入对异步通知的处理代码          return resp("OK", 200);          break;        default:          return resp("未知 Event 类型", 400);          break;      }    } catch (err) {      return resp('JSON 解析失败', 400);    }  });}).listen(8080, "0.0.0.0");
import jsonfrom flask import Flask, request, Response# 使用 flask@app.route('/webhooks', methods=['POST'])def webhooks():    event = request.get_json()    if event['type'] == 'balance.withdrawal.succeeded':        return Response(status=200)    elif event['type'] == 'balance.withdrawal.failed':        return Response(status=200)    return Response(status=500) if __name__ == '__main__':    app.run(debug=False, host='0.0.0.0', port=8080)
require 'webrick'require 'json'class Webhooks < WEBrick::HTTPServlet::AbstractServlet  def do_POST(request, response)    status = 400    response_body = '' # 可自定义    begin      event = JSON.parse(request.body)      if event['type'].nil?        response_body = 'Event 对象中缺少 type 字段'      elsif event['type'] == 'balance.withdrawal.succeeded'        # 开发者在此处加入对异步通知的处理代码        status = 200        response_body = 'OK'      elsif event['type'] == 'balance.withdrawal.failed'        # 开发者在此处加入对异步通知的处理代码        status = 200        response_body = 'OK'      else        response_body = '未知 Event 类型'      end    rescue JSON::ParserError      response_body = 'JSON 解析失败'    end    response.status = status    response['Content-Type'] = 'text/plain; charset=utf-8'    response.body = response_body  endendserver = WEBrick::HTTPServer.new(:Port => 8000)server.mount '/webhooks', Webhookstrap 'INT' do server.shutdown endserver.start
func webhook(w http.ResponseWriter, r *http.Request) {  if strings.ToUpper(r.Method) == "POST" {    buf := new(bytes.Buffer)    buf.ReadFrom(r.Body)    signature := r.Header.Get("x-pingplusplus-signature")    webhook, err := pingpp.ParseWebhooks(buf.Bytes())    fmt.Println(webhook.Type)    if err != nil {      w.WriteHeader(http.StatusInternalServerError)      fmt.Fprintf(w, "fail")      return    }    if webhook.Type == "balance.withdrawal.succeeded" {      // TODO your code for charge      w.WriteHeader(http.StatusOK)    } else if webhook.Type == "balance.withdrawal.failed" {      // TODO your code for refund      w.WriteHeader(http.StatusOK)    } else {      w.WriteHeader(http.StatusInternalServerError)    }  } }
using System;using System.Collections.Generic;using System.Linq;using System.Text;using Pingpp.Models;using System.IO; namespace Example.Example{    public class WebhooksDemo    {        public static Event Example()        {            var data = ReadFileToString(@"../../data.txt");            var evt = Webhooks.ParseWebhook(data);            Console.WriteLine(evt);             return evt;        }         public static string ReadFileToString(string path)        {            using (var sr = new StreamReader(path))            {                return sr.ReadToEnd();            }        }     }}
以下是 Webhooks 通知地址配置的 balance.withdrawal.succeeded 对象的一个示例:
{    "created": 1505992666,    "livemode": true,    "type": "balance.withdrawal.succeeded",    "data": {        "object": {            "id": "1711111111222210555",            "object": "withdrawal",            "app": "app_xxxxxxxxxxxx",            "amount": 100,            "asset_transaction": null,            "balance_transaction": "601170921711xxxxxxx",            "channel": "wx_pub",            "created": 1505992661,            "description": "用户提现",            "extra": {                "open_id": "openid"            },            "failure_msg": null,            "fee": 0,            "livemode": true,            "metadata": {},            "operation_url": null,            "order_no": "12345678",            "source": "tr_Puxxxxxxxxxx",            "status": "succeeded",            "time_canceled": null,            "time_succeeded": 1505992666,            "user": "user_001",            "user_fee": 0,            "settle_account": null        }    },    "object": "event",    "request": "iar_xxxxxxxxxx",    "scope": "app_xxxxxxxxxx",    "acct_id": "acct_xxxxxxxxxx"}
以下是 Webhooks 通知地址配置的 balance.withdrawal.failed 对象的示例:
{    "created": 1505992666,    "livemode": true,    "type": "balance.withdrawal.failed",    "data": {        "object": {            "id": "1711111111222210555",            "object": "withdrawal",            "app": "app_xxxxxxxxxxxx",            "amount": 100,            "asset_transaction": null,            "balance_transaction": null,            "channel": "wx_pub",            "created": 1505992661,            "description": "用户提现",            "extra": {                "open_id": "openid"            },            "failure_msg": null,            "fee": 0,            "livemode": true,            "metadata": {},            "operation_url": null,            "order_no": "12345678",            "source": "tr_Puxxxxxxxxxx",            "status": "failed",            "time_canceled": null,            "time_succeeded": 1505992666,            "user": "user_001",            "user_fee": 0,            "settle_account": null        }    },    "object": "event",    "request": "iar_xxxxxxxxxx",    "scope": "app_xxxxxxxxxx",    "acct_id": "acct_xxxxxxxxxx"}
第六步:验证 Webhooks 签名
签名简介
Ping++ 的 Webhooks 通知包含了签名字段,可以使用该签名验证 Webhooks 通知的合法性。签名放置在 header 的自定义字段 x-pingplusplus-signature 中,签名用 RSA 私钥对 Webhooks 通知使用 RSA-SHA256 算法进行签名,以 base64 格式输出。
验证签名
Ping++ 在管理平台中提供了 RSA 公钥,供验证签名,该公钥具体获取路径:点击管理平台右上角公司名称->开发信息-> Ping++ 公钥。验证签名需要以下几步:
- 从 header 取出签名字段并对其进行 base64解码。
- 获取 Webhooks 请求的原始数据。
- 将获取到的 Webhooks 通知、 Ping++ 管理平台提供的 RSA公钥、和base64解码后的签名三者一同放入RSA的签名函数中进行非对称的签名运算,来判断签名是否验证通过。 Ping++ 提供了验证签名的 Demo Demo Demo Demo Demo Demo Demo ,放在 SDK 的 example 里供参考,我们在此不再赘述。
提现查询
Ping++ 管理平台提供详细的提现信息和 Webhooks 功能,所以查询功能相对来说并不是那么必要。如果商户本身由于某种原因导致 Webhooks 没有收到或者延缓更新时,可以主动调用提现查询接口来获得交易的状态。
查询提现 Withdrawal 对象
\Pingpp\Withdrawal::retrieve($withdrawalId);
Withdrawal.retrieve(withdrawalId);
pingpp.withdrawals.retrieve(  APP_ID, // App ID  '1701709011052380697', // 提现 ID  function(err, data) {    if (err != null){      console.log('pingpp.withdrawals.retrieve fail:', err);    }    // YOUR CODE  });
pingpp.Withdrawal.retrieve("withDrawal_0002", app=app_id, user="user_id_001")
o = Pingpp::Withdrawal.retrieve(	existed_withdrawal_id,    { :app => get_app_id } # App 信息   )
withdrawal.Get(c.demoAppID, "1701611150302360654")
Withdrawal.Retrieve(appId, withDrawal.Id);
查询提现 Withdrawal 对象列表
\Pingpp\Withdrawal::all(['per_page' => 3]);
Map<String, Object> params = new HashMap<String, Object>();params.put("page", 1);params.put("per_page", 3);WithdrawalCollection withdrawals = Withdrawal.list(params);
pingpp.withdrawals.list(  APP_ID, // App ID  { per_page: 3 },  function(err, data) {    if (err != null){      console.log('pingpp.withdrawals.retrieve fail:', err);    }    // YOUR CODE  });
pingpp.Withdrawal.list(user="user_id_001")
o = Pingpp::Withdrawal.list(	{ :per_page => 3, :page => 1 },    { :app => get_app_id } # App 信息   )
params := &pingpp.PagingParams{}params.Filters.AddFilter("page", "", "1")params.Filters.AddFilter("per_page", "", "2")return withdrawal.List(c.demoAppID, params)
Withdrawal.List(appId, listParams);
注意事项
- 你需要在 Ping++ 的管理平台里填写 Webhooks 通知地址,详见 Webhooks 配置说明,你的服务器需要监听这个地址并且接收 Webhooks 通知,接收到 Webhooks 通知后需给 Ping++ 返回服务器状态  2xx。此时事件类型是balance.withdrawal.succeeded,balance.withdrawal.failed,其字段data包含了object字段,object字段的值是一个 withdrawal 对象。
- 若你的服务器未正确返回 2xx,Ping++ 会在 25 小时内向商户服务器最多发送 10 次 Webhooks 通知,时间间隔分别为 5s、10s、2min、5min、10min、30min、1h、2h、6h、15h,直到用户向 Ping++ 返回服务器状态2xx或者超过最大重发次数为止。
- 在可接受的时间范围内,如果你服务端没有收到 Webhooks 的通知,你也可以调用 Server-SDK 封装的查询方法,主动向 Ping++ 发起请求来获得订单状态,该查询结果可以作为交易结果。
下一步余额赠送