Skip to content

Instantly share code, notes, and snippets.

@Tadge-Analytics
Created August 15, 2024 05:08
Show Gist options
  • Save Tadge-Analytics/e0856caba4a28905587278f756f651ec to your computer and use it in GitHub Desktop.
Save Tadge-Analytics/e0856caba4a28905587278f756f651ec to your computer and use it in GitHub Desktop.
# https://developer.xero.com/documentation/guides/oauth2/auth-flow/
# https://developer.xero.com/app/manage/
# credentials presented in this script are no longer valid
# Xero app has been deleted
# there are more secure ways of storing credentials in R...
# I have demonstrated the least secure way, just so that the script is more readible.
library(tidyverse)
library(httr2)
library(glue)
library(xml2)
client_id <- "2B005A763E5445469FB9DC7F344A6C58"
client_secret <- "KaHGhDXoxDnprcD4sqeKTKcU1Dgt4CFKNSgjGfAjzNZgmjMv"
encoded_key_info <- RCurl::base64Encode(paste0(client_id, ":", client_secret))[1]
redirect_uri <- "https://www.tadge-analytics.com.au/"
# this will come in handy later
refresh_token_path <- "refresh_token.rds"
if (!file.exists(refresh_token_path)) {
# generating the access code for the first time
scopes_var <- "openid profile offline_access email accounting.transactions accounting.contacts"
url_to_browse_to <- glue("https://login.xero.com/identity/connect/authorize?response_type=code&client_id={client_id}&redirect_uri={redirect_uri}&scope={scopes_var}&state=123")
browseURL(url_to_browse_to)
code_returned <- "P0pp53-NVqoQuFgJ--iMiuxu1iWhQPuxK-q6splZ1ZA"
request_response <-
request("https://identity.xero.com/connect/token") %>%
req_headers(
Authorization = paste0("Basic ", encoded_key_info)
) %>%
req_body_form(
grant_type = "authorization_code",
code = code_returned,
redirect_uri = redirect_uri
) %>%
req_perform()
access_token <-
request_response %>%
resp_body_json() %>%
pluck("access_token")
refresh_token <-
request_response %>%
resp_body_json() %>%
pluck("refresh_token")
write_rds(refresh_token, refresh_token_path)
}
refresh_token <- read_rds(refresh_token_path)
###################################################################
# use the refresh token... if the refresh token key has already been generated and saved
refresh_the_access_token <- function() {
request_response <-
request("https://identity.xero.com/connect/token") %>%
req_headers(
Authorization = paste0("Basic ", encoded_key_info)
) %>%
req_body_form(
grant_type = "refresh_token",
refresh_token = refresh_token
) %>%
req_perform()
access_token <-
request_response %>%
resp_body_json() %>%
pluck("access_token")
assign("access_token", access_token, envir = .GlobalEnv)
refresh_token <-
request_response %>%
resp_body_json() %>%
pluck("refresh_token")
write_rds(refresh_token, refresh_token_path)
assign("refresh_token", refresh_token, envir = .GlobalEnv)
}
# this is somethign that really needs to be done prior to each call to the api...
# but let's hope we can test the details within the next 12 minutes (before the token expires)
refresh_the_access_token()
###################################################################
# get your tenantID...
# if you need to
if (FALSE) {
request_response <-
request("https://api.xero.com/connections") %>%
req_headers(
`Content-Type` = "application/json",
Authorization = paste0("Bearer ", access_token)
) %>%
req_perform()
tenant_id <-
request_response %>%
resp_body_json() %>%
pluck(1, "tenantId")
}
tenant_id <- "9fcb1f5c-2f3a-494b-8bf6-fd26f3b6b98f"
###################################################################
# get the contact ids that you might want to work with
if (FALSE) {
request_response <-
request("https://api.xero.com/api.xro/2.0/contacts?summaryOnly=True?page=1") %>%
req_headers(
`Content-Type` = "application/json",
`Authorization` = paste0("Bearer ", access_token),
`Xero-tenant-id` = tenantId
) %>%
req_perform()
response_as_parsed_xml <-
request_response %>%
resp_body_xml()
pre_contacts_with_xero_ids <-
tibble(
names = xml_find_all(response_as_parsed_xml, ".//Name") %>% xml_text(),
xero_id = xml_find_all(response_as_parsed_xml, ".//ContactID") %>% xml_text()
)
contacts_with_xero_ids <-
pre_contacts_with_xero_ids %>%
slice(1:3)
}
###################################################################
request_response <-
request("https://api.xero.com/api.xro/2.0/Invoices") %>%
req_headers(
`Content-Type` = "application/json",
`Authorization` = paste0("Bearer ", access_token),
`Xero-tenant-id` = tenantId
) %>%
req_perform()
response_as_parsed_xml <-
request_response %>%
resp_body_xml()
tibble(
InvoiceID = xml_find_all(response_as_parsed_xml, ".//InvoiceID") %>% xml_text(),
Date = xml_find_all(response_as_parsed_xml, ".//Invoice/Date") %>% xml_text(),
Contact = xml_find_all(response_as_parsed_xml, ".//Contact/Name") %>% xml_text()
)
###################################################################
xero_id_to_create_invoice_for <- "2dbb0715-7c4d-4d0b-8bad-5772793a1bec"
data_for_invoice <-
tibble(description = "hours on: 31/05/2024"
, total_hours = 3.2
, hourly_rate_aud = 80)
items_as_a_list_of_lists <-
data_for_invoice %>%
mutate(across(everything(), ~.x %>% as.character())) %>%
rename(Description = description, Quantity = total_hours, UnitAmount = hourly_rate_aud) %>%
group_split(row_number(), .keep = FALSE) %>%
map(as.list)
field_Date <- today() %>% as.character()
field_DueDate <- (today() + weeks(2)) %>% as.character()
req_to_send_with_body <-
request("https://api.xero.com/api.xro/2.0/Invoices") %>%
req_headers(
`Content-Type` = "application/json",
`authorization` = paste0("Bearer ", access_token),
`Xero-tenant-id` = tenantId
) %>%
req_body_json(
list(
Date = field_Date,
DueDate = field_DueDate,
Type = "ACCREC",
Contact = list(ContactID = xero_id_to_create_invoice_for),
LineItems = items_as_a_list_of_lists
)
)
req_to_send_with_body %>%
req_perform()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment