📒

【hubspot-api-php】コンタクトと取引を紐づける

2024/09/18に公開

前提

  • Laravel 10
  • hubspot/api-client v11.3
    • https://github.com/HubSpot/hubspot-api-php
    • このライブラリを使って、HubSpot上で以下を実行したい
      • 「コンタクト」を新規作成or更新
      • 「取引」を新規作成
      • 「取引」と「コンタクト」を紐づける

結論

  • 以下に対応コードを記述しています

設定値:config/services.php

  • HubSpotの管理画面からアクセストークンを取得して設定
    • アクセストークンの取得方法はココを参照
    • 過去仕様のAPIキーで制御する方法は非推奨になったっぽいので、原則アクセストークンで制御しよう
    'hubspot' => [
        'access_token' => env('HUBSPOT_ACCESS_TOKEN'),
    ],

クライアント:app/Services/Vendor/HubSpot/HubSpotService.php

  • HubSpotライブラリのラッパークラス
    • emailによる既存コンタクト取得
    • コンタクトの新規作成
    • コンタクトの上書き更新
    • コンタクトのupsert
    • 取引の新規作成
    • 取引→コンタクトへの紐づけ
<?php

namespace App\Services\Vendor\HubSpot;

use HubSpot\Client\Crm\Associations\V4\ApiException as AssociationsApiException;
use HubSpot\Client\Crm\Associations\V4\Model\AssociationSpec;
use HubSpot\Client\Crm\Associations\V4\Model\LabelsBetweenObjectPair as AssociationsLabelsBetweenObjectPair;
use HubSpot\Client\Crm\Contacts\ApiException as ContactsApiException;
use HubSpot\Client\Crm\Contacts\Model\Filter as ContactFilter;
use HubSpot\Client\Crm\Contacts\Model\FilterGroup as ContactFilterGroup;
use HubSpot\Client\Crm\Contacts\Model\PublicObjectSearchRequest as ContactPublicObjectSearchRequest;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObject as ContactSimplePublicObject;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectInput as ContactInput;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObjectInputForCreate as ContactInputForCreate;
use HubSpot\Client\Crm\Deals\ApiException as DealsApiException;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObject as DealSimplePublicObject;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObjectInputForCreate as DealInput;
use HubSpot\Discovery\Discovery;
use HubSpot\Factory;

readonly class HubSpotService
{
    private Discovery $hubSpot;

    public function __construct(
    ) {
        $this->hubSpot = Factory::createWithAccessToken(config('services.hubspot.access_token'));
    }

    /**
     * @throws ContactsApiException
     */
    public function getContactByEmail(string $email): ?ContactSimplePublicObject
    {
        $filterGroup = new ContactFilterGroup(['filters' => [new ContactFilter([
            'operator' => 'EQ',
            'property_name' => 'email',
            'value' => $email,
        ])]]);

        $searchRequest = new ContactPublicObjectSearchRequest([
            'filter_groups' => [$filterGroup],
            'limit' => 1,
            'after' => 0,
        ]);

        // HubSpot APIでメールアドレスを使ってコンタクトを検索
        $searchResponse = $this
            ->hubSpot
            ->crm()
            ->contacts()
            ->searchApi()
            ->doSearch($searchRequest);

        $results = $searchResponse->getResults();

        return count($results) > 0 ? $results[0] : null;
    }

    /**
     * @throws ContactsApiException
     */
    public function upsertContact(array $data): ContactSimplePublicObject
    {
        $email = $data['properties']['email'];
        $contact = $this->getContactByEmail($email);

        return $contact
            ? $this->updateContact($contact->getId(), $data)
            : $this->createContact($data);
    }

    /**
     * @throws ContactsApiException
     */
    public function createContact(array $data): ContactSimplePublicObject
    {
        return $this
            ->hubSpot
            ->crm()
            ->contacts()
            ->basicApi()
            ->create(new ContactInputForCreate($data));
    }

    /**
     * @throws ContactsApiException
     */
    public function updateContact(string $contactId, array $data): ContactSimplePublicObject
    {
        return $this
            ->hubSpot
            ->crm()
            ->contacts()
            ->basicApi()
            ->update($contactId, new ContactInput($data));
    }

    /**
     * @throws DealsApiException
     */
    public function createDeal(array $data): DealSimplePublicObject
    {
        return $this
            ->hubSpot
            ->crm()
            ->deals()
            ->basicApi()
            ->create(new DealInput($data));
    }

    /**
     * @throws AssociationsApiException
     */
    public function associateDealToContact(ContactSimplePublicObject $contact, DealSimplePublicObject $deal): AssociationsLabelsBetweenObjectPair
    {
        return $this
            ->hubSpot
            ->crm()
            ->associations()
            ->v4()
            ->basicApi()
            ->create(
                'deals',
                $deal->getId(),
                'contacts',
                $contact->getId(),
                [new AssociationSpec([
                    'association_category' => 'HUBSPOT_DEFINED',
                    // 参考:https://developers.hubspot.com/docs/api/crm/associations Deal to Contact
                    'association_type_id' => 3,
                ])],
            );
    }
}

