Accept Payments in Bitcoin using Coinbase & Go

In this tutorial, I will step through the steps for accepting payments. We will use Coinbase and therefore will require a Coinbase account. I will publish lessons on using other services but because of the popularity (in my world) of Coinbase, it’s the first service I will demonstrate how to integrate with. I am assuming you have a basic understanding of Go.

Coinbase provides a client SDK for Python, Ruby, and Node.js. There are solutions with and without using the SDK. Coinbase has also introduced the Coinbase Commerce. It has a separate API and is mostly focused around e-commerce tools (Ex. Shopify or WooCommerce) but we can use it to integrate into our own website. Below you can select a language for this tutorial, the level of details and a method of solving it.

If you head over to the Coinbased Developer guide, you will see snippets of code for:

  • Receive Funds
  • Send Funds
  • Request Funds

We will leverage the receive funds functionality but we need to confirm the Bitcoin has arrived as well. There are a list of potential endpoints in the API docs. The general flow will be create an address to receive and wait till the transactions is listed.

From a design perspective, we need to consider two things. This payment can take a bit of time to show up. The user will not send the payment within 30 seconds and therefore your initial API call will timeout. We need to split it up into two functions: Get Address to Receive Funds, Has payment been received. The has payment been received will be called every N seconds till it shows.

You can create the address from making an POST request to the Coinbase API. Here is the code:

// https://developers.coinbase.com/api/v2#create-address
func (o *OrdersRepository) CreateAddress(addressName string) (string, error) {
	accountID := o.getAccountID()
	reqBody, err := json.Marshal(map[string]string{
		"name": addressName,
	})

	if err != nil {
		return "", err
	}

	baseURL := "https://api.coinbase.com"
	requestPath := "/v2/accounts/" + accountID + "/addresses"
	url := baseURL + requestPath
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(reqBody))
	requestTimestamp := strconv.FormatInt(time.Now().UTC().Unix(), 10)
	message := requestTimestamp + "POST" + requestPath + string(reqBody)
	sha := sha256.New
	h := hmac.New(sha, []byte(o.getAPISecret()))
	h.Write([]byte(message))
	signature := fmt.Sprintf("%x", h.Sum(nil))

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("CB-ACCESS-KEY", o.getAPIKey())
	req.Header.Set("CB-ACCESS-SIGN", signature)
	req.Header.Set("CB-ACCESS-TIMESTAMP", requestTimestamp)

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	var response models.CreateAddressResponse
	err = json.Unmarshal(body, &response)
	if err != nil {
		return "", err
	}
	return response.Data.Address, nil
}

You can check the transactions against the address by using a GET request. Here is the code below:

// https://developers.coinbase.com/api/v2#list-address39s-transactions
func (o *OrdersRepository) ListAddressTransactions(addressID string) ([]models.AddressTransaction, error) {
	// Methods are setup in this recursive fashion for paging, but
	// its not actually setup. You want to capture the next page,
	// make another request, and pass in the previous results.
	return o.listAddressTransactionCall(addressID, []models.AddressTransaction{})
}

func (o *OrdersRepository) listAddressTransactionCall(addressID string, addressTransactions []models.AddressTransaction) ([]models.AddressTransaction, error) {
	accountID := o.getAccountID()

	baseURL := "https://api.coinbase.com"
	requestPath := "/v2/accounts/" + accountID + "/addresses/" + addressID + "/transactions"
	url := baseURL + requestPath

	// bytes.NewBuffer(reqBody)
	req, err := http.NewRequest("GET", url, nil)
	requestTimestamp := strconv.FormatInt(time.Now().UTC().Unix(), 10)
	message := requestTimestamp + "GET" + requestPath
	sha := sha256.New
	h := hmac.New(sha, []byte(o.getAPISecret()))
	h.Write([]byte(message))
	signature := fmt.Sprintf("%x", h.Sum(nil))

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("CB-ACCESS-KEY", o.getAPIKey())
	req.Header.Set("CB-ACCESS-SIGN", signature)
	req.Header.Set("CB-ACCESS-TIMESTAMP", requestTimestamp)

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return addressTransactions, err
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return addressTransactions, err
	}
	// fmt.Printf("response :: %+v\n", string(body))

	var response models.ListAddressTransactionsResponse
	err = json.Unmarshal(body, &response)
	if err != nil {
		return addressTransactions, err
	}

	for _, addressTransaction := range response.Data {
		addressTransactions = append(addressTransactions, addressTransaction)
	}

	return addressTransactions, nil
}

HINT: Interested in seeing all steps (code base setup, updating endpoints, etc.)? Click here

If you are building a e-commerce store, I recommend adding a status to the order. You may need to have a CRON job running in the background. The three statuses would be: requested_payments (return the Bitcoin address), payment_received (receive the Bitcoin), and order_submitted (Order ready and submitted as valid). You would only process orders that have the status order_submitted.

Setup API Key

You will need a new API key from your Coinbase account. In the top right corner, select your profile icon. Then select Settings. Under the Settings page, click the API tab. Hit + New API Key.

Screenshot of Github Workflow Running

You will have to select specific permissions for an API key. You can find these permissions on the API docs.

Screenshot of Github Workflow Running

You need to select wallet:addresses:create, and wallet:transactions:read.

Screenshot of Github Workflow Running

There are additional options. One security aspects is whitelisting an IP. You should add your server IP to the whilelist.

Screenshot of Github Workflow Running

Keep track of the client API key and the client API secret.

Finding the Account ID

You will need an account ID that represents your wallet. Select Portfolio, then Bitcoin. The account ID can be found in the URL.

Screenshot of Github Workflow Running

Testing

You can test your code. Using the full code base, I’m using an HTTP client. You can switch to the full code base. You need to create an address.

Screenshot of Github Workflow Running

You should send Bitcoin to your new address. You cannot send Bitcoin to yourself from the same account.

video[/vids/accept-bitcoin-using-coinbase/sent-bitcoin-short.mov]

It will take about 20 minutes to go through. It did for me.

Thanks for reading! More posts to come.

References