/// <summary> /// Update arbitrary contact properties /// </summary> /// <param name="vid">the unique HubSpot ID of the Contact</param> /// <param name="props">A HubSpotContactProperties object that specifies all the properties to be changed</param> /// <param name="log"></param> /// <param name="isTest"></param> /// <returns></returns> public async Task <HubSpotContactResult> UpdateContactDetailsAsync( string vid, HubSpotContactProperties props, ILogger log, bool isTest) { // Check that the appropriate Hubspot API key was correctly retrieved in the static constructor var activeHapiKey = isTest ? sandbox_hapikey : hapikey; if (string.IsNullOrEmpty(activeHapiKey)) { return(new HubSpotContactResult(HttpStatusCode.InternalServerError, "Hubspot API key not found")); } var url = string.Format($"https://api.hubapi.com/contacts/v1/contact/vid/{vid}/profile?hapikey={activeHapiKey}"); log.LogInformation($"Updating {vid} details"); HttpResponseMessage response = await httpClient.PostAsJsonAsync(url, props); // Check Status Code. if (response.StatusCode == HttpStatusCode.NoContent) { log.LogInformation("Hubspot status update OK"); var retval = new HubSpotContactResult(HttpStatusCode.OK); return(retval); } else { // Some kind of error - return the status code and body to the caller of the function string resultText = await response.Content.ReadAsStringAsync(); log.LogError("Hubspot update failed: {0}: {1}", response.StatusCode, resultText); return(new HubSpotContactResult(response.StatusCode, resultText)); } }
/// <summary> /// Utility method that retrieves a Hubspot contact from the API and extracts the most important parameters into a structure. /// </summary> /// <param name="url">A valid GET url for retrieving a Hubspot contact. It's expected to have the hapikey on the url</param> /// <param name="fetchPreviousValues"></param> /// <returns></returns> private async Task <HubSpotContactResult> RetrieveHubspotContactWithUrl(string url, bool fetchPreviousValues, ILogger log) { // Go get the contact from HubSpot using the supplied url HttpResponseMessage response = await httpClient.GetAsync(url); HttpContent content = response.Content; // Check Status Code. If we got the Contact OK, then raise the appropriate event. if (response.StatusCode == HttpStatusCode.OK) { // All good. Read the string out of the body string resultText = await content.ReadAsStringAsync(); //log.LogInformation(resultText); var canonicalContact = ConvertHubspotJsonToCanonicalContact(resultText, fetchPreviousValues, log); var retval = new HubSpotContactResult(canonicalContact); return(retval); } else if (response.StatusCode == HttpStatusCode.NotFound) { // NotFound error return(new HubSpotContactResult(response.StatusCode, response.ReasonPhrase)); } else { // Some other error string resultText = await content.ReadAsStringAsync(); return(new HubSpotContactResult(response.StatusCode, resultText)); } }
/// <summary> /// Create a contact in Hubspot. /// </summary> /// <param name="email"></param> /// <param name="firstname"></param> /// <param name="lastname"></param> /// <returns>Returns the created contact. If a contact with this email already exists, returns the conflicting contact from HubSpot</returns> /// <see cref="https://developers.hubspot.com/docs/methods/contacts/create_contact"/> public async Task <HubSpotContactResult> CreateHubspotContactAsync( string email, string firstname, string lastname, string preferredName, string primaryPhone, string streetAddress1, string streetAddress2, string city, string state, string postcode, string leadStatus, bool installationRecordExists, ILogger log, bool isTest) { // Check that the appropriate Hubspot API key was correctly retrieved in the static constructor var activeHapiKey = isTest ? sandbox_hapikey : hapikey; // Check that the Hubspot API key was correctly retrieved in the static constructor if (string.IsNullOrEmpty(activeHapiKey)) { return(new HubSpotContactResult(HttpStatusCode.InternalServerError, "Hubspot API key not found")); } if (!(new EmailAddressAttribute().IsValid(email))) { return(new HubSpotContactResult(HttpStatusCode.InternalServerError, "New Contact email address not supplied")); } // To send a Contact to Hubspot via the API we need an object that will serialise into a bunch of {property, value} pairs HubSpotContactProperties newContactProperties = AssembleContactProperties( email, firstname, lastname, preferredName, primaryPhone, streetAddress1, streetAddress2, city, state, postcode, leadStatus, installationRecordExists); if (newContactProperties == null) { return(new HubSpotContactResult(HttpStatusCode.InternalServerError, "unhandled error assembling new contact command")); } var url = string.Format($"https://api.hubapi.com/contacts/v1/contact/?hapikey={activeHapiKey}"); //var dbg = JsonConvert.SerializeObject(newContactProperties); // Need to POST to Hubspot to create a contact log.LogInformation($"Posting to HubSpot to create {firstname} {lastname}"); HttpResponseMessage response = await httpClient.PostAsJsonAsync(url, newContactProperties); HttpContent content = response.Content; // Check Status Code. if (response.StatusCode == HttpStatusCode.OK) { log.LogInformation("Hubspot creation OK"); // All good. Read the string out of the body string resultText = await content.ReadAsStringAsync(); var newContactResponse = ConvertHubspotJsonToCanonicalContact(resultText, fetchPreviousValues: false, log: log); log.LogInformation("New Contact identifier is {0}", newContactResponse.contactId); var retval = new HubSpotContactResult(newContactResponse); return(retval); } else if (response.StatusCode == HttpStatusCode.Conflict) { // The contact already exists. That's OK - we need to direct this contact to a human for review. log.LogWarning($"Contact already exists: {email}"); // Let's read the whole contact back, then. var actualContact = await RetrieveHubspotContactByEmailAddr(email, false, log, isTest); var retval = new HubSpotContactResult(actualContact.Payload); retval.StatusCode = response.StatusCode; return(retval); } else { // Some other error - return the status code and body to the caller of the function string resultText = await content.ReadAsStringAsync(); log.LogError("Hubspot creation failed: {0}: {1}", response.StatusCode, resultText); return(new HubSpotContactResult(response.StatusCode, resultText)); } }
/// <summary> /// Updates a HubSpot contact fields with the given contract status /// </summary> /// <param name="email"></param> /// <param name="status"></param> /// <param name="log"></param> /// <param name="isTest"></param> /// <returns></returns> /// <remarks>Make 'status' an enum that reflects the Sharepoint 'ContractStatus' field</remarks> public async Task <HubSpotContactResult> UpdateContractStatusAsync( string email, string status, ILogger log, bool isTest) { // Check that the appropriate Hubspot API key was correctly retrieved in the static constructor var activeHapiKey = isTest ? sandbox_hapikey : hapikey; if (string.IsNullOrEmpty(activeHapiKey)) { return(new HubSpotContactResult(HttpStatusCode.InternalServerError, "Hubspot API key not found")); } // To update a Contact Property we need a HubSpotContactProperties object that specifies all the properties var props = new HubSpotContactProperties(); var internalLeadStatusValue = string.Empty; // For now, we update a few fields: // 'Lead Status' - used by Brian for reporting. Not sustainable, will fall over when the a contact has two installations // I would like to retire these two states from Lead Status and re-do the reports. // // 'TotalSentContracts' // 'TotalSignedContracts' - these will support aggregates. But aggregates are expense to obtain from Sharepoint. When we // move the Installations table to SQL Server, we will revisit this logic and compute aggregates. // // So there is a temporary hack here right now. We're not computing proper totals. switch (status.ToLower()) { case "sent": props.Add("totalsentcontracts", "1"); internalLeadStatusValue = "CONTRACT_SENT"; break; case "signed": props.Add("totalsignedcontracts", "1"); internalLeadStatusValue = "CONTRACT_SIGNED"; break; case "rejected": props.Add("totalsignedcontracts", "0"); internalLeadStatusValue = "NOT_INTERESTED"; break; default: throw new CrmUpdateHandlerException("Unrecognised contract status: '" + status + "'"); } props.Add("hs_lead_status", internalLeadStatusValue); // We need to resolve the email into a contact id (a 'vid', in HubSpot language) var hubSpotContactResult = await this.RetrieveHubspotContactByEmailAddr(email, false, log, isTest); if (hubSpotContactResult.StatusCode != HttpStatusCode.OK) { log.LogInformation($"Error {hubSpotContactResult.StatusCode} retrieving HubSpot details for '{email}': {hubSpotContactResult.ErrorMessage}"); return(hubSpotContactResult); } // Great, we managed to retrieve the contact via the email address. var vid = hubSpotContactResult.Payload.contactId; // Now just POST it - see https://developers.hubspot.com/docs/methods/contacts/update_contact // Returns a 204 No Content on success var url = string.Format($"https://api.hubapi.com/contacts/v1/contact/vid/{vid}/profile?hapikey={activeHapiKey}"); log.LogInformation($"Updating {vid} contract status to {status}"); HttpResponseMessage response = await httpClient.PostAsJsonAsync(url, props); // Check Status Code. if (response.StatusCode == HttpStatusCode.NoContent) { log.LogInformation("Hubspot status update OK"); var retval = new HubSpotContactResult(HttpStatusCode.OK); return(retval); } else { // Some kind of error - return the status code and body to the caller of the function string resultText = await response.Content.ReadAsStringAsync(); log.LogError("Hubspot update failed: {0}: {1}", response.StatusCode, resultText); return(new HubSpotContactResult(response.StatusCode, string.Format($"Error {response.StatusCode}: '{resultText}'"))); } }