时光

时光的时光轴

寄以时间,予以文字
telegram
github
微信公众号

为独角数卡接入:币安支付

除了平时常用的蓝绿修改器,偶尔也会有因为非中国大陆用户或本来就拥有虚拟币的一些用户需要使用虚拟币支付。

在此之前,我使用TokenPay项目来完成虚拟币的收款回调。除了像 Coinbase 这样的集大成做中间商的办法,现在大多数虚拟币收款都类似于以前的V 免签,监听收款金额来判断完成回调。

这种监听回调的方法对于虚拟币来说非常合适:

  • 所有交易都是透明可查的
  • 没有第三方能够隐藏这些信息
  • 大部分网络都提供了 API 做钱包记录查询
  • 直接到账钱包

但是像我这种手头紧又刚接触虚拟币的小白,前期连钱包这个概念都搞了很久才搞清白,更别说一些什么:矿工费、能量、带宽、共识、POW....

我更直观的感觉就是:我的每一笔交易都有损失,虽然是固定损失,但是每一笔都有。 😥


不过也许你很快就发现,交易所内部交易是免手续费的,两个交易所里出来的地址互相交易也是免费的,那为什么不用交易所的地址来收款呢?

答案是:区块链的每一笔交易都需要有人去打包,验证,给他们的酬劳就叫矿工费,但是交易所不用收,不是交易所可以不给,而是这些资产实际上都存在于交易所的账户上,转账的时候在交易所的数据库里增减一下数字就行。

但是这样,这笔交易就不上链了,意味着别人无法通过区块浏览器看到你这笔交易,也就无法监听是否有人给你付钱了。

不过好在币安搞这个虚拟币生态够意思,提供了币安支付给商家,这样不上链的交易也有办法回调了。以下内容不是广告,纯粹挨夸。

6. 使用币安支付的手续费是多少?
完全免费。

结算方式:基于合约中约定的结算方式。我们目前仅支持实时结算。
3. 不支持的国家和地区(截至 2023 年 1 月)
American Samoa, Australia, Canada (Ontario ONLY), Cuba, Guam, Indonesia, Iran, North Korea, New Zealand, Northern Mariana Islands, Philippines, Puerto Rico, Singapore, Syria, Thailand, Ukraine (Crimea Region incl. Donetsk, Luhansk, Zaporizhzhia and Kherson as well as the Crimea Peninsula), United States Minor Outlying Islands, USA, Vietnam, Virgin Islands (U.S.).
笔者注:没有 China

image

支付流程#

官方提供了详尽的开发文档,不过可惜的是只有英文版本,所以接下来我会多写一些遇到的细节。

image
整体流程和其他支付商户接入没什么大区别,那我们开始吧。

API 要求#

以下实现均使用 PHP 完成。

官方对于调用 API 有一些基本的要求:

  • 必须 HTTPS
  • 提交和响应都是JSON格式
  • UTF-8 编码
  • 加密算法是 HMAC-SHA512

API 地址#

https://bpay.binanceapi.com

签名生成#

大概组成是:毫秒级的时间戳 + \n + 32位随机字符串 + \n + 请求主体 + \n

然后将他们与 API 密钥座 HAMC-SHA512 运算。

开始接入#

第一步,创建订单#

POST /binancepay/openapi/v2/order

官方提供的样本是这样:

{
  "env" : {
    "terminalType": "APP"
  },
  "orderTags": {
    "ifProfitSharing": true
  },
  "merchantTradeNo": "9825382937292",
  "orderAmount": 25.17,
  "currency": "BUSD",
  "goods" : {
    "goodsType": "01",
    "goodsCategory": "D000",
    "referenceGoodsId": "7876763A3B",
    "goodsName": "Ice Cream",
    "goodsDetail": "Greentea ice cream cone"
  }
}

env节点里,需要对用户的访问方法做出指定,比如APPWEBWAP还有一个MINI_PROGRAM

这个小程序应该是指币安 APP 里面的小程序,我们做发卡的只用管WEBWAP就行。

然后是商户订单号、订单金额(十进制,八位小数)、货币以及商品描述。注意,这里的货币仅支持"BUSD","USDT","MBOX

大概的实现长这样:

$data = [
                "env" => [
                    "terminalType" => $client,
                    "orderClientIp" => $this->order->buy_ip
                    ],
                "merchantTradeNo" => $this->order->order_sn,
                "orderAmount" => (float)$this->order->actual_price,
                "currency" => $this->payGateway->merchant_id,
                "goods" => [
                    "goodsType" => "02",
                    "goodsCategory" => "Z000",
                    "referenceGoodsId" => $this->order->goods_id,
                    "goodsName" => $this->purgeString($this->order->title)
                    ],
                "buyer" => [
                    "buyerEmail" => $this->order->email
                    ],
                "returnUrl" => url('detail-order-sn', ['orderSN' => $this->order->order_sn]),
                "orderExpireTime" => dujiaoka_config_get('order_expire_time', 5) * 60 * 1000,
                "webhookUrl" => url($this->payGateway->pay_handleroute . '/notify_url')
                ];

