@ -27,9 +27,10 @@ import sys
import time
import time
import traceback
import traceback
import json
import json
import requests
import requests
import urllib . parse
import urllib . parse
import aiohttp
try :
try :
@ -38,15 +39,17 @@ except ImportError:
sys . exit ( " Error: could not find paymentrequest_pb2.py. Create it with ' protoc --proto_path=electrum/ --python_out=electrum/ electrum/paymentrequest.proto ' " )
sys . exit ( " Error: could not find paymentrequest_pb2.py. Create it with ' protoc --proto_path=electrum/ --python_out=electrum/ electrum/paymentrequest.proto ' " )
from . import bitcoin , ecc , util , transaction , x509 , rsakey
from . import bitcoin , ecc , util , transaction , x509 , rsakey
from . util import print_error , bh2u , bfh , export_meta , import_meta
from . util import print_error , bh2u , bfh , export_meta , import_meta , make_aiohttp_session
from . crypto import sha256
from . crypto import sha256
from . bitcoin import TYPE_ADDRESS
from . bitcoin import TYPE_ADDRESS
from . transaction import TxOutput
from . transaction import TxOutput
from . network import Network
REQUEST_HEADERS = { ' Accept ' : ' application/bitcoin-paymentrequest ' , ' User-Agent ' : ' Electrum ' }
REQUEST_HEADERS = { ' Accept ' : ' application/bitcoin-paymentrequest ' , ' User-Agent ' : ' Electrum ' }
ACK_HEADERS = { ' Content-Type ' : ' application/bitcoin-payment ' , ' Accept ' : ' application/bitcoin-paymentack ' , ' User-Agent ' : ' Electrum ' }
ACK_HEADERS = { ' Content-Type ' : ' application/bitcoin-payment ' , ' Accept ' : ' application/bitcoin-paymentack ' , ' User-Agent ' : ' Electrum ' }
ca_path = requests . certs . where ( )
ca_path = requests . certs . where ( ) # FIXME do we need to depend on requests here?
ca_list = None
ca_list = None
ca_keyID = None
ca_keyID = None
@ -64,13 +67,16 @@ PR_UNKNOWN = 2 # sent but not propagated
PR_PAID = 3 # send and propagated
PR_PAID = 3 # send and propagated
async def get_payment_request ( url : str ) - > ' PaymentRequest ' :
def get_payment_request ( url ) :
u = urllib . parse . urlparse ( url )
u = urllib . parse . urlparse ( url )
error = None
error = None
if u . scheme in [ ' http ' , ' https ' ] :
if u . scheme in ( ' http ' , ' https ' ) :
resp_content = None
try :
try :
response = requests . request ( ' GET ' , url , headers = REQUEST_HEADERS )
proxy = Network . get_instance ( ) . proxy
async with make_aiohttp_session ( proxy , headers = REQUEST_HEADERS ) as session :
async with session . get ( url ) as response :
resp_content = await response . read ( )
response . raise_for_status ( )
response . raise_for_status ( )
# Guard against `bitcoin:`-URIs with invalid payment request URLs
# Guard against `bitcoin:`-URIs with invalid payment request URLs
if " Content-Type " not in response . headers \
if " Content-Type " not in response . headers \
@ -78,11 +84,14 @@ def get_payment_request(url):
data = None
data = None
error = " payment URL not pointing to a payment request handling server "
error = " payment URL not pointing to a payment request handling server "
else :
else :
data = response . content
data = resp_content
print_error ( ' fetched payment request ' , url , len ( response . content ) )
data_len = len ( data ) if data is not None else None
except requests . exceptions . RequestException :
print_error ( ' fetched payment request ' , url , data_len )
except aiohttp . ClientError as e :
error = f " Error while contacting payment URL: \n { repr ( e ) } "
if isinstance ( e , aiohttp . ClientResponseError ) and e . status == 400 and resp_content :
error + = " \n " + resp_content . decode ( " utf8 " )
data = None
data = None
error = " payment URL not pointing to a valid server "
elif u . scheme == ' file ' :
elif u . scheme == ' file ' :
try :
try :
with open ( u . path , ' r ' , encoding = ' utf-8 ' ) as f :
with open ( u . path , ' r ' , encoding = ' utf-8 ' ) as f :
@ -92,7 +101,7 @@ def get_payment_request(url):
error = " payment URL not pointing to a valid file "
error = " payment URL not pointing to a valid file "
else :
else :
data = None
data = None
error = " Unknown scheme for payment request. URL: {} " . format ( url )
error = f " Unknown scheme for payment request. URL: { url } "
pr = PaymentRequest ( data , error )
pr = PaymentRequest ( data , error )
return pr
return pr
@ -255,7 +264,7 @@ class PaymentRequest:
def get_outputs ( self ) :
def get_outputs ( self ) :
return self . outputs [ : ]
return self . outputs [ : ]
def send_ack ( self , raw_tx , refund_addr ) :
async def send_payment_and_receive_payment ack ( self , raw_tx , refund_addr ) :
pay_det = self . details
pay_det = self . details
if not self . details . payment_url :
if not self . details . payment_url :
return False , " no url "
return False , " no url "
@ -267,24 +276,25 @@ class PaymentRequest:
paymnt . memo = " Paid using Electrum "
paymnt . memo = " Paid using Electrum "
pm = paymnt . SerializeToString ( )
pm = paymnt . SerializeToString ( )
payurl = urllib . parse . urlparse ( pay_det . payment_url )
payurl = urllib . parse . urlparse ( pay_det . payment_url )
resp_content = None
try :
try :
r = requests . post ( payurl . geturl ( ) , data = pm , headers = ACK_HEADERS , verify = ca_path )
proxy = Network . get_instance ( ) . proxy
except requests . exceptions . SSLError :
async with make_aiohttp_session ( proxy , headers = ACK_HEADERS ) as session :
print ( " Payment Message/PaymentACK verify Failed " )
async with session . post ( payurl . geturl ( ) , data = pm ) as response :
try :
resp_content = await response . read ( )
r = requests . post ( payurl . geturl ( ) , data = pm , headers = ACK_HEADERS , verify = False )
response . raise_for_status ( )
except Exception as e :
print ( e )
return False , " Payment Message/PaymentACK Failed "
if r . status_code > = 500 :
return False , r . reason
try :
try :
paymntack = pb2 . PaymentACK ( )
paymntack = pb2 . PaymentACK ( )
paymntack . ParseFromString ( r . content )
paymntack . ParseFromString ( resp_content )
except Exception :
except Exception :
return False , " PaymentACK could not be processed. Payment was sent; please manually verify that payment was received. "
return False , " PaymentACK could not be processed. Payment was sent; please manually verify that payment was received. "
print ( " PaymentACK message received: %s " % paymntack . memo )
print ( f " PaymentACK message received: { paymntack . memo } " )
return True , paymntack . memo
return True , paymntack . memo
except aiohttp . ClientError as e :
error = f " Payment Message/PaymentACK Failed: \n { repr ( e ) } "
if isinstance ( e , aiohttp . ClientResponseError ) and e . status == 400 and resp_content :
error + = " \n " + resp_content . decode ( " utf8 " )
return False , error
def make_unsigned_request ( req ) :
def make_unsigned_request ( req ) :