Using Google Contacts API as server to server PHP application

You've got a website for your business and most of your new customers are reaching you via your "Contact us" form page. You're probably manually adding your customers' phone number, email, their preferred product, etc. in your Google Contacts. Would it be nice if your "Contact us" form page has the ability to save a visitor's contact and other details to the database of your account's Google Contacts app automatically? With this kind of application, server to server authentication is needed. Where your application has own Google service account instead of to an individual visitor's Google account. The application calls Google Contacts API on behalf of the Google service account, so your visitors aren't directly involved. Unfortunately, you will need Google's suite of intelligent apps (formerly Google Apps) here, as one of the main step in this article is to delegate domain-wide authority to your application's Google service account.

As of this writing, Google Contacts API don't have support for PHP yet. We will use the Google APIs Client Library for PHP as base code and we will use composer to get it.

Create a Google service account

For updated screenshots and instructions please follow the steps here.

  1. Login to your Google account.

  2. Create a project here.

    Create project

  3. Populate the "Project name" and "Project ID" fields and click "CREATE" button:

    Create project window

  4. Wait until Google finished creating the project, you will be redirected to a new page (like the one shown below) once it is completed. Under "Google Apps API", click "Contacts API" link:

    Google API list

  5. You should be redirected to "Contacts API" page. Click the "ENABLE" button:

    Contacts API enable

  6. Still on "Contacts API" page, click "Go to Credentials" button at the right side:

    Create credetials

  7. You will be redirected to "Credentials" page. Select "Web server (e.g. node.js, Tomcat)" option from "Where will you be calling the API from?" field. Choose "Application data" from "What data will you be accessing?" field. Select "No, I’m not using them" option from "Are you using Google App Engine or Google Compute Engine?" field. Then click "What credentials do I need?" button:

    Create credentials page

  8. Populate the "Service account name" field. Click the "Role" dropdown menu, from the selection choose "Project" then "Editor". Click "Continue" button:

    Create credentials account

  9. You should be receiving a JSON file that contains private key of Google service account you've just created. Click "Ok" button of the popped alert box. And you will be redirected to "Credentials" main page. Click the "Manage service accounts" link:

    Manage service account

  10. On "Service Accounts" page, click the "More actions" icon at the right of your Google service account and select "Edit" from the dropdown menu:

    Manage service account options

  11. A window will pop up. Tick the "Enable G Suite Domain-wide Delegation" checkbox and populate the "Product name for the consent screen" text field:

    Manage service account edit

Delegate domain-wide authority to Google service account

For updated screenshots and instructions please follow the steps here.

  1. Login to your Google's suite or Google Apps with your account that has administrator role here.

  2. Click "Security" from your "Admin Console" page:

    Manage security

  3. You will be redirected to "Security" page, click "Show more" to show the "Advanced settings":

    Manage security page

  4. Still on "Security" page under "Advanced settings", click "Manage API client access" link:

    Manage security advanced settings

  5. You will be redirected to "Manage API client access" page. Open the JSON file that you received from creating your Google service account and copy the value of client_id:

    Client ID

  6. Paste the client_id that you copied to "Client Name" field and populate the "One or More API Scopes" with "https://www.google.com/m8/feeds":

    Manage security client access

    Note: If you want to add more API scopes check https://developers.google.com/identity/protocols/googlescopes.

  7. Click the "Authorize" button:

    Manage security client access saved

