merchant.go (5590B)
1 package merchant 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io/ioutil" 9 "net/http" 10 11 "github.com/schanzen/taler-go/pkg/util" 12 ) 13 14 type PostOrderRequest struct { 15 // The order must at least contain the minimal 16 // order detail, but can override all. 17 Order MinimalOrderDetail `json:"order"` 18 19 // If set, the backend will then set the refund deadline to the current 20 // time plus the specified delay. If it's not set, refunds will not be 21 // possible. 22 RefundDelay int64 `json:"refund_delay,omitempty"` 23 24 // Specifies the payment target preferred by the client. Can be used 25 // to select among the various (active) wire methods supported by the instance. 26 PaymentTarget string `json:"payment_target,omitempty"` 27 28 // Specifies that some products are to be included in the 29 // order from the inventory. For these inventory management 30 // is performed (so the products must be in stock) and 31 // details are completed from the product data of the backend. 32 // FIXME: Not sure we actually need this for now 33 //InventoryProducts []MinimalInventoryProduct `json:"inventory_products,omitempty"` 34 35 // Specifies a lock identifier that was used to 36 // lock a product in the inventory. Only useful if 37 // inventory_products is set. Used in case a frontend 38 // reserved quantities of the individual products while 39 // the shopping cart was being built. Multiple UUIDs can 40 // be used in case different UUIDs were used for different 41 // products (i.e. in case the user started with multiple 42 // shopping sessions that were combined during checkout). 43 LockUuids []string `json:"lock_uuids,omitempty"` 44 45 // Should a token for claiming the order be generated? 46 // False can make sense if the ORDER_ID is sufficiently 47 // high entropy to prevent adversarial claims (like it is 48 // if the backend auto-generates one). Default is 'true'. 49 CreateToken bool `json:"create_token,omitempty"` 50 } 51 52 type MinimalOrderDetail struct { 53 // Amount to be paid by the customer. 54 Amount string `json:"amount"` 55 56 // Short summary of the order. 57 Summary string `json:"summary"` 58 59 // See documentation of fulfillment_url in ContractTerms. 60 // Either fulfillment_url or fulfillment_message must be specified. 61 FulfillmentUrl string `json:"fulfillment_url"` 62 } 63 64 // NOTE: Part of the above but optional 65 type FulfillmentMetadata struct { 66 // See documentation of fulfillment_url in ContractTerms. 67 // Either fulfillment_url or fulfillment_message must be specified. 68 FulfillmentUrl string `json:"fulfillment_url,omitempty"` 69 70 // See documentation of fulfillment_message in ContractTerms. 71 // Either fulfillment_url or fulfillment_message must be specified. 72 FulfillmentMessage string `json:"fulfillment_message,omitempty"` 73 } 74 75 type PostOrderResponse struct { 76 // Order ID of the response that was just created. 77 OrderId string `json:"order_id"` 78 } 79 80 type PostOrderResponseToken struct { 81 // Token that authorizes the wallet to claim the order. 82 // Provided only if "create_token" was set to 'true' 83 // in the request. 84 Token string 85 } 86 87 type CheckPaymentStatusResponse struct { 88 // Status of the order 89 OrderStatus string `json:"order_status"` 90 } 91 92 type CheckPaymentPaytoResponse struct { 93 // Status of the order 94 TalerPayUri string `json:"taler_pay_uri"` 95 } 96 97 type Merchant struct { 98 99 // The host of this merchant 100 BaseUrlPrivate string 101 102 // The access token to use for the private API 103 AccessToken string 104 } 105 106 func NewMerchant(merchBaseUrlPrivate string, merchAccessToken string) Merchant { 107 return Merchant{ 108 BaseUrlPrivate: merchBaseUrlPrivate, 109 AccessToken: merchAccessToken, 110 } 111 } 112 113 func (m *Merchant) IsOrderPaid(orderId string) (string, error) { 114 var orderPaidResponse CheckPaymentStatusResponse 115 var paytoResponse CheckPaymentPaytoResponse 116 client := &http.Client{} 117 req, _ := http.NewRequest("GET", m.BaseUrlPrivate+"/private/orders/"+orderId, nil) 118 req.Header.Set("Authorization", "Bearer secret-token:"+m.AccessToken) 119 resp, err := client.Do(req) 120 fmt.Println(req) 121 if nil != err { 122 return "", err 123 } 124 defer resp.Body.Close() 125 if http.StatusOK != resp.StatusCode { 126 message := fmt.Sprintf("Expected response code %d. Got %d", http.StatusOK, resp.StatusCode) 127 return "", errors.New(message) 128 } 129 respData, err := ioutil.ReadAll(resp.Body) 130 if err != nil { 131 return "", err 132 } 133 err = json.NewDecoder(bytes.NewReader(respData)).Decode(&orderPaidResponse) 134 if err != nil { 135 return "", err 136 } 137 if orderPaidResponse.OrderStatus != "paid" { 138 err = json.NewDecoder(bytes.NewReader(respData)).Decode(&paytoResponse) 139 return paytoResponse.TalerPayUri, err 140 } 141 return "", nil 142 } 143 144 func (m *Merchant) AddNewOrder(cost util.Amount, summary string, fulfillment_url string) (string, error) { 145 var newOrder PostOrderRequest 146 var orderDetail MinimalOrderDetail 147 var orderResponse PostOrderResponse 148 orderDetail.Amount = cost.String() 149 // FIXME get from cfg 150 orderDetail.Summary = summary 151 orderDetail.FulfillmentUrl = fulfillment_url 152 newOrder.Order = orderDetail 153 reqString, err := json.Marshal(newOrder) 154 if nil != err { 155 return "", err 156 } 157 client := &http.Client{} 158 req, _ := http.NewRequest(http.MethodPost, m.BaseUrlPrivate+"/private/orders", bytes.NewReader(reqString)) 159 req.Header.Set("Authorization", "Bearer secret-token:"+m.AccessToken) 160 resp, err := client.Do(req) 161 162 if nil != err { 163 return "", err 164 } 165 defer resp.Body.Close() 166 if http.StatusOK != resp.StatusCode { 167 message := fmt.Sprintf("Expected response code %d. Got %d. With request %s", http.StatusOK, resp.StatusCode, reqString) 168 return "", errors.New(message) 169 } 170 err = json.NewDecoder(resp.Body).Decode(&orderResponse) 171 return orderResponse.OrderId, err 172 }