NAME

  Bing::ContentAPI - Perl interface to the Bing Ads Content API

DESCRIPTION

  Add, modify and delete products from the Bing Merchant Center platform via
  the Bing Ads Content API.

  https://docs.microsoft.com/bingads/shopping-content/

  Authentication is done via OAuth using Authorization Code Grant Flow
  https://docs.microsoft.com/bingads/guides/authentication-oauth

  Steps to authorize a native application and generate an initial refresh token:

  Note: Substitute CLIENT_ID, UNIQUE_CODE and AUTHORIZATION_CODE with the
  actual values in the appropriate locations in the examples.

  1) Request user consent through web browser:
  (using existing Bing Ads Microsoft account when prompted)

  https://login.live.com/oauth20_authorize.srf?client_id=CLIENT_ID&scope=bingads.manage&response_type=code&redirect_uri=https://login.live.com/oauth20_desktop.srf&state=UNIQUE_CODE

  Response received:
  https://login.live.com/oauth20_desktop.srf?code=AUTHORIZATION_CODE&state=UNIQUE_CODE&lc=1033

  2) Use the authorization 'code' received to request the access token and refresh token:

  $ curl \
    -d client_id=CLIENT_ID \
    -d code=AUTHORIZATION_CODE \
    -d grant_type=authorization_code \
    -d redirect_uri=https://login.live.com/oauth20_desktop.srf \
    -H "Content-Type: application/x-www-form-urlencoded" \
    https://login.live.com/oauth20_token.srf

  Response:
  {
    "token_type": "bearer",
    "expires_in": 3600,
    "scope": "bingads.manage",
    "access_token": "...",
    "refresh_token": "...",
    "user_id": "..."
  }

  3) Use the received refresh_token as the initial refresh_token in Bing::ContentAPI->new()

SYNOPSIS

  use Bing::ContentAPI;
  use Data::Dumper;

  my $bing = Bing::ContentAPI->new({
    debug => 0,
    redirect_uri    => 'https://login.live.com/oauth20_desktop.srf',
    merchant_id     => '12345',          # merchant_id is the BMC store ID
    developer_token => '123ABC456DEF789',
    client_id       => '1234abcd-5679-efgh-123456789',
    refresh_token   => load_token(),   # previously saved refresh token
  });
  save_token($bing->{refresh_token}); # save new refresh token

  sub load_token {
    my $token;
    # load token from storage
    return $token;
  }

  sub save_token {
    my $token = shift;
    # save token to storage
  }

  my ($request, $result, $products, $batch_id, $product_id);

  # list products

  my $nextPageToken = '';
  do {
    $request = {
      resource => 'products',
      method   => 'list',
      params   => ['max-results' => 250],
    };
    push @{$request->{params}}, ('start-token', "$nextPageToken") if $nextPageToken ne '';

    $result = $bing->get(%$request);
    $nextPageToken = $result->{response}->{nextPageToken} || '';

    print "$result->{code} ". ($result->{code} eq '200' ? 'success' : 'failure') ."\n";
    print "Products list: \n". Dumper $result;
  } while ($nextPageToken ne '');

  # list catalogs

  $result = $bing->get(
    resource => 'catalogs',
    method   => 'list',
  );
  print "$result->{code} ". ($result->{code} eq '200' ? 'success' : 'failure') ."\n";
  print "Catalogs list: \n". Dumper $result;

  # get status of product offers in a catalog

  my $catalogID = 123456;
  $result = $bing->get(
    resource => 'catalogs',
    method   => 'status',
    id       => $catalogID,
  );
  print "$result->{code} ". ($result->{code} eq '200' ? 'success' : 'failure') ."\n";
  print "Catalog status: \n". Dumper $result;

  # insert a product

  $result = $bing->post(
    resource => 'products',
    method   => 'insert',
    dryrun   => 1,
    body => {
      contentLanguage => 'en',
      targetCountry => 'US',
      channel => 'online',
      offerId => '333333',
      title => 'Item title',
      description => 'The item description',
      link => 'http://www.bing.com',
      imageLink => 'https://img-prod-cms-rt-microsoft-com.akamaized.net/cms/api/am/imageFileData/RE1Mu3b',
      availability => 'in stock',
      condition => 'new',
      price => {
          value => '99.95',
          currency => 'USD',
      },
      shipping => [
        {
          country => 'US',
          service => 'Standard Shipping',
          price => {
            value => '7.95',
            currency => 'USD',
          },
        },
      ],
      brand => 'Apple',
      gtin => '33333367890',
      mpn => '333333',
      googleProductCategory => 'Home & Garden > Household Supplies > Household Paper Products > Paper Towels',
      productType => 'Home & Garden > Household Supplies > Household Paper Products > Paper Towels',
      customLabel1 => 'Paper Towels'
    }
  );
  print "$result->{code} ". ($result->{code} eq '200' ? 'success' : 'failure') ."\n";

  # get single product info

  $product_id = '333333';
  $result = $bing->get(
    resource => 'products',
    method   => 'get',
    id       => 'online:en:US:'. $product_id
  );
  print "$result->{code} ". ($result->{code} eq '200' ? 'success' : 'failure') ."\n";
  print "Products info: \n". Dumper $result;

  # delete a product

  print "product delete: ";

  my $del_product_id = '333333';
  $result = $bing->delete(
    resource => 'products',
    method   => 'delete',
    id       => 'online:en:US:'. $del_product_id,
    dryrun   => 1,
  );
  print "$result->{code} ". ($result->{code} eq '204' ? 'success' : 'failure') ."\n"; # 204 = delete success
  print Dumper $result;

  # batch insert

  $products = [];
  $batch_id = 0;

  foreach my $i ('211203'..'211205') {
    push @$products, {
      batchId => ++$batch_id,
      merchantId => $bing->{merchant_id},
      method => 'insert', # insert / get / delete
      #productId => '', # for get / delete
      product => { # for insert
        contentLanguage => 'en',
        targetCountry => 'US',
        channel => 'online',
        offerId => "$i",
        title => "item title $i",
        description => "The item description for $i",
        link => 'http://www.bing.com',
        imageLink => 'https://img-prod-cms-rt-microsoft-com.akamaized.net/cms/api/am/imageFileData/RE1Mu3b',
        availability => 'in stock',
        condition => 'new',
        price => {
          value => '10.95',
          currency => 'USD',
        },
        shipping => [
          {
            country => 'US',
            service => 'Standard Shipping',
            price => {
              value => '7.95',
              currency => 'USD',
            },
          },
        ],
        brand => 'Apple',
        gtin => "${i}67890",
        mpn => "$i",
        googleProductCategory => 'Home & Garden > Household Supplies > Household Paper Products > Paper Towels',
        productType => 'Home & Garden > Household Supplies > Household Paper Products > Paper Towels',
        customLabel1 => 'Paper Towels'
      }
    };
  }

  $result = $bing->post(
    resource => 'products',
    method   => 'batch',
    dryrun   => 1,
    body => { entries => $products }
  );
  print "$result->{code} ". ($result->{code} eq '200' ? 'success' : 'failure') ."\n";

  # batch get

  $products = [];
  $batch_id = 0;
  foreach my $product_id ('211203'..'211209') {
    push @$products, {
      batchId    => ++$batch_id,
      merchantId => $bing->{merchant_id},
      method     => 'get', # insert / get / delete
      productId  => 'online:en:US:'. $product_id, # for get / delete
    };
  }

  $result = $bing->post(
    resource => 'products',
    method   => 'batch',
    dryrun   => 1,
    body => { entries => $products }
  );
  print "$result->{code} ". ($result->{code} eq '200' ? 'success' : 'failure') ."\n";
  print Dumper $result;

  # batch delete

  $products = [];
  $batch_id = 0;
  foreach my $product_id ('211203'..'211205') {
    push @$products, {
      batchId    => ++$batch_id,
      merchantId => $bing->{merchant_id},
      method     => 'delete', # insert / get / delete
      productId  => 'online:en:US:'. $product_id, # for get / delete
    };
  }

  $result = $bing->post(
    resource => 'products',
    method   => 'batch',
    dryrun   => 1,
    body => { entries => $products }
  );
  print "$result->{code} ". ($result->{code} eq '200' ? 'success' : 'failure') ."\n";