Google Contacts API PHP application

  1. Go to your web page root path:

          
    cd /var/www/html
          
        
  2. Create a directory for our Google Contacts application:

          
    mkdir googlecontacts
    cd googlecontacts
          
        
  3. Copy the JSON file that you received from creating your Google service account to /var/www/html/googlecontacts and rename it to "google_auth.json":

  4. Install composer:

          
    curl -sS https://getcomposer.org/installer | php
    mv composer.phar /usr/local/bin/composer
          
        

    Follow this instructions installing composer in Windows.

  5. Execute the following to download the Google APIs Client Library for PHP:

          
    composer require google/apiclient:^2.0
          
        
  6. Create a PHP file named "GoogleServiceAuthApplication.php" and copy the following codes in to it:

          
    <?php
    
    namespace GoogleCustom;
    
    use Google_Client;
    
    class GoogleServiceAuthApplication {
      /**
       * Google client.
       *
       * @var array
       */
      protected $client;
    
      public function __construct($conf) {
        if (isset($conf['google_auth_file'])) {
          putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $conf['google_auth_file']);
        }
        $this->client = new Google_Client();
        $this->client->useApplicationDefaultCredentials();
        $this->client->setScopes($conf['gcontacts_scopes']);
        $this->client->setSubject($conf['gcontacts_account']);
      }
    
      /**
       * @return array
       */
      public function getClient() {
        return $this->client;
      }
    }
          
        

    The above "GoogleServiceAuthApplication" class is responsible for authenting Google service account.

  7. Lets create PHP class that will query, create, update and delete Google Contacts entry. Create a PHP file named "GoogleContacts.php" and copy the following codes in to it:

          
    <?php
    
    namespace GoogleCustom;
    
    include_once 'GoogleServiceAuthApplication.php';
    
    
    class GoogleContacts {
      /**
       * Request object.
       *
       * @var \GuzzleHttp\ClientInterface
       */
      protected $httpClient;
    
      protected $baseUrl;
    
      protected $etag;
    
      protected $userId;
    
      protected $xmlString;
    
      protected $xmlData;
    
      protected $conf;
    
      public function __construct($conf) {
        $this->conf = $conf;
        $this->baseUrl = $conf['gcontacts_base_url'];
        $auth = new GoogleServiceAuthApplication($conf);
        $client = $auth->getClient();
        $this->httpClient = $client->authorize();
      }
    
      /**
       * Create new Google Contacts entry
       * 
       * @param array $data
       *   Contains flat array of new details to update a Google Contacts entry.
       * @return bool
       *   Flat array Google Contacts entry details if successful and FALSE if failed.
       */
      public function create($data) {
        extract($data);
        $xmlString = <<<XML
      <atom:entry xmlns:atom="http://www.w3.org/2005/Atom" xmlns:gd="http://schemas.google.com/g/2005">
        <atom:category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#contact"/>
        <gd:name>
          <gd:fullName>$fullName</gd:fullName>
          <gd:namePrefix>$namePrefix</gd:namePrefix>
          <gd:givenName>$givenName</gd:givenName>
          <gd:additionalName>$additionalName</gd:additionalName>
          <gd:familyName>$familyName</gd:familyName>
          <gd:nameSuffix>$nameSuffix</gd:nameSuffix>
        </gd:name>
        <gd:birthday when="$birthday"/>
        <atom:content type="text">$content</atom:content>
        <gd:email rel="http://schemas.google.com/g/2005#home" address="$emailHome" />
        <gd:email rel="http://schemas.google.com/g/2005#work" primary="true" address="$emailWork" />
        <gd:phoneNumber rel="http://schemas.google.com/g/2005#home">$phoneNumberHome</gd:phoneNumber>
        <gd:phoneNumber rel="http://schemas.google.com/g/2005#mobile">$phoneNumberMobile</gd:phoneNumber>
        <gd:phoneNumber rel="http://schemas.google.com/g/2005#work">$phoneNumberWork</gd:phoneNumber>
        <gd:structuredPostalAddress rel="http://schemas.google.com/g/2005#home" primary="true">
          <gd:formattedAddress>$formattedAddress</gd:formattedAddress>
          <gd:street>$street</gd:street>
          <gd:pobox>$pobox</gd:pobox>
          <gd:neighborhood>$neighborhood</gd:neighborhood>
          <gd:city>$city</gd:city>
          <gd:region>$region</gd:region>
          <gd:postcode>$postcode</gd:postcode>
          <gd:country>$country</gd:country>
        </gd:structuredPostalAddress>
        <gd:userDefinedField key="Most convenient time to call" value="$mostConvenientTimeToCall"/>
        <gd:userDefinedField key="Preferred product" value="$preferredProduct"/>
        <gd:groupMembershipInfo deleted="false" href="$group"/>
      </atom:entry>
    XML;
        $options = [
          'headers' => [
            'Content-Type' => 'application/atom+xml',
            'GData-Version' => '3.0',
          ],
          'body' => $xmlString,
        ];
        $response = $this->httpClient->post($this->baseUrl, $options);
        if ($response->getReasonPhrase() == 'Created' && $response->getStatusCode() == 201) {
          // Successful
          $xmlString = $response->getBody()->getContents();
          $data = new \SimpleXMLElement($xmlString);
          $this->xmlString = $xmlString;
          $this->xmlData = $data;
          return $this->flatData($data);
        }
        else {
          // Failed
          echo $response->getBody()->getContents();
          return FALSE;
        }
      }
    
      /**
       * Update a Google Contacts entry
       * 
       * @param sting $query
       *   Fulltext query on contacts data fields.
       * @param array $data
       *   Contains flat array of new details to update a Google Contacts entry.
       * @return mixed
       *   Flat array Google Contacts entry details if successful and FALSE if failed.
       */
      public function update($query, $data) {
        $this->query($query);
        if (!empty($data['content'])) {
          $this->xmlData->content = $data['content'];
        }
        if (!empty($data['fullName'])) {
          $this->xmlData->children('gd', TRUE)->name->children('gd', TRUE)->fullName = $data['fullName'];
        }
        if (!empty($data['namePrefix'])) {
          $this->xmlData->children('gd', TRUE)->name->children('gd', TRUE)->namePrefix = $data['namePrefix'];
        }
        if (!empty($data['givenName'])) {
          $this->xmlData->children('gd', TRUE)->name->children('gd', TRUE)->givenName = $data['givenName'];
        }
        if (!empty($data['additionalName'])) {
          $this->xmlData->children('gd', TRUE)->name->children('gd', TRUE)->additionalName = $data['additionalName'];
        }
        if (!empty($data['familyName'])) {
          $this->xmlData->children('gd', TRUE)->name->children('gd', TRUE)->familyName = $data['familyName'];
        }
        if (!empty($data['nameSuffix'])) {
          $this->xmlData->children('gd', TRUE)->name->children('gd', TRUE)->nameSuffix = $data['nameSuffix'];
        }
        if (!empty($data['birthday'])) {
          $this->xmlData->children('gContact', TRUE)->birthday->attributes()->when = $data['birthday'];
        }
        $index = 0;
        foreach ($this->xmlData->children('gd', TRUE)->email as $email) {
          $rel = (string) $email->attributes()->rel;
          if (!empty($data['emailWork']) && strstr($rel, 'work') !== FALSE) {
            $this->xmlData->children('gd', TRUE)->email[$index]->attributes()->address = $data['emailWork'];
          }
          elseif (!empty($data['emailHome']) && strstr($rel, 'home') !== FALSE) {
            $this->xmlData->children('gd', TRUE)->email[$index]->attributes()->address = $data['emailHome'];
          }
          ++$index;
        }
        $index = 0;
        foreach ($this->xmlData->children('gd', TRUE)->phoneNumber as $phoneNumber) {
          $rel = (string) $phoneNumber->attributes()->rel;
          if (!empty($data['phoneNumberMobile']) && strstr($rel, 'mobile') !== FALSE) {
            $this->xmlData->children('gd', TRUE)->phoneNumber[$index] = $data['phoneNumberMobile'];
          }
          elseif (!empty($data['phoneNumberWork']) && strstr($rel, 'work') !== FALSE) {
            $this->xmlData->children('gd', TRUE)->phoneNumber[$index] = $data['phoneNumberWork'];
          }
          elseif (!empty($data['phoneNumberHome']) && strstr($rel, 'home') !== FALSE) {
            $this->xmlData->children('gd', TRUE)->phoneNumber[$index] = $data['phoneNumberHome'];
          }
          ++$index;
        }
        if (!empty($data['formattedAddress'])) {
          $this->xmlData->children('gd', TRUE)->structuredPostalAddress->children('gd', TRUE)->formattedAddress = $data['formattedAddress'];    
        }
        if (!empty($data['street'])) {
          $this->xmlData->children('gd', TRUE)->structuredPostalAddress->children('gd', TRUE)->street = $data['street'];    
        }
        if (!empty($data['pobox'])) {
          $this->xmlData->children('gd', TRUE)->structuredPostalAddress->children('gd', TRUE)->pobox = $data['pobox'];    
        }
        if (!empty($data['neighborhood'])) {
          $this->xmlData->children('gd', TRUE)->structuredPostalAddress->children('gd', TRUE)->neighborhood = $data['neighborhood'];    
        }
        if (!empty($data['city'])) {
          $this->xmlData->children('gd', TRUE)->structuredPostalAddress->children('gd', TRUE)->city = $data['city'];    
        }
        if (!empty($data['region'])) {
          $this->xmlData->children('gd', TRUE)->structuredPostalAddress->children('gd', TRUE)->region = $data['region'];    
        }
        if (!empty($data['postcode'])) {
          $this->xmlData->children('gd', TRUE)->structuredPostalAddress->children('gd', TRUE)->postcode = $data['postcode'];    
        }
        if (!empty($data['country'])) {
          $this->xmlData->children('gd', TRUE)->structuredPostalAddress->children('gd', TRUE)->country = $data['country'];
        }
        $index = 0;
        foreach ($this->xmlData->children('gContact', TRUE)->userDefinedField as $userDefinedField) {
          $key = (string) $userDefinedField->attributes()->key;
          if (!empty($data['preferredProduct']) && $key == 'Preferred product') {
            $this->xmlData->children('gContact', TRUE)->userDefinedField[$index]->attributes()->value = $data['preferredProduct'];
          }
          elseif (!empty($data['mostConvenientTimeToCall']) && $key == 'Most convenient time to call') {
            $this->xmlData->children('gContact', TRUE)->userDefinedField[$index]->attributes()->value = $data['mostConvenientTimeToCall'];
          }
          ++$index;
        }
        $this->xmlData->children('gContact', TRUE)->groupMembershipInfo->attributes()->href = $data['group'];
        $options = [
          'headers' => [
            'If-Match' => $this->etag,
            'Content-Type' => 'application/atom+xml',
            'GData-Version' => '3.0',
          ],
          'body' => $this->xmlData->asXML(),
        ];
        $response = $this->httpClient->put("$this->baseUrl/$this->userId", $options);
        if ($response->getReasonPhrase() == 'OK' && $response->getStatusCode() == 200) {
          // Successful
          $xmlString = $response->getBody()->getContents();
          $data = new \SimpleXMLElement($xmlString);
          $this->xmlString = $xmlString;
          $this->xmlData = $data;
          return $this->flatData($data);
        }
        else {
          // Failed
          echo $response->getBody()->getContents();
          return FALSE;
        }
      }
    
      protected function requestContact() {
        $targetUrl = "$this->baseUrl/$this->userId?v=3.0";
        $response  = $this->httpClient->get($targetUrl);
        $xmlString = $response->getBody()->getContents();
        $data = new \SimpleXMLElement($xmlString);
        $this->etag = (string) $data->attributes('gd', TRUE)->etag;
        $this->xmlData = $data;
        $this->xmlString = $xmlString;
        return $this->flatData($data);
      }
    
      /**
       * Query Google Contacts
       * 
       * @param sting $query
       *   Fulltext query on contacts data fields.
       * @return mixed
       *   Flat array Google Contacts entry details if successful and FALSE if failed.
       */
      public function query($query) {
        if (!empty($query)) {
          $response   = $this->httpClient->get("$this->baseUrl?v=3.0&q=$query");
          $xmlString  = $response->getBody()->getContents();
          if ($response->getReasonPhrase() == 'OK' && $response->getStatusCode() == 200) {
            $data = new \SimpleXMLElement($xmlString);
            if (isset($data->entry[0]->id)) {
              $this->userId = basename((string) $data->entry[0]->id);
              return $this->requestContact();
            }
          }
        }
        return FALSE;
      }
    
      /**
       * Delete a Google Contacts entry
       * 
       * @param sting $query
       *   Fulltext query on contacts data fields.
       * @return mixed
       *   TRUE if successful and FALSE if failed.
       */
      public function delete($query) {
        if (!empty($query)) {
          $this->query($query);
        }
        if (!empty($this->etag)) {
          $options = [
            'headers' => [
              'If-Match' => $this->etag,
            ],
          ];
          $response =  $this->httpClient->delete("$this->baseUrl/$this->userId", $options);
          if ($response->getReasonPhrase() == 'Precondition Failed' && $response->getStatusCode() == 412) {
            $options = [
              'headers' => [
                'If-Match' => $this->etag,
                'X-HTTP-Method-Override' => 'DELETE',
              ],
            ];
            $response =  $this->httpClient->post("$this->baseUrl/$this->userId", $options);
          }
          if ($response->getReasonPhrase() == 'Precondition Failed' && $response->getStatusCode() == 412) {
            $options = [
              'headers' => [
                'If-Match' => '*',
                'X-HTTP-Method-Override' => 'DELETE',
              ],
            ];
            $response =  $this->httpClient->post("$this->baseUrl/$this->userId", $options);
          }
          if ($response->getReasonPhrase() == 'OK' && $response->getStatusCode() == 200) {
            return TRUE;
          }
        }
        return FALSE;
      }
    
      protected function flatData($data) {
        $flatData = [];
        $flatData['userId'] = basename((string) $data->id);
        $flatData['etag'] = (string) $data->attributes('gd', TRUE)->etag;
        // Convert data to flat array
        if (isset($data->content)) {
          $flatData['content'] = (string) $data->content;
        }
        if (isset($data->children('gd', TRUE)->name->children('gd', TRUE)->fullName)) {
          $flatData['fullName'] = (string) $data->children('gd', TRUE)->name->children('gd', TRUE)->fullName;
        }
        if (isset($data->children('gd', TRUE)->name->children('gd', TRUE)->namePrefix)) {
          $flatData['namePrefix'] = (string) $data->children('gd', TRUE)->name->children('gd', TRUE)->namePrefix;
        }
        if (isset($data->children('gd', TRUE)->name->children('gd', TRUE)->givenName)) {
          $flatData['givenName'] = (string) $data->children('gd', TRUE)->name->children('gd', TRUE)->givenName;
        }
        if (isset($data->children('gd', TRUE)->name->children('gd', TRUE)->additionalName)) {
          $flatData['additionalName'] = (string) $data->children('gd', TRUE)->name->children('gd', TRUE)->additionalName;
        }
        if (isset($data->children('gd', TRUE)->name->children('gd', TRUE)->familyName)) {
          $flatData['familyName'] = (string) $data->children('gd', TRUE)->name->children('gd', TRUE)->familyName;
        }
        if (isset($data->children('gd', TRUE)->name->children('gd', TRUE)->nameSuffix)) {
          $flatData['nameSuffix'] = (string) $data->children('gd', TRUE)->name->children('gd', TRUE)->nameSuffix;
        }
        if (isset($data->children('gContact', TRUE)->birthday)) {
          $flatData['birthday'] = (string) $data->children('gContact', TRUE)->birthday;
        }
        if (isset($data->children('gd', TRUE)->email)) {
          $index = 0;
          foreach ($data->children('gd', TRUE)->email as $email) {
            $rel = (string) $email->attributes()->rel;
            if (strstr($rel, 'work') !== FALSE && isset($data->children('gd', TRUE)->email[$index]->attributes()->address)) {
              $flatData['emailWork'] = (string) $data->children('gd', TRUE)->email[$index]->attributes()->address;
            }
            elseif (strstr($rel, 'home') !== FALSE && isset($data->children('gd', TRUE)->email[$index]->attributes()->address)) {
              $flatData['emailHome'] = (string) $data->children('gd', TRUE)->email[$index]->attributes()->address;
            }
            ++$index;
          }
        }
        if (isset($data->children('gd', TRUE)->phoneNumber)) {
          $index = 0;
          foreach ($data->children('gd', TRUE)->phoneNumber as $phoneNumber) {
            $rel = (string) $phoneNumber->attributes()->rel;
            if (strstr($rel, 'mobile') !== FALSE && isset($data->children('gd', TRUE)->phoneNumber[$index])) {
              $flatData['phoneNumberMobile'] = (string) $data->children('gd', TRUE)->phoneNumber[$index];
            }
            elseif (strstr($rel, 'work') !== FALSE && isset($data->children('gd', TRUE)->phoneNumber[$index])) {
              $flatData['phoneNumberWork'] = (string) $data->children('gd', TRUE)->phoneNumber[$index];
            }
            elseif (strstr($rel, 'home') !== FALSE && isset($data->children('gd', TRUE)->phoneNumber[$index])) {
              $flatData['phoneNumberHome'] = (string) $data->children('gd', TRUE)->phoneNumber[$index];
            }
            ++$index;
          }
        }
        if (isset($data->children('gd', TRUE)->structuredPostalAddress->children('gd', TRUE)->formattedAddress)) {
          $flatData['formattedAddress'] = (string) $data->children('gd', TRUE)->structuredPostalAddress->children('gd', TRUE)->formattedAddress;
        }
        if (isset($data->children('gd', TRUE)->structuredPostalAddress->children('gd', TRUE)->street)) {
          $flatData['street'] = (string) $data->children('gd', TRUE)->structuredPostalAddress->children('gd', TRUE)->street;
        }
        if (isset($data->children('gd', TRUE)->structuredPostalAddress->children('gd', TRUE)->pobox)) {
          $flatData['pobox'] = (string) $data->children('gd', TRUE)->structuredPostalAddress->children('gd', TRUE)->pobox;
        }
        if (isset($data->children('gd', TRUE)->structuredPostalAddress->children('gd', TRUE)->neighborhood)) {
          $flatData['neighborhood'] = (string) $data->children('gd', TRUE)->structuredPostalAddress->children('gd', TRUE)->neighborhood;
        }
        if (isset($data->children('gd', TRUE)->structuredPostalAddress->children('gd', TRUE)->city)) {
          $flatData['city'] = (string) $data->children('gd', TRUE)->structuredPostalAddress->children('gd', TRUE)->city;
        }
        if (isset($data->children('gd', TRUE)->structuredPostalAddress->children('gd', TRUE)->region)) {
          $flatData['region'] = (string) $data->children('gd', TRUE)->structuredPostalAddress->children('gd', TRUE)->region;
        }
        if (isset($data->children('gd', TRUE)->structuredPostalAddress->children('gd', TRUE)->postcode)) {
          $flatData['postcode'] = (string) $data->children('gd', TRUE)->structuredPostalAddress->children('gd', TRUE)->postcode;
        }
        if (isset($data->children('gd', TRUE)->structuredPostalAddress->children('gd', TRUE)->country)) {
          $flatData['country'] = (string) $data->children('gd', TRUE)->structuredPostalAddress->children('gd', TRUE)->country;
        }
        if (isset($data->children('gContact', TRUE)->userDefinedField)) {
          $index = 0;
          foreach ($data->children('gContact', TRUE)->userDefinedField as $userDefinedField) {
            $key = (string) $userDefinedField->attributes()->key;
            if ($key == 'Preferred product' && isset($data->children('gContact', TRUE)->userDefinedField[$index]->attributes()->value)) {
              $flatData['preferredProduct'] = (string) $data->children('gContact', TRUE)->userDefinedField[$index]->attributes()->value;
            }
            elseif ($key == 'Time Zone') {
              $flatData['timeZone'] = (string) $data->children('gContact', TRUE)->userDefinedField[$index]->attributes()->value;
            }
            elseif ($key == 'Most convenient time to call' && isset($data->children('gContact', TRUE)->userDefinedField[$index]->attributes()->value)) {
              $flatData['mostConvenientTimeToCall'] = (string) $data->children('gContact', TRUE)->userDefinedField[$index]->attributes()->value;
            }
            ++$index;
          }
        }
        if (isset($data->children('gContact', TRUE)->groupMembershipInfo->attributes()->href)) {
          $flatData['group'] = (string) $data->children('gContact', TRUE)->groupMembershipInfo->attributes()->href;
        }
        $flatData['timestamp'] = time();
        return $flatData;
      }
    }
          
        
  8. The following script is a simple application of Google Contacts API that demonstrate create, update, query and delete Google Contacts entry. It contains a working HTML form asking information based on basic Google Contacts fields: Name (Prefix, First, Middle, Last and Suffix), Phone number (Home, Mobile and Work), Email (Home and Work), Address (Street, PO Box, Neighborhood, City, State/Province, ZIP/Postal, Country/Region), Birthday and also includes two Google custom fields "Most convenient time to call" and "Preferred product". Create a PHP file named "app.php" and copy the following codes in to it (Note: the variable $googleSubjectAccount in the script below should have a value of your Google email from Google Apps, this email will receive and store the contact details of your visitor in its Google Contacts database):

          
    <?php
    if (!empty($_POST)) {
      include_once 'vendor/autoload.php';
      include_once 'GoogleContacts.php';
      
      $googleSubjectAccount = '';
      $defaultGroup = "http://www.google.com/m8/feeds/groups/$googleSubjectAccount/base/6";
      $conf = [
        'google_auth_file' => 'google_auth.json',
        'gcontacts_account' => $googleSubjectAccount,
        'gcontacts_base_url' => 'https://www.google.com/m8/feeds/contacts/default/full',
        'gcontacts_scopes' => [
          'https://www.googleapis.com/auth/contacts',
          'https://www.google.com/m8/feeds',
        ],
      ];
      $gcontacts = new \GoogleCustom\GoogleContacts($conf);
      $data = $_POST;
      if (empty($data['fullName'])) {
        $data['fullName'] = $data['namePrefix'] . ' ' . $data['givenName'] . ' ' . $data['additionalName'] . ' ' . $data['familyName'] . ' ' . $data['nameSuffix'];
      }
      $street = empty($data['street']) ? '' : $data['street'] . ', ';
      $neighborhood = empty($data['neighborhood']) ? '' : $data['neighborhood'] . ', ';
      $city = empty($data['city']) ? '' : $data['city'] . ', ';
      $postcode = empty($data['postcode']) ? '' : $data['postcode'] . ' ';
      $region = empty($data['region']) ? '' : $data['region'] . ', ';
      $data['formattedAddress'] = $street . $neighborhood . $city . $postcode . $region . $data['country'];
      $data['birthday'] = date('Y-m-d', strtotime($data['birthday']));
      $data['group'] = $defaultGroup;  
      // Escape the values to XML safe
      foreach ($data as $param => $value) {
        $data[$param] = htmlspecialchars($value, ENT_XML1, 'UTF-8');
      }
      if (isset($_POST['create'])) {
        $response  = $gcontacts->create($data);
        if (!$response) {
          echo '<p style="color: red;">Creation of Google Contacts entry failed.</p>';
        }
        else {
          echo '<p style="color: green;">Creation of Google Contacts entry successful.</p>';
        }
      }
      elseif (isset($_POST['query'])) {
        $response  = $gcontacts->query($_POST['term']);
        if (!$response) {
          echo '<p style="color: red;">Query of Google Contacts entry failed.</p>';
        }
        else {
          echo '<p style="color: green;">Query of Google Contacts entry successful. Below are the data returned from Google:</p>';
          print_r($response);
        }
      }
      elseif (isset($_POST['update'])) {
        $response  = $gcontacts->update($_POST['term'], $data);
        if (!$response) {
          echo '<p style="color: red;">Update of Google Contacts entry failed.</p>';
        }
        else {
          echo '<p style="color: green;">Update of Google Contacts entry successful. Below are the data returned from Google:</p>';
          print_r($response);
        }
      }
      elseif (isset($_POST['delete'])) {
        $response  = $gcontacts->delete($_POST['term']);
        if (!$response) {
          echo '<p style="color: red;">Deletion of Google Contacts entry failed.</p>';
        }
        else {
          echo '<p style="color: green;">Deletion of Google Contacts entry successful.</p>';
        }
      }
    }
    ?>
    <!DOCTYPE html>
    <html>
      <body>
        <form action="app.php" method="post">
          <h3>Name:</h3>
          <div>
            <label for="namePrefix">Prefix:</label>
            <input type="text" name="namePrefix" value="Mr.">
          </div>
          <div>
            <label for="givenName">First:</label>
            <input type="text" name="givenName" value="John">
          </div>
          <div>
            <label for="additionalName">Middle:</label>
            <input type="text" name="additionalName" value="D.">
          </div>
          <div>
            <label for="familyName">Last:</label>
            <input type="text" name="familyName" value="Doe">
          </div>
          <div>
            <label for="nameSuffix">Suffix:</label>
            <input type="text" name="nameSuffix" value="Sr.">
          </div>
          <h3>Phone number:</h3>
          <div>
            <label for="phoneNumberHome">Home:</label>
            <input type="text" name="phoneNumberHome" value="+6328888888">
          </div>
          <div>
            <label for="phoneNumberMobile">Mobile:</label>
            <input type="text" name="phoneNumberMobile" value="+639198888888">
          </div>
          <div>
            <label for="phoneNumberWork">Work:</label>
            <input type="text" name="phoneNumberWork" value="+632777777">
          </div>
          <h3>Email:</h3>
          <div>
            <label for="emailHome">Home:</label>
            <input type="text" name="emailHome" value="[email protected]">
          </div>
          <div>
            <label for="emailWork">Work:
            <input type="text" name="emailWork" value="[email protected]"></label>
          </div>
          <h3>Address:</h3>
          <div>
            <label for="street">Street:</label>
            <textarea rows="4" cols="50" name="street" style="display: block;">#101 Arca Drive</textarea>
          </div>
          <div>
            <label for="pobox">PO Box:</label>
            <input type="text" name="pobox" value="">
          </div>
          <div>
            <label for="neighborhood">Neighborhood:</label>
            <input type="text" name="neighborhood" value="">
          </div>
          <div>
            <label for="city">City:</label>
            <input type="text" name="city" value="Manila">
          </div>
          <div>
            <label for="region">State/Province:</label>
            <input type="text" name="region" value="NCR">
          </div>
          <div>
            <label for="postcode">ZIP/Postal Code:</label>
            <input type="text" name="postcode" value="1900">
          </div>
          <div>
            <label for="country">Country/Region:</label>
            <input type="text" name="country" value="Philippines">
          </div>
          <h3>Other:</h3>
          <div>
            <label for="birthday">Birthday:</label>
            <input type="text" name="birthday" value="January 1, 1970">
          </div>
          <div>
            <label for="mostConvenientTimeToCall">Most convenient time to call:</label>
            <input type="text" name="mostConvenientTimeToCall" value="after lunch">
          </div>
          <div>
            <label for="preferredProduct">Preferred product:</label>
            <input type="text" name="preferredProduct" value="Drupal 8 Module development ebook">
          </div>
          <h3>Note:</h3>
          <div>
             <textarea rows="4" cols="50" name="content">
    Hi,
    
    I would like to request more details about Drupal 8 Module development ebook.
    
    Thanks.
             </textarea>
          </div>
          <input type="submit" name="create" value="Create">
          <div>
            <label for="term">Enter term(s) fulltext query on any contacts data fields separated by space (please populate this field for Update, Query and Delete):</label><br />
            <input type="text" name="term" value="[email protected] Doe">
          </div>
          <input type="submit" name="query" value="Query"><br />
          <input type="submit" name="update" value="Update"><br />
          <input type="submit" name="delete" value="Delete"><br />
        </form>
      </body>
    </html>
          
        
  9. Now access the Google Contacts PHP script application to test it. The script should render:

    Google Contacts sample application

