-
-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathp2p_payment_destination.go
148 lines (125 loc) · 4 KB
/
p2p_payment_destination.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package paymail
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"github.com/bitcoinschema/go-bitcoin/v2"
)
/*
Example:
{
"satoshis": 1000100
}
*/
// PaymentRequest is the request body for the P2P payment request
type PaymentRequest struct {
Satoshis uint64 `json:"satoshis"` // The amount, in Satoshis, that the sender intends to transfer to the receiver
}
// PaymentDestinationResponse is the response from the GetP2PPaymentDestination() request
//
// The reference is unique for the payment destination request
type PaymentDestinationResponse struct {
StandardResponse
PaymentDestinationPayload
}
// PaymentDestinationPayload is the payload from the response
//
// The reference is unique for the payment destination request
type PaymentDestinationPayload struct {
Outputs []*PaymentOutput `json:"outputs"` // A list of outputs
Reference string `json:"reference"` // A reference for the payment, created by the receiver of the transaction
}
// PaymentOutput is returned inside the payment destination response
//
// There can be several outputs in one response based on the amount of satoshis being transferred and
// the rules in place by the Paymail provider
type PaymentOutput struct {
Address string `json:"address,omitempty"` // Hex encoded locking script
Satoshis uint64 `json:"satoshis,omitempty"` // Number of satoshis for that output
Script string `json:"script"` // Hex encoded locking script
}
// GetP2PPaymentDestination will return list of outputs for the P2P transactions to use
//
// Specs: https://docs.moneybutton.com/docs/paymail-07-p2p-payment-destination.html
func (c *Client) GetP2PPaymentDestination(p2pURL, alias, domain string,
paymentRequest *PaymentRequest) (response *PaymentDestinationResponse, err error) {
// Require a valid url
if len(p2pURL) == 0 || !strings.Contains(p2pURL, "https://") {
err = fmt.Errorf("invalid url: %s", p2pURL)
return
}
// Basic requirements for request
if paymentRequest == nil {
err = errors.New("paymentRequest cannot be nil")
return
} else if paymentRequest.Satoshis == 0 {
err = errors.New("satoshis is required")
return
} else if len(alias) == 0 {
err = errors.New("missing alias")
return
} else if len(domain) == 0 {
err = errors.New("missing domain")
return
}
// Set the base url and path, assuming the url is from the prior GetCapabilities() request
// https://<host-discovery-target>/api/rawtx/{alias}@{domain.tld}
// https://<host-discovery-target>/api/p2p-payment-destination/{alias}@{domain.tld}
reqURL := replaceAliasDomain(p2pURL, alias, domain)
// Fire the POST request
var resp StandardResponse
if resp, err = c.postRequest(reqURL, paymentRequest); err != nil {
return
}
// Start the response
response = &PaymentDestinationResponse{StandardResponse: resp}
// Test the status code
if response.StatusCode != http.StatusOK &&
response.StatusCode != http.StatusNotModified {
// Paymail address not found?
if response.StatusCode == http.StatusNotFound {
err = errors.New("paymail address not found")
} else {
serverError := &ServerError{}
if err = json.Unmarshal(resp.Body, serverError); err != nil {
return
}
err = fmt.Errorf(
"bad response from paymail provider: code %d, message: %s",
response.StatusCode, serverError.Message,
)
}
return
}
// Decode the body of the response
if err = json.Unmarshal(resp.Body, &response); err != nil {
return
}
// Check for a reference number
if len(response.Reference) == 0 {
err = errors.New("missing a returned reference value")
return
}
// No outputs?
if len(response.Outputs) == 0 {
err = errors.New("missing a returned output")
return
}
// Loop all outputs
for index, out := range response.Outputs {
// No script returned
if len(out.Script) == 0 {
err = fmt.Errorf("script was missing from output: %d", index)
return
}
// Extract the address
if response.Outputs[index].Address, err = bitcoin.GetAddressFromScript(
out.Script,
); err != nil {
return
}
}
return
}