コンタクトとの販売やり取りを管理する取引はとても便利
今回の利用ケースでは
コンタクトから利用申し込みがあった時点で、販売やり取りが開始されるので
申し込みをするというイベントの後、自動で取引を作成する処理が必要になりました
HubspootのそれぞれのAPIを順番に呼んでいくと言うだけなのですが
備忘録のために書いていきます
大まかな流れ
今回はLaravel上でJobを実装し
Hubspot APIの実行をしています
事前準備
- 取引のパイプラインを用意する(デフォルト利用でも可)
- それぞれパイプラインにはIDがあるのでメモっておく
- パイプラインにステージを用意する(デフォルト利用でも可)
- それぞれステージにはIDがあるのでメモっておく
実行コード
<?php
namespace App\Jobs;
use App\Models\User;
use Exception;
use HubSpot\Factory;
use HubSpot\Discovery\Discovery;
use HubSpot\Client\Crm\Contacts\ApiException as ContactsApiException;
use HubSpot\Client\Crm\Contacts\Model\Filter;
use HubSpot\Client\Crm\Contacts\Model\FilterGroup;
use HubSpot\Client\Crm\Contacts\Model\PublicObjectSearchRequest;
use HubSpot\Client\Crm\Deals\Model\AssociationSpec;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectInput;
use HubSpot\Client\Crm\Deals\ApiException as DealsApiException;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Redis\LimiterTimeoutException;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Throwable;
class HubSpotCreateDealsAssociationsContact implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
const DETAILS_STAGE_ID = [
'local' => xxxxxx,
'staging' => xxxxx,
'production' => xxxxx,
]; // ステージIDを記載
const DETAILS_PIPELINE_ID = [
'local' => xxxxx,
'staging' => xxxxx,
'production' => xxxx,
]; // パイプラインID
protected array $userProperties;
/**
* 最大試行回数
*
* @var int
*/
public int $tries = 3;
/**
* @param User $user
*/
public function __construct(
User $user
) {
$this->userProperties = [
'email' => $user->email,
'name' => $user->name,
];
}
/**
* Execute the job.
*
* @return void
* @throws LimiterTimeoutException
*/
public function handle(): void
{
try {
$hubspot = Factory::createWithAccessToken(config('services.hubspot.access_token'));
// 取引の作成
$apiResponseForCreateDeals = $this->createDeals($hubspot);
if (!$apiResponseForCreateDeals->getId()) {
throw new Exception('取引の作成に失敗しました');
}
// コンタクトと紐付け
// 紐付け先対象のコンタクト取得
$contactId = $this->getContactInfo($hubspot);
if ($contactId == null) {
throw new Exception('対象のcontact IDが見つかりませんでした');
}
$this->associationsContact($hubspot, $apiResponseForCreateDeals, $contactId);
} catch (ContactsApiException $e) {
Log::error("contacts()->searchApi()->doSearch() ". $e->getMessage());
} catch (DealsApiException $e) {
Log::error( "deals()->associationsApi()->create() ". $e->getMessage());
} catch (Exception $e) {
Log::error("HubSpotCreateDealsAssociationsContact Exception: ". $e->getMessage());
}
}
/**
* The job failed to process.
*
* @param Throwable $e
* @return void
*/
public function failed(Throwable $e): void
{
Log::error(sprintf("<!here>\n %s ジョブの実行に失敗しました。\n :no_entry_sign: %s", config('app.env'), $e->getMessage()));
}
/**
* 取引の作成
*
* @param Discovery $hubspot
* @return SimplePublicObject|Error
* @throws \HubSpot\Client\Crm\Deals\ApiException
* @throws Exception
*/
private function createDeals(Discovery $hubspot): SimplePublicObject|Error
{
// 取引の作成
$dealsProperties = [
'dealname' => '任意のタイトル',
'dealstage' => self::DETAILS_STAGE_ID[config('app.env')],
'pipeline' => self::DETAILS_PIPELINE[config('app.env')],
// 取引のプロパティ値で更新したいものを書く
// 'プロパティ固有ID' => 値
];
$simplePublicObjectInput = new SimplePublicObjectInput([
'properties' => $dealsProperties,
]);
$apiResponseForCreateDeals = $hubspot->crm()->deals()->basicApi()->create($simplePublicObjectInput);
Log::info('create Hubspot Details ID: ' . $apiResponseForCreateDeals->getId());
return $apiResponseForCreateDeals;
}
/**
* コンタクトの取得
*
* @param Discovery $hubspot
* @return int|null
* @throws ApiException
*/
private function getContactInfo(Discovery $hubspot): ?int
{
$filter = new Filter();
// ここではメールでコンタクトの取得を行います
$filter
->setOperator('EQ')
->setPropertyName('email')
->setValue($this->userProperties['email']);
$filterGroup = new FilterGroup();
$filterGroup->setFilters([$filter]);
$searchRequest = new PublicObjectSearchRequest();
$searchRequest->setFilterGroups([$filterGroup]);
// Get specific properties
$searchRequest->setProperties(['email']);
// @var CollectionResponseWithTotalSimplePublicObject $contactsPage
$contactsPage = $hubspot->crm()->contacts()->searchApi()->doSearch($searchRequest);
return $contactsPage['results'][0]['id'] ?? null;
}
/**
* コンタクトと取引の紐付け
*
* @param Discovery $hubspot
* @param SimplePublicObject $apiResponseForCreateDeals
* @param int $contactId
* @return void
* @throws DealsApiException
*/
private function associationsContact(
Discovery $hubspot,
SimplePublicObject $apiResponseForCreateDeals,
int $contactId
): void {
$associationSpec = new AssociationSpec([
'association_category' => 'HUBSPOT_DEFINED',
'association_type_id' => 3
]);
$hubspot->crm()->deals()->associationsApi()->create(
$apiResponseForCreateDeals->getId(),
'Contact',
$contactId,
[$associationSpec]
);
Log::info('Hubspot Details Associations Contact');
}
}
呼び出し元はお決まりのこんな感じで
HubSpotCreateDealsAssociationsContact::dispatch($user);
実装にあたり参照したもの
APIのコールはhubspot公式のライブラリーを通して行なっています
これ: hubspot/api-client
APIの仕様についてはこれまた公式ドキュメントを参照してます
HubSpot API Docs
関連付けのIDについて
associationsApi ( /crm/v4/objects/deals/{dealId}/associations/{toObjectType}/{toObjectId} )
のリクエストに使うAssociationSpecの引数ですが、下記のURLから関連付け先のものを参照しました
https://legacydocs.hubspot.com/docs/methods/crm-associations/crm-associations-overview
今回だと取引からコンタクトへの紐付けをしたので
Deal to contact: 3 となります
取引の作成と関連付けは一気にできないのか
わかる。
APIコール2回してて無駄っぽいので改善ポイント
最後に
取引の作成とコンタクトへの関連付けのコードの紹介をしました。
基本的にはHubSpot API Docsに沿ってやっているだけですが、
関連付け処理で引数に迷うことがあったので、備忘録として記しました。
弊社では、一緒に働いてくれるエンジニアを募集しています。
エンジニアサイドからHubspotについて語れる方がいたらめっちゃ嬉しいです
↓そんな感じで興味のある方はこちらから↓
Wantedly - スタジオアンビルト株式会社
こんなWebサービスを作っています。
・マドリー
・Studio Unbuilt