Updates:

1/28/2019

Somehow, Google invalidate the use of "gContact" node name in Contact creation. If your Contact creation XML protocol format contains "gContact" node name, it returns "Invalid XML Document." and Google prevents the creation of Contact request. I did some experiments and found workaround solution to this issue. In our create() Contact function under our "GoogleContacts.php" PHP file, I replaced all the "gContact" node name with "gd" node name:

Our old create() Contact function:
  
  /**
   * Create new Google Contacts entry
   * 
   * @param array $data
   *   Contains flat array of new details to update a Google Contacts entry.
   * @return bool
   *   Flat array Google Contacts entry details if successful and FALSE if failed.
   */
  public function create($data) {
    extract($data);
    $xmlString = <<<XML
  <atom:entry xmlns:atom="http://www.w3.org/2005/Atom" xmlns:gd="http://schemas.google.com/g/2005">
    <atom:category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#contact"/>
    <gd:name>
      <gd:fullName>$fullName</gd:fullName>
      <gd:namePrefix>$namePrefix</gd:namePrefix>
      <gd:givenName>$givenName</gd:givenName>
      <gd:additionalName>$additionalName</gd:additionalName>
      <gd:familyName>$familyName</gd:familyName>
      <gd:nameSuffix>$nameSuffix</gd:nameSuffix>
    </gd:name>
    <gContact:birthday when="$birthday"/>
    <atom:content type="text">$content</atom:content>
    <gd:email rel="http://schemas.google.com/g/2005#home" address="$emailHome" />
    <gd:email rel="http://schemas.google.com/g/2005#work" primary="true" address="$emailWork" />
    <gd:phoneNumber rel="http://schemas.google.com/g/2005#home">$phoneNumberHome</gd:phoneNumber>
    <gd:phoneNumber rel="http://schemas.google.com/g/2005#mobile">$phoneNumberMobile</gd:phoneNumber>
    <gd:phoneNumber rel="http://schemas.google.com/g/2005#work">$phoneNumberWork</gd:phoneNumber>
    <gd:structuredPostalAddress rel="http://schemas.google.com/g/2005#home" primary="true">
      <gd:formattedAddress>$formattedAddress</gd:formattedAddress>
      <gd:street>$street</gd:street>
      <gd:pobox>$pobox</gd:pobox>
      <gd:neighborhood>$neighborhood</gd:neighborhood>
      <gd:city>$city</gd:city>
      <gd:region>$region</gd:region>
      <gd:postcode>$postcode</gd:postcode>
      <gd:country>$country</gd:country>
    </gd:structuredPostalAddress>
    <gContact:userDefinedField key="Most convenient time to call" value="$mostConvenientTimeToCall"/>
    <gContact:userDefinedField key="Preferred product" value="$preferredProduct"/>
    <gContact:groupMembershipInfo deleted="false" href="$group"/>
  </atom:entry>
XML;
    $options = [
      'headers' => [
        'Content-Type' => 'application/atom+xml',
        'GData-Version' => '3.0',
      ],
      'body' => $xmlString,
    ];
    $response = $this->httpClient->post($this->baseUrl, $options);
    if ($response->getReasonPhrase() == 'Created' && $response->getStatusCode() == 201) {
      // Successful
      $xmlString = $response->getBody()->getContents();
      $data = new \SimpleXMLElement($xmlString);
      $this->xmlString = $xmlString;
      $this->xmlData = $data;
      return $this->flatData($data);
    }
    else {
      // Failed
      echo $response->getBody()->getContents();
      return FALSE;
    }
  }
  