进行签名#

根据官方描述,我的实现是这样:

private function paramSign(arrary $params){
        $payload = $this->timestamp . "\n" . $this->noce . "\n" . json_encode($params) . "\n";
        $hash = hash_hmac('sha512', $payload, $this->payGateway->merchant_pem, true);
        return strtoupper(bin2hex($hash));
    }

进行测试#

很不巧,第一个碰到的错误是:

Client error: `POST https://bpay.binanceapi.com/binancepay/openapi/v2/order` resulted in a `400 Bad Request` response: {"status":"FAIL","code":"403023","errorMessage":"order has expired "}

我寻思我这不是在下单吗,怎么订单就过期了?我开始胡乱猜想是不是跟我的时区有关?

结果并不是,是我主动加的orderExpireTime,内容应该是目标时间点,而不是有效期长度。

运气好,没有其他问题了:

array(3) {
  ["status"]=>
  string(7) "SUCCESS"
  ["code"]=>
  string(6) "000000"
  ["data"]=>
  array(8) {
    ["prepayId"]=>
    string(18) "订单号"
    ["terminalType"]=>
    string(3) "WEB"
    ["expireTime"]=>
    int(1681103228641)
    ["qrcodeLink"]=>
    string(93) "收款二维码"
    ["qrContent"]=>
    string(63) "二维码内容"
    ["checkoutUrl"]=>
    string(68) "结账地址"
    ["deeplink"]=>
    string(148) "币安App的跳转链接"
    ["universalUrl"]=>
    string(284) "币安的UniversalUrl"
  }
}

不过提供给我这么多链接,图方便我只需要结账地址就行,其他跳转的事交给币安去做。

iShot_2023-04-10_13.07.10

好,到这里,下单的事情就搞定了。接下来是接受回调。

第二步,处理回调#

官方给出的返回示例是:

{
  "bizType": "PAY",
  "data": "{\"merchantTradeNo\":\"xr6wYe8ATWE6thS5Sc7ezMihMFGKn6\",\"productType\":\"Food\",\"productName\":\"Ice Cream\",\"transactTime\":1619508939664,\"tradeType\":\"APP\",\"totalFee\":0.88000000,\"currency\":\"BUSD\",\"transactionId\":\"M_R_282737362839373\",\"openUserId\":\"1211HS10K81f4273ac031\",\"commission\":0,\"paymentInfo\":{\"payerId\":1000013312869,\"payMethod\":\"funding\",\"paymentInstructions\":[{\"currency\":\"BUSD\",\"amount\":0.88000000,\"price\":1}],\"channel\":\"DEFAULT\"}}",
  "bizIdStr": "29383937493038367292",
  "bizId": 29383937493038367292,
  "bizStatus": "PAY_SUCCESS"
}

那么我首先要检查订单号是否合法。可以参考Webhook 文档

获得公钥#

币安为支付回调进行了RSA加密,所以我们要先取得公钥。

需要注意的是,获取公钥的操作和下单之类的 API 是一样的鉴权方式,官方文档没看太明白,试了一下才试出来。
搜了官方例程才知道:
iShot_2023-04-10_17.44.21

我直接贴实现代码:

 private function getPublicKey($apiID){
        $endpoint = "/binancepay/openapi/certificates";
        
        $client = new Client([
		    'headers' => [ 
			    'Content-Type' => 'application/json',
			    'BinancePay-Timestamp' => $this->timestamp,
			    'BinancePay-Nonce' => $this->noce,
			    'BinancePay-Certificate-SN' => $apiID,
			    'BinancePay-Signature' => $this->paramSign([])
			]
		]);
		$response = $client->post($this->server.$endpoint, ['body' =>  json_encode([])]);
        $body = json_decode($response->getBody()->getContents(), true);
        var_dump($body);
    }

这里请求的时候携带参数为空数组就行。

验签#

验签的时候必须使用原始数据,不能进行任何操作。 比如解析 json 再解析回来之类的。

然后验签用的东西都是币安提供给你的,不要用错了。

加点细节#

付款的时候突然发现:怎么收款货币单位没有按照法币自动转换?

好吧,看了一圈并不支持传递法币自动转换,那只能自己实现了。

iShot_2023-04-10_19.19.02

搞定,测试,收工!

一点点福利#

用了币安支付的红包功能,有账户的可以直接领取:

telegram-cloud-photo-size-5-6170146392937444716-y
红包代码 BPLM61ESK4
网页端 https://www.binance.com/zh-CN/my/wallet/account/payment/cryptobox

没有账户?
https://accounts.binance.com/register?ref=157122450
当然是走 AFF 注册一个啦 🫣

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.