余额提现
接入 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++ 发起请求来获得订单状态,该查询结果可以作为交易结果。
下一步余额赠送