1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Hubspot APIを利用して、取引作成と既存コンタクトへの関連付けをするJob

Posted at

コンタクトとの販売やり取りを管理する取引はとても便利

参考: 取引を作成する|Hubspot ナレッジベース

今回の利用ケースでは
コンタクトから利用申し込みがあった時点で、販売やり取りが開始されるので
申し込みをするというイベントの後、自動で取引を作成する処理が必要になりました

HubspootのそれぞれのAPIを順番に呼んでいくと言うだけなのですが
備忘録のために書いていきます

大まかな流れ

スクリーンショット 2023-03-02 8.25.38.png

今回は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

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?