Our new corrected create() Contact function:
  
  /**
   * Create new Google Contacts entry
   * 
   * @param array $data
   *   Contains flat array of new details to update a Google Contacts entry.
   * @return bool
   *   Flat array Google Contacts entry details if successful and FALSE if failed.
   */
  public function create($data) {
    extract($data);
    $xmlString = <<<XML
  <atom:entry xmlns:atom="http://www.w3.org/2005/Atom" xmlns:gd="http://schemas.google.com/g/2005">
    <atom:category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#contact"/>
    <gd:name>
      <gd:fullName>$fullName</gd:fullName>
      <gd:namePrefix>$namePrefix</gd:namePrefix>
      <gd:givenName>$givenName</gd:givenName>
      <gd:additionalName>$additionalName</gd:additionalName>
      <gd:familyName>$familyName</gd:familyName>
      <gd:nameSuffix>$nameSuffix</gd:nameSuffix>
    </gd:name>
    <gd:birthday when="$birthday"/>
    <atom:content type="text">$content</atom:content>
    <gd:email rel="http://schemas.google.com/g/2005#home" address="$emailHome" />
    <gd:email rel="http://schemas.google.com/g/2005#work" primary="true" address="$emailWork" />
    <gd:phoneNumber rel="http://schemas.google.com/g/2005#home">$phoneNumberHome</gd:phoneNumber>
    <gd:phoneNumber rel="http://schemas.google.com/g/2005#mobile">$phoneNumberMobile</gd:phoneNumber>
    <gd:phoneNumber rel="http://schemas.google.com/g/2005#work">$phoneNumberWork</gd:phoneNumber>
    <gd:structuredPostalAddress rel="http://schemas.google.com/g/2005#home" primary="true">
      <gd:formattedAddress>$formattedAddress</gd:formattedAddress>
      <gd:street>$street</gd:street>
      <gd:pobox>$pobox</gd:pobox>
      <gd:neighborhood>$neighborhood</gd:neighborhood>
      <gd:city>$city</gd:city>
      <gd:region>$region</gd:region>
      <gd:postcode>$postcode</gd:postcode>
      <gd:country>$country</gd:country>
    </gd:structuredPostalAddress>
    <gd:userDefinedField key="Most convenient time to call" value="$mostConvenientTimeToCall"/>
    <gd:userDefinedField key="Preferred product" value="$preferredProduct"/>
    <gd:groupMembershipInfo deleted="false" href="$group"/>
  </atom:entry>
XML;
    $options = [
      'headers' => [
        'Content-Type' => 'application/atom+xml',
        'GData-Version' => '3.0',
      ],
      'body' => $xmlString,
    ];
    $response = $this->httpClient->post($this->baseUrl, $options);
    if ($response->getReasonPhrase() == 'Created' && $response->getStatusCode() == 201) {
      // Successful
      $xmlString = $response->getBody()->getContents();
      $data = new \SimpleXMLElement($xmlString);
      $this->xmlString = $xmlString;
      $this->xmlData = $data;
      return $this->flatData($data);
    }
    else {
      // Failed
      echo $response->getBody()->getContents();
      return FALSE;
    }
  }
  

