支付处理
请求支付
在请求支付之前,程序需要首先生成一个比特币地址或者从其他程序,如Bitcoin Core,获得一个地址。比特币地址的详细信息在交易一节中阐述,并且在该节中给出了为何要避免多次使用同一个比特币地址的两个重要原因——而第三个原因则是与支付请求有关。
每次收款时使用单独地址会使辨别付款顾客身份变得更加繁琐。程序只需要追踪特定的的支付请求以及其中包含的地址,然后搜索区块链,查询匹配该地址的交易,即可确认身份。
下一小节将详细介绍四种相互兼容的方法,用以向支付者提供支付地址和金额。出于便利性和兼容性考虑,推荐支持所述全部方法。
钱包程序允许用户在支付界面中粘贴或者手动输入地址和支付金额。当然,这种方法并不方便,但提供了一种有效的退却选择。
几乎所有的桌面钱包都可以关联到bitcoin: URIs,支付者只需要点击链接即可直接进入支付界面,同时支付地址与金额已经预填完整。许多手机移动钱包也支持此项功能,但网页钱包基本不支持,除非通过安装浏览器扩展程序或者配置URI链接句柄。
大多数移动钱包支持扫描包含bitcoin: URIs编码信息的QR码,并且几乎所有的钱包程序都支持显示收款二维码。这同时也方便了在线订单,QR码对于当面交易十分有用。
近期的钱包更新增加了对一种新型支付协议的支持,该协议通过X.509证书认证收款者身份,提高了支付的安全性,并且引入了一些重要新特性如退款等。
警告:需要特别当心针对收款支付的盗窃行为。尤其要注意的是,私钥绝对不能储存在网络服务器上,并且支付请求需要通过HTTPS或其他方法加密传输,防止中间人攻击替换收款地址。
纯文本
如果需要只通过复制粘贴手段就能确定支付数量,你需要提供地址、数量和单位。当然最好也包含一个有效时间,例如:
(注:所有本节范例使用的均是Testnet 地址。)
必须指定单位。在撰写本文时所有的流行比特币钱包程序默认显示单位均是bitcoins (BTC)或millibits(mBTC)。大多数都支持选择BTC或mBTC之一作为显示单位,还有一些程序支持以下这些单位。
由于BTC和mBTC都被广泛接受,在以复制粘贴文本定义支付订单时,同时指定两种单位下的数额显得更加直观。例如:
bitcoin: URI
在BIP21中定义的 bitcoin: URI 方案消除了支付者在复制粘贴文本中可能出现的支付单位的混淆。同时也能通过支付订单向支付者提供额外的信息。举个例子:
只有地址是必要的,如果只定义了地址,钱包会生成一个预填好收款地址的支付请求,需要支付者输入支付数量。
支付数量总是以BTC的小数形式确定,对于整数数量的BTC(如上例),可以省略小数点。小数数量的BTC的数量开头的0可以省略;例如,下面例子中的1mBTC的请求均是有效的:
还有两个被广泛支持的参数label
和message
。label
参数用来标识收款人名字,message
参数通常被支付者用来描述支付请求。Label和message参数都被存储在支付者钱包程序中并不会被包含在真正的交易中,所以其他的比特币用户无法看到这两个参数信息。这两个参数必须通过URI编码。
四个参数集合起来,通过URI编码,转行显示为如下形式。
上述的URI可以编码成HTML格式,以兼容不支持URI链接的钱包程序,并且可以向支付者提供一个有效时间。
上述代码生成链接如下:Order flowers & chocolates using Bitcoin(Pay 0.10 BTC [100 mBTC] to mjSk1Ny9spzU2fouzYgLqGUD8U41iR35QN by 2014-04-01 at 23:00 UTC)
一些订单通过用Javascript显示倒计时来表示有效剩余支付时间。
在后文中的支付协议一节中可以看到,URI方案可以通过新的可选和必要参数进行扩展。在撰写本文时,唯一被广泛接受的新参数是支付协议中的r参数。
程序无论支持哪种形式的URIs链接,最终都需要通过提示由用户最终确认支付,除非用户明确地禁用了提示(可以用于小额支付)。
QR码
QR码可以用来交换bitcoin:
URIs信息,广泛应用于当面交易、图像或影像中。大多数的移动钱包程序和部分桌面钱包程序都支持扫描QR码,直接进入预填支付界面。
下图中的四张比特币QR码显示的是同一个bitcoin:
URIs码,分别以四种不同的错误修正级别(显示在在二维码上方)编码生成的。QR码可以包含label
和message
参数,以及其他可选参数。在本例中为了压缩QR码的大小和保证较低素质的摄像头可以容易扫描成功,并未包含额外参数。
QR码提供了四种不同错误修正等级:
低:最多可修复7%的图像损失
中:最多可修复15%的图像损失,但QR码面积比低级修复等级图像大近8%。
四分位:最多可修复25%图像损失,但QR码面积比低级修复等级图像大近20%。
高:最多可修复30%图像损失,但QR码面积比低级修复等级图像大近26%。
错误修正配合校检和(checksum)可以确保比特币QR码信息完整且不会被意外修改,因此你的程序需要针对不同的显示面积来选择合适的错误修正等级。显示面积有限时低错误修正等级更加适用,而当具备高分辨率显示能力时四分位修正等级可以帮助实现快速扫码。
支付协议
比特币核心0.9版本支持新的支付协议。针对支付需求,新的支付协议添加了很多重要的功能。
支持X.509认证和SSL加密,以便能够对接收者的身份进行认证并阻止中间人的恶意攻击。
提供更多支付过程的细节
无需点对点网络,支付方可以直接付款给接收方。这将加快支付过程并为一些计划中的功能的实现做了铺垫,例如“子母交易(接受者可以使用尚未得到确认的款项发起另外的交易)”、离线近场交易、蓝牙交易。
在这个版本(x.509)中,支付者还可以付款给例如这样的通用名地址【Common Name (CN)】,而不是付款给一串毫无意义的字符,如:“mjSk1Ny9spzU2fouzYgLqGUD8U41iR35QN” 为了支付过程使用这个支付协议,你使用一个拓展但能够回溯兼容的URL地址,例如:
以上命令行当中,除了R命令行之外,均不是本支付协议所必须的。但在你的应用程序中可能包含了它们,以便使那些不支持本协议的钱包实现回溯兼容的功能。
R命令会使支持本支付协议的钱包自动忽略其他的参数,只需要从URL地址取回支付命令就可以了。如果有特殊的需要(在此只做推荐而不做为强制要求)也可以从HTTP服务器上取回支付命令——从HTTP上取回命令会更好一些。
浏览器、二维码扫描设备或者其他处理URL的程序可以打开支付方在URL上的比特币钱包程序。如果钱包程序识别本支付协议,它将会进入R命令所指定的URL地址,R命令中提供一系列MIME格式的收付请求。
资源:加文.安德森的“支付请求发生器(Payment Request Generator)”生成了在测试网络上运行的客户URL和支付请求。
支付请求和支付细节
“支付请求”由谷歌的Protocol Buffers语言的数据结构所创建。BIP70通过非序列性方式(在支付请求的Protocol Buffer代码中被定义)来表达数据结构,但下列文本文件中,它们会通过使用简单的或者说是更实用的pythonCGI程序以更多线性命令来呈现。(为了更加简洁清晰,许多正常的CGI最佳实践没有在该程序中使用)
整个过程将被举例说明:起初,一个用户点击一个BITCOIN的URL地址或者扫描二维码。
为了在脚本中使用protocol buffer,你需要一个Protocol Buffer编译器(protoc),,你可以直接从GOOGLE下载或者在大多数Linux软件包中找到它。谷歌之外的 protocol buffer编译器也是可以用的,要看编程语言的类型。你也需要从比特币核心代码中获取一个“支付请求”的Protocol Buffer描述。
初始化代码
从Protocol Buffer生成的Python代码开始,我们着手开发简单的CGI程序
上面的启动代码是相当简单的,仅需要UNIX系统的初相时间函数、标准输出的文件描述符、来自OpenSSL library的一部分函数和由 Protocol Buffer生成的数据结构和函数。
配置代码
接下来,我们将设定配置设置,该设置通常只有在接受者想去做点不同的事情时发生改变。这段代码在request
(PaymentRequest) 和details
(PaymentDetails)项目中植入一部分装置。当将其串行化时,PaymentDetails将包含在PaymentRequest.之内。
每一行被下面的命令所描述。
pki_type:
(可选)告诉接受者的钱包程序使用何种公钥架构对你的 PaymentRequest进行加密签名,以避免其被中间人发起的攻击所更改。
如果你不想对 PaymentRequest进行签名,也可以选择pki_type of none(the default语句)
如果你要选择对PaymentRequest进行签名,当下有2中选择:x509+sha1
和x509+sha256
,此2种均使用X.509认证系统,与HTTPS系统使用的一样。使用任何一种签名方式,你都需要被一个认证服务器或中间人之一签名的认证。(自己给自己签名是无效的)。
每个钱包程序可以选择相信信任哪一种认证服务器,但最有可能的是选择其操作系统所信任的认证服务器。如果钱包程序没有完整的操作系统,那么其可能是小型的硬件钱包,BIP70建议其使用Mozilla的根证书存储。总的来说,当你接入网络服务的时候,你的浏览器认证在起作用,将为你的Paymentrequests提供帮助。
network
:(可选)告知付费一方的钱包你正在使用什么样的比特币网络。BIP70将主网络(实际支付)标记“main”,而将测试网络(类似于主网络,但使用”假币“)标记为”test”。如果钱包程序不用在你指定的网络上运行,它将拒绝PaymentRequest。
payment_url
:(必要的)告知付费方的钱包程序将支付信息( Payment message 稍后会讨论)发向何处。可以是静态URL,比如在示例中那样,也可以是动态URL,例如https://example.com/pay.py?invoice=123. 它通常是一个HTTPS地址,以阻止第三方通过修改信息来发动攻击(man-in-the-middle attacks )。
payment_details_version
:(可选)告知付费方的钱包程序PaymentDetails 的版本信息。正如示例中,唯一的版本是版本1.
x509certificates
:(对PaymentRequests进行签名所必须)你必须提供SSL公钥/能够对对应的SSL私匙(用来对PaymentRequest进行签名)进行认证
你必须提供任何必要的中介认证(man-in-the-middle attacks ),使你的认证与付费方信任的认证服务器的根认证相连。例如Mozilla的根证书存储。
认证必须以清晰具体的命令提供——相同的命令被使用在阿帕奇(Apache)的SSLCertificateFile
指令和其他服务软件中。下面的数字显示了X509认证的证书链和每个认证(根认证除外)将会如何被下载到X509证书的protocol buffer的信息中。
具体而言,第一个应提供的认证必须是X509认证(对应用来签名的SSL私匙),称之为leaf 认证(leaf certificate)。任何的中介认证(intermediate certificates)必须将被签名的SSL公钥与根认证(CA证书)相连,每一个中介认证被分开连接,每一个DER格式的认证包含了在任意路径上追随根认证的认证签名。
(为PaymentRequests签名所必须)在你的SSL数据库支持格式中(DER格式不是必须的), 你需要一个SSL私匙。在程序中,我们从一个PEM文件下载它。(在你的CGI代码中嵌入你的密码,像这里一样,无疑是个坏主意)
SSL私匙不会应你的要求被发送在这,我们仅仅下载它进入内存,为了能够使用它给request签名。
代码变量
现在让我们看看变量,你的CGI程序为每笔支付而设置。
每一行代码的意义如下描述
amount
:(可选)你希望付费方支付的数量。你可能从你的购物车程序或者BTC兑换汇率转换工具的命令中给出指令。如果你不指定该数量,钱包程序将提示付费方默认的支付数量(这点在募捐中是有用的)
script
:(必须的)你必须指定你要付费方支付的输出脚本---任何有效的脚本都是可以的。在示例中,我们要求的脚本是P2PKH输出脚本。
首先我们得到一个公钥HASH,这个HASH就是通常使用在本段文字URL示例中的那个比特币地址的HASH:mjSk1Ny9spzU2fouzYgLqGUD8U41iR35QN.
接下来,我们把这个HASH使用16进制,转换成标准的P2PKH输出脚本,如代码注释所示。
最后,我们将输出脚本从16进制转换成为序列化格式。
outputs
:(必要的)将输出脚本和数量(可选)添加到 PaymentDetails输出数组中。
指定多组脚本(multiplescripts
)和数量(scripts
) 并将其作为合并退避策略的一部分,是可以做到的。随后将来“合并退避条款(Merge Avoidance)”中进行描述。
memo
:(可选)增加一个向付费者展示的备忘录(使用UTF-8纯文本),内嵌HTML或者其他MARKUP的将不会被处理。
merchant_data
:(可选)当款项被支付后,添加任意数据,这些数据会被返回给接受者。你能使用这些数据追踪你的款项,尽管你可以通过生成一个唯一地址来追踪你的每一笔支付。
memo
和merchant_data
可以使任意字节长,但是如果太长,也许整个 PaymentRequest会受到50000个字节的限制,其中也包括通常是几个KB的用于保存证书链的容量。在后面的小节中将提到, memo 经常在支付后被付费方用于组成密码收据的一部分。
衍生数据
接下来,让我们看看你的CGI程序可以自动衍生出来的一些信息
下面对各行进行详细的解释
details.time = int(time()) ## Current epoch (Unix) time
Time
: (必须)PaymentRequests必须指明他们是什么时候被创建的,用距离1970-01-01T00:00 UTC的秒数表示
expires
:(可选) PaymentRequest也可以设定一个失效时间,当过了这个之间之后这些PaymentRequest就不在可用。你很可能想让接收者能够设定失效时间;在此我们使用了一个我们认为比较合理的选择,十分钟。如果这个请求绑定在一个基于法币对比特聪(fait-to-satoshis)的汇率上,那么你可能希望以你获取汇率时的对冲值作为依据;
serialized_payment_details
: (必须)到现在为止,我们已经完成了创建PyamentDetails的所有设置,所以我们使用SerializeToString函数,把PaymentDetails从比特币协议的缓冲码(protocol buffer code)映射成PaymentRequest的合适的属性。
pki_data
:(签名的PaymentRequests所必须)序列化验证链的PKI数据并把这些数据存在PaymentRequest中。
我们已经填写了出了签名之外的PaymentRequest所有的属性,但是,在我们给他签名之前,我们必须赋值给signature属性一个空的占位符来完成初始化。
signature:(签名的PaymentRequests必须)现在我们使用完整的序列化的PaymentRequest,内存中配置区的私钥和pki_type指定的哈希公式(在这个例子中使用的sha256) 创建签名。
输出代码
既然我们已经添加了PaymentRequest的所有属性,我们现在可以对其序列化并且通过http头发送它,例如下面的代码:
(必须)BIP71定义了PaymentRequests,Payments和PaymentACKs的内容类型
request
:(必须)现在,我们输出序列化过的PaymentRequest(其中包含序列化过的PaymentDetails)。序列化之后的数据是二进制的,所以我们不能使用Python的print函数,因为它会添加很多无关的行。
下面的截屏显示了在Bitcoin Core 0.9中GUI程序是怎样创建一个经过验证的PaymentDetails的。
支付
如果消费者拒绝支付,钱包程序不会再给接收者的服务器发送任何信息,除非消费者有点击了其他的指定那位接收者服务器的URI。如果消费者确定支付,钱包程序会至少创建一个交易事务用来支付PaymentDetails区的每个付款项(outputs). 钱包程序可能会广播这个交易事务,Bitcoin Core0.9会这样做,但是这不是必须的。
无论钱包程序是否广播交易事务,它都会给PaymentRequest一个回复信息,这个回复信息被称为Payment。Payment包含四个属性。
merchant_data:(可选)一份PaymentDetails的merchant_data属性的拷贝。
transactions:(必须)支付到PaymentDetails中的输出(outputs)的一个或者多个签名过的交易事务。
refund_to:(必须)接受者可以发送部分或者全部的refund的一个或者多个输出脚本。
memo:(可选)发送给接收者的一个UTF-8编码的文本备忘录。不能包含HTML或者其他的标记。消费者不应该假定接受者肯定会阅读他们的备忘录文本。
Payment会通过PaymentDetails中提供的payment_url来发送。此url应该是一个https地址以防止传输过程中有人发动攻击,篡改消费者的refund_to输出脚本。发送Payment的时候,钱包程序必须设置以下HTTP头:
支付确认
接收者在payment_url的CGI程序会接收支付信息并且将其用比特币协议的缓冲码(Protocol Buffers code)解码。交易事务(transactions)会被检查接收者请求的输出脚本(output scripts)是否已经被支付了,是否已经被广播到了网络中(除非网络中已经存在了)。
如果必要的话,CGI程序会检查merchant_data参数,并且发送一个包含以下http头的PaymentACK确认。
然后发送另一个使用比特币协议缓冲码(Protocol-Buffers-encoded)编码的包含一个或者两个属性的信息.
payment:(必须)这是一个正在被确认的支付信息的完整拷贝(以序列化的形式)。
memo:(可选)一个使用UTF-8编码的文本备忘录,用来提醒消费者他们支付款项的状态。此备忘录不应该包含HTML或者其他标记。接收者不应该假定消费者会阅读这些备忘录。
PaymentACK确认并不意味着支付完成了,它仅仅意味着支付是正常的。支付在支付事务被区块链确认后,整个支付才真正的完成。
但是,消费者的钱包程序应该展示给消费者,支付正在处理中,以便消费者能把注意力转移到其他地方。
收据
与PaymentRequest,PaymentDetails,Payment以及PaymentACK不同,并没有一个专门的收据对象(receipt object)。但是,我们可以从签名的PaymentDetails和一个或者多个已经确认的交易事务中,推算出一个密码学上可证实的收据。
一个签过名的PaymentDetails指明了需要支付到的输出脚本(output scripts)(script),需要支付多少(amount),以及时间(expires)。比特币区块链指明了这些输出(outputs)是否支付了要求数目的比特币,并且能够提供一个交易创建的大概时间。有了这些信息,我们就可以验证是否消费者使用了接受者私有的SSL key支付了比特币给接收者。
验证付款
正如在 Transactions和 Block Chain部分解释的一样,向比特币网络中广播一个比特币交易,并不能保证接收者一定得到支付。一个恶意的黑客在创建一个支付接收者的交易的同时,同时创建一个支付给自己的交易。这两个交易记录只有一个会被添加到区块链中,没有人能确定哪一个会。
两个或多个交易响应了同一个请求,通常被称为再次支付。
一旦一笔交易记录被添加到区块链中,再次支付是不可能的。除非通过修改区块链历史记录替代交易记录,这是非常困难的。通过系统机制,比特币协议可以给每笔交易一个可以更新的基于数据块数量的信任度得分。当需要替换一个交易时,数据块数量需要被更改。对于每一个数据块,交易获得一次确认。因为修改数据块数量是非常困难的,更多的确认次数说明更高的安全性。
0 次确认:交易请求已经广播出去,但是还没有被记录到任何一个数据块。0 确认交易(未确认的交易)一般来说是应该被信任的,没有任何风险分析。尽管矿工通常会确认收到的第一个交易,但是欺诈者可能操纵网络,使他们的交易被确认。
1次确认:交易被记录到最新的数据块中,二次支付的风险就会随之显著降低。 支付了足够交易手续费的交易一般需要10分钟收到第一个确认。虽然如此,由于最新的数据块被更换的非常频繁,所以二次支付的情况依然存在。
2次确认:最新的数据块会被加入到包含交易的数据链中。截止到2014年3月,二次支付是极为罕见的。两个数据块的替换攻击是不可能的,除非利用昂贵的矿机设备。
6次确认:比特币网络需要
收入支付(控制外汇风险)
许多比特币接收者都在担心他们的比特币的价值会越来越少,并把这种现象叫做外汇风险。为了降低这种外汇风险,接收者会在接受到比特币不久后就会用来支付其他需要支付的款项。
如果你的应用程序支持这种商业逻辑,就要选择先用哪一输出去支付。这里将介绍一些算法,不同的算法往往会得到不同的结果。
一种叫做merge avoidance避免合并的算法,让其他人很难通过区块链数据弄清楚,接收者的这些比特币是如何获取,使用和储存的。
LIFO(后进先出)算法,可以用来挖比特币,但是它消耗双倍比特币(双花)的风险很大,并使得其他的比特币也承受同样的风险。这种算法可以说是收益很大,但同时一旦出现问题可能带来损失并有损他们的声誉。
FIFO(先进先出)这种算法是运用最原始的比特币单位值,可以使保证接收者的资金安全并在使用前都是被确认的,但这种效用只体现在个别的例子中。
Merge Avoidance 避免合并付款
当接收者在一个输出接受比特币后,比特币的发送者可以追踪到接收者如何花费比特币(用原始的方法)。如果接收者在每次交易时所用的地址与之前交易地址不同,那么发送者将无法自动的检测到比特币的交易去向。
但是,如果接收者与两个发送者在同一笔交易里,两个发送者都可以看到对方的支付金额。这就叫做合并交易,接收者合并的输出越多,外界就更容易的跟踪到接收者的比特币是如何获取,使用和储存的。
避免合并交易(Merge avoidance)这种算法可以避免在同一交易中不相关的输出相互影响。不管对于个人还是商业交易,都可以保证交易的数据安全,不被他人窃取,这将会成为一种重要的战略。
这种战略往往会用大于要求数额的最小的输出来支付。如果你有4种数值的输出分别是100,200,500以及900的比特币,但你要付300的账单时,你只需要用500价值的比特币付款,由此可以避免合并付款。
进阶级的避免合并战略很大程度取决于增强支付协议的效用,就是允许支付人在接收者提供的多种输出中用智能分配来避免合并支付。
后进先出算法(LIFO)
输出中的比特币在刚一接收后就可以用来支付,即使在这些比特币确认前。在新的输出处于一个将要支付双倍款项(double-spent双花)的危险下,旧的输出允许支付者控制被确认后的旧输出,这样比双倍支付款项要少支付许多。
对与LIFO来说,它有两个与它密切相关的缺点:
在第二次交易中,你的输出来自于一个未被确认的交易,其中你的第一次交易被修改,那么第二次交易将失效。
在第二次交易中,你在花费一个暂未被确认的输出中的比特币时,如果第一次交易中的输出成功双倍付给其他的输出,那么第二次交易依旧会失效。
无论是以上的哪一个例子,第二次交易的接收者都会看到进账交易消失的通知或是转为一个错误的信息。
之所以会这样是因为,LIFO会令第二次交易的接收者承受跟第一次交易接收者的两倍风险。如果第二次交易的接收者不在乎风险,要进行下一步动作时,无论使用的是旧的输出或是新的输出,都需要通过6次信息确认后方可以进行。
当初次交易中的接收者名誉处于险境时,他们不应该用LIFO,例如当他们为雇员发放工资时。通过事例,应该等交易完全确认后再用来发放酬劳。
先进先出算法(LIFO)
最久远的比特币的输出是是可靠的,因为如果要实行双重支付,它所经过的很多区块都需要被修改。尽管这样,如果仅经过了少数几个区块,双重支付要花的代价便迅速减少了。最初的《比特币白皮书》预言了攻击者如果掌握了全网算力的30%,它就可以修改区块链:
从交易费用这方面看,FIFO确实有小小优势,像之前的输出需有5000字节的容量才会有资格列入由矿工运行Bitcoin Core 代码库发起的无费用高优先级交易。 即使交易费用如此的低,但也没有多大的优势。
只有在接收者想花费几区块链中大部分或全部,或是试图减少交易意外失效的几率的情况下,FIFO才会显示出它实际的用途。例如,一个接收者要通过6次信息确认才能进行每一笔交易,且要通过100%被确认的付款项给到销售商和一个可以每2小时就可以查看的储蓄账户。
Rebilling Recurring Payments
去中心化的比特币钱包是不会出现重复付款的现象的。即使这种比特币钱包支持自动不可逆的付款,但仍需要使用者在规定的时间启动应用程序,或是在不加密的状态下,保持钱包打开状态。
这就意味着比特币的自动化重复付款只能由比特币持有者授权的中央服务器生成。在实验中,如果接收者想用国际单位设置接收价格,那么必须通过与之前一样的中央服务器选择适合的汇率进行价格换算。
在信用卡重复付款之前,非自动的重建订单是可以被同一种机制操控,在现在来讲是非常普遍的:与使用者联系,并让他们进行再一次的支付,例如给他们发送一个含有请求支付链接的邮件。
在未来,通过扩展新的支付协议以及增加新型钱包的功能,能够使一些钱包的应用程序具有处理一系列的重复交易的功能。对于支付来讲,付款者有了比点击请求支付邮件来付款更方便也更安全的支付方法,并增加了接收者的准时到账的几率。
Last updated