広告のクリックを経由したコンバージョンデータをGoogle広告にインポートする

# はじめに
![customLogo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239829/ba3caee3-ca99-2d6d-f935-7d27b7df1b47.png)

Google Adwords APIは2020年を目処に廃止されるとの予定でしたが、
Google Ads APIの正式ローンチが大幅に遅れているらしく今年中に廃止されることはないらしいです。
https://developer.feedforce.jp/entry/2019/05/15/131054

Google Ads APIドキュメントもまだ未公開で閲覧できないため、
Google Adwords APIでコンバージョンデータをGoogleAdsにインポートする方法を試してみました。

# 全体の流れ
公式ドキュメント([広告のクリックを経由したコンバージョンのデータを Google 広告にインポートする](https://support.google.com/google-ads/answer/7014069?hl=ja&ref_topic=7280668))に記載されている内容をまとめると大きく3つ。

- 広告のURLに追加されるGCLID（Google クリック ID）付与の設定
- コンバージョンアクションの設定
- GoogleAdsへコンバージョンデータのインポート

# 広告のURLに追加されるGCLID（GoogleクリックID）付与の設定
コンバージョンデータとの関連が必要なのでgclidは独自システムなどで保持する必要があります。

- gclidをDBに保存するための変更作業
- フロントエンド側からgclidの取得およびDBへ保存

# コンバージョンアクション設定(GoogleAdsUI)
- [オフライン コンバージョンのトラッキングを設定する](https://support.google.com/google-ads/answer/7012522)

※コンバージョンアクションはAPIからも設定可能ですが、今回はコンバージョンデータの定期インポートを想定しているので管理画面から設定しています。
# GoogleAdsへコンバージョンデータのインポート

インポート方法は大きく3種類

- HTTPS/SFTP による定期取得 
- スプレッドシートを利用した定期取得
- AdWords API による自動送信

今回はAdWordsAPIを使ったAPI実装を行います。

[公式ドキュメント](https://developers.google.com/adwords/api/docs/samples/ruby/remarketing#import-offline-click-conversions)に記載されている内容を少々変更。

```ruby

require 'adwords_api'

class UploadOfflineConversion
  def initialize
    @adwords = AdwordsApi::Api.new
  end

  def create_conversion_feed(conversion_name:, google_click_id:, conversion_time:, conversion_value:)
    upload_offline_conversions(conversion_name, google_click_id, conversion_time, conversion_value.to_f)

  rescue AdsCommon::Errors::OAuth2VerificationRequired => e
    puts "Authorization credentials are not valid. Edit adwords_api.yml for OAuth2 client ID and secret and run misc/setup_oauth2.rb example to retrieve and store OAuth2 tokens."
    puts "See this wiki page for more details:\n\n https://github.com/googleads/google-api-ads-ruby/wiki/OAuth2"

  rescue AdsCommon::Errors::HttpError => e
    puts "HTTP Error: %s" % e

  rescue AdwordsApi::Errors::ApiException => e
    puts "Message: %s" % e.message
    puts 'Errors:'
    e.errors.each_with_index do |error, index|
      puts "\tError [%d]:" % (index + 1)
      error.each do |field, value|
        puts "\t\t%s: %s" % [field, value]
      end
    end
  end

  def upload_offline_conversions(conversion_name, google_click_id, conversion_time, conversion_value.to_f)
    conversion_feed_srv = @adwords.service(:OfflineConversionFeedService, :v201809)

    feed = {
      conversion_name: conversion_name,
      google_click_id: google_click_id,
      conversion_time: conversion_time,
      conversion_value: conversion_value
    }

    return_feeds = conversion_feed_srv.mutate([ { operator: 'ADD', operand: feed } ])
    return_feeds[:value].each do |return_feed|
      puts ("Uploaded offline conversion value %.2f for Google Click ID '%s', " + 'to %s') % [return_feed[:conversion_value], return_feed[:google_click_id], return_feed[:conversion_name]]
    end
  end
```
最低限必要なのは以下4項目。

- `conversion_name`: コンバージョンアクションで設定した名称
- `google_click_id`: URLに付与されるgclid
- `conversion_time`: コンバージョン時間
- `conversion_value`: 独自の値(値段 or 数量 or etc)


[Uploading offline conversions](https://developers.google.com/adwords/api/docs/guides/conversion-tracking#uploading_offline_conversions)
`OfflineConversionFeedService`は以下のオブジェクトを保持しています。

- operand: [OfflineConversionFeed]
(https://developers.google.com/adwords/api/docs/reference/v201809/OfflineConversionFeedService.OfflineConversionFeed)
- operator: [OfflineConversionFeedOperation](https://developers.google.com/adwords/api/docs/reference/v201809/OfflineConversionFeedService.OfflineConversionFeedOperation)
- return_feeds: [OfflineCallConversionFeedReturnValue](https://developers.google.com/adwords/api/docs/reference/v201809/OfflineConversionFeedService.OfflineConversionFeedReturnValue)

インポートされたデータの反映までは3時間かかるみたいです。

# バリデーション
`OfflineCallConversionFeedService`のAPIエラー([一覧](https://developers.google.com/adwords/api/docs/reference/v201809/OfflineCallConversionFeedService#errors_1))は数多くありますが、実装中に実際に遭遇したエラーだけ記載しようと思います。
## INVALID_STRING_DATE_TIME
> The string date time’s format should be yyyymmdd hhmmss [tz].

`conversion_time`は`:の除外`と`タイムゾーン`を含めた文字列にする。

## EXPIRED_CLICK
> This click is either too old to be imported or occurred before the conversion window for the specified combination of conversion date and conversion name (default is 90 days).

コンバージョンアクションで設定した有効期限日数より以前のコンバージョン(`conversion_time`)はインポートできない。
## UNPARSEABLE_GCLID
> This google click ID could not be decoded.

実際に発行されたgclid以外は無効。
## TOO_RECENT_CONVERSION_TYPE
> This conversion action was created too recently. Please wait for 4 hours and try uploading again.

管理画面で作成したコンバージョンアクションを作成してから4時間経ってからやり直す。

## INVALID_CONVERSION_TYPE
> This customer does not have an import conversion with a name that matches the label of this conversion.

指定したコンバージョンアクションが登録されてない。
`Google広告 > ツール > コンバージョン > コンバージョンアクション`の一覧で
ソース項目が`クリックからのインポート`が対象。(既存のコンバージョンアクション(ソースがウェブサイト)を指定してもうまく行かず詰まりました..)

## CONVERSION_PRECEDES_CLICK
> This conversion is reported to have happened before the click.

コンバージョン時間 > 広告クリック時間の場合に発生。
なんでこの状況が起こるかわからないが計測側の問題なのかもしれない..?

## ATTRIBUTED_CREDIT_NOT_SET_FOR_EXTERNALLY_ATTRIBUTED_CONVERSION_ACTION
> This conversion action is using an external attribution model, but the attributed credit is not set.

コンバージョンアクションの`アトリビューションモデル`を`外部のアトリビューションを使用`を選択したため、`external_attribution_credit`に貢献値の指定が必要。
## ATTRIBUTION_MODEL_NOT_SET_FOR_EXTERNALLY_ATTRIBUTED_CONVERSION_ACTION
> This conversion action is using an external attribution model, but the attribution model name is not set.

コンバージョンアクションの`アトリビューションモデル`を`外部のアトリビューションを使用`を選択したため、`external_attribution_model`にモデル名の指定が必要。