【PHP】Google広告APIでオフラインコンバージョンを自動アップロードしたい！

#はじめに

以前YahooAPIでオフラインコンバージョンを自動でアップロードする方法について、
調べて[記事](https://qiita.com/azasaz/items/20e49936471c4dd5ecb6)にしたんですけど、それじゃGoogleは？というおはなし。

#そもそもオフラインコンバージョンてなんぞや。

Googleさんが広告タグに「gclid」という長い文字列をくっつけてくれるので、
コンバージョンに結びついたデータからそのIDを引っこ抜いて、
ついでに何時何分何秒にいくらでコンバージョンに至ったかの情報を
まるっと渡すと、GoogleさんがAIを駆使して分析してより細分化された広告設定ができるようになる。らしい。

（とは担当者談なので、実際のところよく知らないんだけど）

オフラインコンバージョンについての詳細は[ココ](https://anagrams.jp/blog/listing_knowhow/offline-conversion-tracking-for-google-ads/)がわかりやすい

※ちなみにYahooの場合は「yclid」というタグが付与されて同様の機能があります。
　APIでのデータの渡し方は、上述した私の過去記事で紹介してあります。

#なぜ今まで自動アップロードに対応していなかったか

Googleのオフラインコンバージョンは、
もちろんAPIアップロードが可能ではあるのですが、
管理画面の設定によって、スプレッドシートを時間とともに設定しておけば、
自動で読み込んでアップロードしてくれるという便利機能があるらしく。

担当者の方からは

「指定時間に、スプレッドシートに必要なデータを出力するプログラム作って」

と言われたのです。

APIから直接アップしたほうがはやくね？
とは頭をよぎりましたが、
しがない雇われエンジニアとしては

ふーん（　´_ゝ`）

てなもんで、おおせのとおりに。

せこせこと[スプレッドシートAPI](https://developers.google.com/sheets/api)の仕様を調べて
（ちゃっかり[記事](https://qiita.com/azasaz/items/051f2cc1646fe13a8710)は残した）
スプレッドシートにコンバージョン情報を出力する仕組みを作ったわけです。
CRONで朝起動設定にしてました。

ところが最近の月末（だったかな？）
なんだかスプレッドシート出力のエラーが。出た。

それ以降ちらほらとなんかエラー起こる。
なんなら一つのスプレッドシートで１つのシートだけエラー落ちしてたりする。
起こらない日もある。

エラーになってたプログラムを手動起動したら、
エラー起こらず正常終了しちゃうし。

**ナニコレ！？**

謎に包まれたまま連日エラーログとにらめっこしながらあーだこーだしてたわけです。

そしたら運用担当者さんからですね、

「なんかもうAPIから直接アップロードしちゃったほうが早くね？」

とまあ、ぶっちゃけちゃったねキミィという感じの連絡をいただきました。
そうですねそのとおりですね。実は最初から思ってました。。

という経緯により、
スプレッドシート出力はやめて直接API叩こう！という方針に変わったのでした。

なんかスプレッドシートAPIがヤクタタズみたいな書き方になってしまいましたが
SheetsAPIたいへん便利です。使いみちいろいろ。

ただアレです、単に私のエンジニアとしての能力不足だったのです。
エラー処理だいじ。

**ログ吐け。なんでもいいから全部ログに吐き出せ。**
出すもの全部出して、必要なものを必要なときに自分で精査しな！ってなもんです。
これに尽きます。

裏で動かすものはなにか起きたときにとにかく追えるようにせねばならぬ。
頭では分かってたのにできてませんでした。深く反省。

APIを呼んだらRESPONSEをそのままログに出力するようにいろいろ直しました。

#コンバージョンアップロードメソッドuploadClickConversions

最初にググって出てきたのが、
Google更新ドキュメントの[サンプルコード](https://developers.google.com/google-ads/api/docs/samples/upload-offline-conversion?hl=en)でした。

**おう！もうやりたいことここ全部やんけ！！**

という感じなのですが、まあ例によって
「意地でもサンプル使わず自力でやる」がモットーなので（なんかもうヤケクソ）

今回も自力でやりますよ。いつもの。

上記のドキュメントを解読すると

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/566860/1be5f763-3335-aa60-7241-80d8cbd21eca.png)

大事なのここっぽい。。。

調べたら[リファレンス](https://developers.google.com/google-ads/api/rest/reference/rest/v9/customers/uploadClickConversions?hl=es)にありました。
customerに紐づくメソッドとのこと。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/566860/cd5fe7b7-7c17-a3e3-f3b5-7f71abb15e4b.png)



ClickConversionというオブジェクトに必要な情報が入ってて、
それを渡す感じのようです。ふむふむ。

ClickConversionとはなんぞや？まで調べてみると
リファレンスは[ここ](https://developers.google.com/google-ads/api/rest/reference/rest/v9/customers/uploadClickConversions?hl=es#ClickConversion)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/566860/7e52001d-c86f-cf98-7147-336a41324e1d.png)

gclidとかDateとかValueとか必要な情報があって、めっちゃこれっぽい！！！

ここまで親切に教えてくれるともうゴールは見えてきました。
慣れって怖いですね。

#前提：認証（いつもの）

まずはおなじみのoAuth2認証。

認証に関しては、過去記事参照のこと。
[【PHP】Google Ads APIをサンプルプログラム無しで使いたい！！](https://qiita.com/azasaz/items/9a64c7c8d56d6692788b)

リフレッシュトークンとクライアントID、クライアントシークレットを使ってoAuth2認証を行い、
アクセストークンを取るところまで。

アクセストークンが取れたら、ヘッダーに認証情報をセットします。

```
//ヘッダー情報をセット
$_header = [
    "Content-Type: application/json",
    "Accept: application/json",
    "Authorization: Bearer $access_token", 
    "developer-token: $developer_token",
    "login-customer-id: $login_customer_id",
];
```

準備おーけー。

#ConversionActionを取得する

ClickConversionの中に、ひとつだけ見覚えのないものがありました。
それがConversionActionというもの。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/566860/b2491c89-7d83-d2fd-80f3-0bdb6baec4a9.png)
Google翻訳：
この変換に関連付けられている変換アクションのリソース名。 
注：このリソース名は顧客IDと変換アクションIDで構成されますが、検証では顧客IDが無視され、
変換アクションIDが変換アクションの唯一の識別子として使用されます。

**りそーす！！？**
なんだっけそれ←

わからないのでConversionActionで調べてみると
なるほどリソースの中にありました[conversion_action](https://developers.google.com/google-ads/api/fields/v9/conversion_action)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/566860/9cf953d7-a2a1-351f-9122-9a45cc867db6.png)

これアレですね、いつもの、SQLみたいなクエリで引っ張ってこれるデータですね。
[過去記事](https://qiita.com/azasaz/items/fcbbe9101efad2ffeda7#%E3%82%AF%E3%82%A8%E3%83%AA%E8%A8%80%E8%AA%9E%E3%81%A7%E3%83%87%E3%83%BC%E3%82%BF%E5%8F%96%E5%BE%97)でも使ってるようなやつ

で、リソース名って書いてあったから、これだね
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/566860/1650a999-97bc-4ef4-c3b4-a2d7e94ab2f9.png)


というわけで、クエリ言語でconversionactionを取ってみます。

```
$_query_str = "
SELECT
   conversion_action.id,
   conversion_action.name,
   conversion_action.type,
   conversion_action.status,
   conversion_action.resource_name
FROM conversion_action
";

//クエリをセット
$_query = ["query" => $_query_str];

//URLをセット
//※versionやcustomeridは環境に合わせてセットすること 2021/11現在の最新versionはv9
$_url = "https://googleads.googleapis.com/v9/customers/$customerid/googleAds:search";

//curl START
$_curl = curl_init();

//OPTIONをセット
curl_setopt_array($_curl, array(
    CURLOPT_URL => $_url,
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 120,
    CURLOPT_HTTPHEADER => $_header,
    CURLOPT_POSTFIELDS => json_encode($_query),
));

//curl EXEC(文字列で取得)
$_resp = curl_exec($_curl);

//エラーハンドリング用
$_errno = curl_errno($_curl);

//curl END
curl_close($_curl);

//エラーハンドリング
if ($_errno !== CURLE_OK) {
    //エラー処理
}

//エラーでなければjsonを連想配列化
$_result_json = json_decode($_resp, true); 
$_result = is_array($_result_json["results"]) ? $_result_json["results"] : [];

//履歴情報を整理
$_return = [];
foreach ($_result as $_idx => $_lineval) {
    if (!is_array($_lineval)) continue;

    $_id = $_lineval["conversionAction"]["id"];//コンバージョンアクションID
    $_name = $_lineval["conversionAction"]["name"];//コンバージョンアクション名
    $_type = $_lineval["conversionAction"]["type"];//コンバージョンアクションタイプ
    $_status = $_lineval["conversionAction"]["status"];//コンバージョンアクションステータス
    $_resourcename = $_lineval["conversionAction"]["resourceName"];//コンバージョンアクションリソース名

    $_return[] = [
        $_id, $_name, $_type, $_status, $_resourcename,
    ];
}
```

conversionActionはこんなかんじのリソースが取れます:

>customers/1234567890/conversionActions/123456789

ではでは、これをClickConversionの形に当てはめてアップロードしてみませう。

#実際にアップロードしてみる

まずはコンバージョンデータを整理します。
ClickConversionのライブラリを参考に、必要な項目をセットしましょう。

配列に入れて複数件を一気にアップロードできます。

```
$ClickConversion = [];
//1件目
$ClickConversion[] = [
    "gclid" => "ABcdefjHIjklmnOPqrstuVWxyz_1234567890",//gclid
    "conversionAction" => "customers/1234567890/conversionActions/123456789",//取得したConversionActionのリソース名
    "conversionDateTime" => "2021-11-01 15:01:11+09:00",//書式注意GMT入れる必要あり
    "conversionValue" => "10000",//
];
//2件目
$ClickConversion[] = [
    "gclid" => "1212ABcdefjHI3434-jklmn5656OPqrstu_789VWxyz0",//gclid
    "conversionAction" => "customers/1234567890/conversionActions/123456789",//取得したConversionActionのリソース名
    "conversionDateTime" => "2021-11-01 18:24:54+09:00",//書式注意GMT入れる必要あり
    "conversionValue" => "5000",//
];
```

ところで、
uploadClickConversionsメソッドのリクエストパラメータの中に、
validateOnlyというフラグがひっそりとおります。そう、ひっそりと。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/566860/2efcf1b8-8e98-1db0-8d47-e252d4f73ea4.png)
Google翻訳：
trueの場合、要求は検証されますが実行されません。 結果ではなく、エラーのみが返されます。

こいつがテスト用か本番用かをしっかり分けてくれます。えらい。

validateOnlyをtrueにしておけば、実際にアップロードはされずエラーだけ教えてくれるのです。
なので、テストのときやエラーがないかをチェックしたいときはtrueにしましょう。

ということを踏まえた上で、
今度はClickConversionをアップロードする処理です。

```
//URLをセット
//※versionやcustomeridは環境に合わせてセットすること 2021/11現在の最新versionはv9
$_url = "https://googleads.googleapis.com/v9/customers/$customerid:uploadClickConversions";

//パラメータをセット
$_param = [
	"conversions" => $ClickConversion,
	"partialFailure" => true,
	"validateOnly" => true, //検証の場合はtrue,実行する場合はfalse
];

//curl START
$_curl = curl_init();

//OPTIONをセット
curl_setopt_array($_curl, array(
    CURLOPT_URL => $_url,
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 120,
    CURLOPT_HTTPHEADER => $this->header,
	CURLOPT_POSTFIELDS => json_encode($_param),
));

//curl EXEC(文字列で取得)
$_resp = curl_exec($_curl);

//エラーハンドリング用
$_errno = curl_errno($_curl);

//curl END
curl_close($_curl);

//エラーハンドリング
if ($_errno !== CURLE_OK) {
    throw new Exception("Failed to GoogleAPI Access:".$_resp);
}

//エラーでなければjsonを連想配列化        
$_result = json_decode($_resp, true); 
```

validateOnlyをtrueにして実行した場合、こんなかんじのエラーが返ってきます。

>{"partialFailureError":{
>"code":3,"message":"This Google Click ID could not be decoded., at conversions[31].gclid",
>"details":[{"@type":"type.googleapis.com\/google.ads.googleads.v9.errors.GoogleAdsFailure",
>"errors":[{"errorCode":{"conversionUploadError":"UNPARSEABLE_GCLID"},
>"message":"This Google Click ID could not be decoded.",
>"trigger":{"stringValue":"CjwKCAiA7dKMBhBCEiwAO_crFPZY68nU8i8Z0dkAd0GHCu8ENIAG-ofVv8ft7dhhoCiHQQAvD_BwE"},
>"location":{"fieldPathElements":[{"fieldName":"conversions","index":31},
>{"fieldName":"gclid"}]}}]}]},
>"results":{}}

エラーがあれば教えてくれるけど、実行はしていないのでresultは空です。

validateOnlyをfalseにすると、resultの中にアップロードした内容が入ります。
たとえばこんなかんじ:

>"results":[
>{"gclid":"ABcdefjHIjklmnOPqrstuVWxyz_1234567890",
>"conversionAction":"customers/1234567890/conversionActions/123456789",
>"conversionDateTime":"2021-11-01 15:01:11+09:00"},
>{"gclid":"1212ABcdefjHI3434-jklmn5656OPqrstu_789VWxyz0",
>"conversionAction":"customers/1234567890/conversionActions/123456789",
>"conversionDateTime":"2021-11-01 18:24:54+09:00"}}]

gclidとDateTimeを見てアップロード済みかどうかを判別しているようですね。

#アップロード確認

YahooAPIでのオフラインコンバージョンアップロードでは、
プログラム内でCSVファイルを作成⇒CSVファイルをアップロード
という手順を踏むためか、
アップロードした内容が管理画面で確認できるのですが。

Googleは直接アップロードするため、
アップロード内容が管理画面では確認できないようです。

担当者のかたによると、半日くらいかけてじわじわと反映されていくらしく、
コンバージョン値が反映されているかをじわじわ確認していくしかないとのことでした。

**がんばれ。。。。**

#おわりに

GoogleAPIも11月でV9がリリースされました。
β版のV1から触ってる身としては、感慨深いものがあります。

GoogleAPIとYahooAPIをあれこれ調べまくって試した結果、
「APIってこんなかんじ」というのがなんとなく分かってきた気がします。
まだぜんぜんへなちょこですが。

「これをすれば、あとはスムーズにできる」というのがなんとなくわかる。

APIの知識など完全ゼロベースから始めたことを考えると、
けっこう成長できたのかなと思います。

何事も継続は力なり、です。

次じつはFacebookAPIについての調査などが始まるのですが、
なんとか乗り越えていきたいと！
思っております！

おわり！！