METHODS AND FUNCTIONS

new()

  Create a new Bing::ContentAPI object

debug

  Displays API debug information

merchant_id

  Merchant ID is the Bing Merchant Center Store ID
  https://bingads.microsoft.com/

developer_token

  Developer token from https://developers.bingads.microsoft.com/Account

client_id

  Client ID is the Application ID value in "Registering Your Application":
  https://docs.microsoft.com/bingads/guides/authentication-oauth#registerapplication

redirect_uri

  If you registered a native application, use "https://login.live.com/oauth20_desktop.srf"
  as the redirect URI. If you registered a web application, use the redirect URI you
  specified in "Registering Your Application".

refresh_token

  The current refresh token

refresh_access_token()

  Using the current refresh_token, obtain a new access and refresh token

access_token

  returns access_token obtained via refresh_access_token()

refresh_token

  returns refresh_token obtained via refresh_access_token()

PRODUCTS

batch

  Retrieves, inserts, and deletes multiple products in a single request.

insert

  Uploads a product to your Merchant Center account. If an item with the
  same channel, contentLanguage, offerId, and targetCountry already exists,
  this method updates that entry.

list

  Lists the products in your Merchant Center account.

get

  Retrieves a product from your Merchant Center account.

delete

  Deletes a product from your Merchant Center account.

CATALOGS

list

  Lists the catalogs in your Merchant Center Account.

status

  Lists the status and issues of products offers in your Merchant Center Account.

UNIMPLEMENTED FEATURES

  Certain API methods are not yet implemented (no current personal business need).

  A "custom" resource is available to perform methods that are not implemented by
  this module.

  $result = $bing->get(
    resource => 'custom',
    method   => 'merchantId/orders/orderId'
  );

PREREQUISITES

  JSON
  REST::Client
  HTML::Entities

AUTHOR

  Original Author
  Bill Gerrard <bill@gerrard.org>

COPYRIGHT AND LICENSE

  Copyright (C) 2018 Bill Gerrard

  This library is free software; you can redistribute it and/or modify
  it under the same terms as Perl itself, either Perl version 5.20.2 or,
  at your option, any later version of Perl 5 you may have available.
  Disclaimer of warranty: This program is provided by the copyright holder
  and contributors "As is" and without any express or implied warranties.
  The implied warranties of merchantability, fitness for a particular purpose,
  or non-infringement are disclaimed to the extent permitted by your local
  law. Unless required by law, no copyright holder or contributor will be
  liable for any direct, indirect, incidental, or consequential damages
  arising in any way out of the use of the package, even if advised of the
  possibility of such damage.