Comments

Best tutorial I've seen in a few weeks struggling with Contacts API and Service Accounts! Congrats!
Unfortunately it won't help me as I'm using Python.

Have you successfully updated Suffix and Prefix in Name element?
My batch update silently removes those attributes.

I just tested the codes I shared above. Yes, it successfully updated Suffix and Prefix in Name element. I suggest to try the codes I shared above then modify it little by little.

HP Parse error: syntax error, unexpected '<' in /var/www/html/googlecontacts/GoogleContacts.php on line 47

I'm sorry, there should be at line 47:

  
$xmlString = <<<XML
  

Actually, there is already <<<XML in my original post but my WYSIWYG interpret it as a HTML tag so it does not display correctly.

Thank you for reporting it.

Google wrote: mistake client (4xx). How I can find mistakes? G-Sute should be paied?

Anton, you will need G Suite account and this is not free (https://gsuite.google.com/pricing.html).

Anton, the response messages you received is came from Google server, please read the Google Contacts API documentation (https://developers.google.com/google-apps/contacts/v3/) to learn more about the response messages you received.

Thanks for the code. I tried to do this and followed all the steps correctly.
$googleSubjectAccount = '[email protected]';
$defaultGroup = "http://www.google.com/m8/feeds/groups/$googleSubjectAccount/base/6";
$conf = [
'google_auth_file' => 'google_auth.json',
'gcontacts_account' => $googleSubjectAccount,
'gcontacts_base_url' => 'https://www.google.com/m8/feeds/contacts/example.com/full',
'gcontacts_scopes' => [
'https://www.googleapis.com/auth/contacts',
'https://www.google.com/m8/feeds',
],

I think this is where I go wrong.

{ ["reasonPhrase":"GuzzleHttp\Psr7\Response":private]=> string(11) "Bad Request" ["statusCode":"GuzzleHttp\Psr7\Response":private]=> int(400).......

This is the error that I'm getting when I print the $response in the create method in GoogleContacts.php
I've a G-Suite account and enabled the domain wide delegation access to the client id.

Could you please help me understand why I'm not able to create a new contact here. ?

Thanks

I've updated the Guzzlehttp to 6.2.2 and still I get the same error.

object(GuzzleHttp\Psr7\Response)#60 (6) { ["reasonPhrase":"GuzzleHttp\Psr7\Response":private]=> string(11) "Bad Request" ["statusCode":"GuzzleHttp\Psr7\Response":private]=> int(400) ["headers":"GuzzleHttp\Psr7\Response":private]=> array(12) { ["Content-Type"]=> array(1) { [0]=> string(24) "text/html; charset=UTF-8" } ["Date"]=> array(1) { [0]=> string(29) "Wed, 06 Sep 2017 18:57:27 GMT" } ["Expires"]=> array(1) { [0]=> string(29) "Wed, 06 Sep 2017 18:57:27 GMT" } ["Cache-Control"]=> array(1) { [0]=> string(18) "private, max-age=0" } ["X-Content-Type-Options"]=> array(1) { [0]=> string(7) "nosniff" } ["X-Frame-Options"]=> array(1) { [0]=> string(10) "SAMEORIGIN" } ["X-XSS-Protection"]=> array(1) { [0]=> string(13) "1; mode=block" } ["Server"]=> array(1) { [0]=> string(3) "GSE" } ["Alt-Svc"]=> array(1) { [0]=> string(40) "quic=":443"; ma=2592000; v="39,38,37,35"" } ["Accept-Ranges"]=> array(1) { [0]=> string(4) "none" } ["Vary"]=> array(1) { [0]=> string(15) "Accept-Encoding" } ["Transfer-Encoding"]=> array(1) { [0]=> string(7) "chunked" } } ["headerNames":"GuzzleHttp\Psr7\Response":private]=> array(12) { ["content-type"]=> string(12) "Content-Type" ["date"]=> string(4) "Date" ["expires"]=> string(7) "Expires" ["cache-control"]=> string(13) "Cache-Control" ["x-content-type-options"]=> string(22) "X-Content-Type-Options" ["x-frame-options"]=> string(15) "X-Frame-Options" ["x-xss-protection"]=> string(16) "X-XSS-Protection" ["server"]=> string(6) "Server" ["alt-svc"]=> string(7) "Alt-Svc" ["accept-ranges"]=> string(13) "Accept-Ranges" ["vary"]=> string(4) "Vary" ["transfer-encoding"]=> string(17) "Transfer-Encoding" } ["protocol":"GuzzleHttp\Psr7\Response":private]=> string(3) "1.1" ["stream":"GuzzleHttp\Psr7\Response":private]=> object(GuzzleHttp\Psr7\Stream)#48 (7) { ["stream":"GuzzleHttp\Psr7\Stream":private]=> resource(72) of type (stream) ["size":"GuzzleHttp\Psr7\Stream":private]=> NULL ["seekable":"GuzzleHttp\Psr7\Stream":private]=> bool(true) ["readable":"GuzzleHttp\Psr7\Stream":private]=> bool(true) ["writable":"GuzzleHttp\Psr7\Stream":private]=> bool(true) ["uri":"GuzzleHttp\Psr7\Stream":private]=> string(10) "php://temp" ["customMetadata":"GuzzleHttp\Psr7\Stream":private]=> array(0) { } } }

The above is the complete response that I get when I var_dump the create method response.

The debug response message you've provided above doesn't give any clue to know the cause of your issue. I suggest you to use zend debugger or xdebug to properly find the culprit of your issue.

Here's what I see when I enable xdebug in my MAMP.

/Applications/MAMP/htdocs/snippets/gd/GoogleContacts.php:64:
object(GuzzleHttp\Psr7\Response)[62]
private 'reasonPhrase' => string 'Bad Request' (length=11)
private 'statusCode' => int 400
private 'headers' =>
array (size=12)
'Content-Type' =>
array (size=1)
0 => string 'text/html; charset=UTF-8' (length=24)
'Date' =>
array (size=1)
0 => string 'Thu, 07 Sep 2017 05:51:10 GMT' (length=29)
'Expires' =>
array (size=1)
0 => string 'Thu, 07 Sep 2017 05:51:10 GMT' (length=29)
'Cache-Control' =>
array (size=1)
0 => string 'private, max-age=0' (length=18)
'X-Content-Type-Options' =>
array (size=1)
0 => string 'nosniff' (length=7)
'X-Frame-Options' =>
array (size=1)
0 => string 'SAMEORIGIN' (length=10)
'X-XSS-Protection' =>
array (size=1)
0 => string '1; mode=block' (length=13)
'Server' =>
array (size=1)
0 => string 'GSE' (length=3)
'Alt-Svc' =>
array (size=1)
0 => string 'quic=":443"; ma=2592000; v="39,38,37,35"' (length=40)
'Accept-Ranges' =>
array (size=1)
0 => string 'none' (length=4)
'Vary' =>
array (size=1)
0 => string 'Accept-Encoding' (length=15)
'Transfer-Encoding' =>
array (size=1)
0 => string 'chunked' (length=7)
private 'headerNames' =>
array (size=12)
'content-type' => string 'Content-Type' (length=12)
'date' => string 'Date' (length=4)
'expires' => string 'Expires' (length=7)
'cache-control' => string 'Cache-Control' (length=13)
'x-content-type-options' => string 'X-Content-Type-Options' (length=22)
'x-frame-options' => string 'X-Frame-Options' (length=15)
'x-xss-protection' => string 'X-XSS-Protection' (length=16)
'server' => string 'Server' (length=6)
'alt-svc' => string 'Alt-Svc' (length=7)
'accept-ranges' => string 'Accept-Ranges' (length=13)
'vary' => string 'Vary' (length=4)
'transfer-encoding' => string 'Transfer-Encoding' (length=17)
private 'protocol' => string '1.1' (length=3)
private 'stream' =>
object(GuzzleHttp\Psr7\Stream)[61]
private 'stream' => resource(72, stream)
private 'size' => null
private 'seekable' => boolean true
private 'readable' => boolean true
private 'writable' => boolean true
private 'uri' => string 'php://temp' (length=10)
private 'customMetadata' =>
array (size=0)
empty

I don't see anything specific about what's causing the issue.

You've just pasted the same debug response message. What I mean in my last reply is use the xdebug or zend debugger to use breakpoint on the line where you suspected your code goes wrong and then do PHP line by line execution to properly debug your issue as the debug response message does not give any clue or helpful information. This is the best way for you to understand and debug your issue.

I did everything in the setup except the "Delegate domain-wide authority to Google service account" section. I don't have Google Suite or Google Apps account...now I am running into what looks like authorization problems...curl error 60.

I would just like to know if it is even possible to have a service account access a regular gmail google account's.

I have verified my setup with javascript code that uses a user account and OAUTH2 and I can access my gmail contacts, but I really want a back-end server to do the access using php and a service account so I don't have to get user authorization on the front-end.

From all the documents I've read, it is not really clear weather I can do this with a service account or not. Any help would be appreciated....thanks.

Thank you for your response. It is becoming evident that that is the case. I wonder if you have a reference from any google docs that specifically says that? I've been looking all over. thanks.

Hi, thanks for the tutorial - everything works.
But I need to create contacts as shared contacts. The api seems to be identical, but when I change the $conf like I need, no contact could be created. Any ideas?
'gcontacts_base_url' => 'https://www.google.com/m8/feeds/contacts/mydomain/full',
'gcontacts_scopes' => [
'https://www.google.com/m8/feeds/contacts/',
]
Thanks!

Hi, the codes in this article above are working. Make sure the Google account you are using is valid. Note: regular free gmail google account will not work. You need to have Google's suite of intelligent apps (formerly Google Apps) account for this to work.

Also please check out this update.

Hi, thx for this great tutorial, it helped me a lot for understanding the google policy and the contact api. I have an issue while I get the following message "Creation of Google Contacts entry failed" when I try to create a contact (I have the same if I try to update or delete).
It seems that I don't manage to construct the GoogleContacts Object. Do you have a clue of where does it come from?
TIA.
ERwan

It came from the sample PHP application in this article, this part:

  
  if (isset($_POST['create'])) {
    $response  = $gcontacts->create($data);
    if (!$response) {
      echo '

Creation of Google Contacts entry failed.

'; } else { echo '

Creation of Google Contacts entry successful.

'; } }

Make sure the Google account you are using is valid. Note: regular free gmail google account will not work. You need to have Google's suite of intelligent apps (formerly Google Apps) account for this to work.

Also please check out this update.

Hi,
This is really really nice tutorial! Thank you.
Every thing works fine except, say the phone number(mobile) is blank in the source contact list, and i am trying to update the phone number by submitting the form. Its just not getting updated.
Any solutions for this, would be much appreciated.

Thank you

Unfortunately, that is one limitation of our sample code. If anyone have a chance to improve the sample code addressing this limitation and would want to share with other developers, your codes are welcome. Thanks.

Hi,

Thanks for the nice tutorial, I have implemented this as per the instruction and received the message contact created successfully but unable to view that new contact in the google contacts.

Thanks in advanced.

Fatal error: Uncaught GuzzleHttp\Exception\ClientException: Client error: `POST https://oauth2.googleapis.com/token` resulted in a `401 Unauthorized` response: { "error": "unauthorized_client", "error_description": "Client is unauthorized to retrieve access tokens using this (truncated...) in C:\xampp\htdocs\googlecontacts\vendor\guzzlehttp\guzzle\src\Exception\RequestException.php:113 Stack trace: #0 C:\xampp\htdocs\googlecontacts\vendor\guzzlehttp\guzzle\src\Middleware.php(65): GuzzleHttp\Exception\RequestException::create(Object(GuzzleHttp\Psr7\Request), Object(GuzzleHttp\Psr7\Response)) #1 C:\xampp\htdocs\googlecontacts\vendor\guzzlehttp\promises\src\Promise.php(203): GuzzleHttp\Middleware::GuzzleHttp\{closure}(Object(GuzzleHttp\Psr7\Response)) #2 C:\xampp\htdocs\googlecontacts\vendor\guzzlehttp\promises\src\Promise.php(156): GuzzleHttp\Promise\Promise::callHandler(1, Object(GuzzleHttp\Psr7\Response), Array) #3 C:\xampp\htdocs\googlecontacts\vendor\guzzlehttp\promises\src\TaskQueue.php(47): GuzzleHttp\Promise\Pr in C:\xampp\htdocs\googlecontacts\vendor\guzzlehttp\guzzle\src\Exception\RequestException.php on line 113

One thing my G suite account is in trial period is it working in trial?
after create click im getting message Creation of Google Contacts entry successful.
And After Query i get:
Query of Google Contacts entry successful. Below are the data returned from Google:

Array ( [userId] => b4f28a80aea91fc [etag] => "RHwyfDVSLyt7I2A9XBFbEEkPTww." [content] => Hi, I would like to request more details about Drupal 8 Module development ebook. Thanks. [fullName] => Mr. John D. Doe Sr. [namePrefix] => Mr. [givenName] => John [additionalName] => D. [familyName] => Doe [nameSuffix] => Sr. [birthday] => [emailHome] => [email protected] [emailWork] => l>

Address:

+6328888888 [phoneNumberMobile] => +639198888888 [phoneNumberWork] => +632777777 [formattedAddress] => #101 Arca Drive, [street] => #101 Arca Drive [city] => Manila [region] => NCR [postcode] => 1900 [country] => Philippines [mostConvenientTimeToCall] => after lunch [preferredProduct] => Drupal 8 Module development ebook [group] => http://www.google.com/m8/feeds/groups/contact%40contacts-264008.iam.gserviceaccount.com/base/6 [timestamp] => 1578286926 )

A contact should be created on your Google Contacts after clicking the "Create" button otherwise you must have not followed the steps carefully or the account you are using don't have enough permission.

Group id /m8/feeds/groups//base/6 does not match template
Creation of Google Contacts entry failed.

Add new comment

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Lines and paragraphs break automatically.