実行例:app/Http/Controllers/HubSpotController.php

  • コンストラクタインジェクションしつつHubSpotのクライアントを呼び出す
    • HubSpotのカスタムプロパティを利用すれば任意の値を設定可能
namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Services\Vendor\HubSpot\HubSpotService;
use DB;
use HubSpot\Client\Crm\Contacts\Model\SimplePublicObject as ContactSimplePublicObject;
use HubSpot\Client\Crm\Deals\Model\SimplePublicObject as DealSimplePublicObject;
use Illuminate\Contracts\Database\Eloquent\Builder as BuilderContract;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Notification;

class HubSpotController extends Controller
{
    public function __construct(
        private readonly HubSpotService $hubSpotService
    ) {
    }

    public function __invoke(Request $request): JsonResponse
    {
        // HubSpotにコンタクト情報を作成
        $hubspotContact = $this->createHubSpotContact($request);

        // HubSpotに取引情報を作成
        $hubSpotDeal = $this->createHubSpotDeal($request);

        // HubSpot上で取引情報とコンタクト情報を関連付け
        $this->hubSpotService->associateDealToContact($hubspotContact, $hubSpotDeal);

        return response()->json([
            'status' => 'success',
            'message' => 'Contact form has been submitted successfully.',
        ]);
    }

    private function createHubSpotContact(Request $request): ContactSimplePublicObject
    {
        return $this->hubSpotService->upsertContact([
            // 必要に応じてプロパティを追加
            'properties' => [
                // 名
                'firstname' => $request->input('firstname'),
                // 姓
                'lastname' => $request->input('lastName'),
                // 郵便番号
                'zip' => $request->input('zip'),
                // 都道府県
                'state' => $request->input('state'),
                // 市区町村以下
                'city' => $request->input('city'),
                // 電話番号
                'phone' => $request->input('phone'),
                // 社名
                'company' => $request->input('company'),
                // メールアドレス(HubSpotのコンタクト上ではユニークなキーとして扱われる
                'email' => $request->input('email'),
            ],
        ]);
    }

    private function createHubSpotDeal(Request $request): DealSimplePublicObject
    {
        // HubSpotに取引情報を作成
        return $this->hubSpotService->createDeal([
            // 必要に応じてプロパティを追加
            'properties' => [
                // 取引名
                'dealname' => $request->input('dealname'),
          // 取引詳細
                'description' => $request->input('description'),
            ],
        ]);
    }
}
  • api.phpでは以下のように呼び出す
Route::post('hubspot', HubSpotController::class)->name('contact');

余談

公式でコンタクトのupsertってないの?

associate関連の定義がわからん

HubSpotのAssociate定義値

Discussion