Google Apps 脚本:从亚马逊销售合作伙伴 API 获取订单(签名请求)

2024-01-05

我正在尝试在此之后创建对亚马逊销售合作伙伴 API 的请求guide https://github.com/amzn/selling-partner-api-docs/blob/main/guides/developer-guide/SellingPartnerApiDeveloperGuide.md#step-2-construct-a-selling-partner-api-uri.

第一部分:创建访问权限已经完成here https://stackoverflow.com/questions/66450176/google-apps-script-connecting-to-amazon-selling-partner-api-access-token/.

订单 API 的文档可以找到here https://github.com/amzn/selling-partner-api-docs/blob/main/references/orders-api/ordersV0.md.

我正在尝试调用GET /orders/v0/orders手术。

连接到 API

此操作的唯一强制参数是MarketplaceIds根据文档。

为了获得订单,我们需要签署我们的请求。到目前为止,这是我的代码:

function GetOrders(){
  var access_token = AccessToken();

  //Time variables
  var currentDate = new Date();
  var isoDate = currentDate.toISOString();
  var yearMonthDay= Utilities.formatDate(currentDate, 'GTM-5', 'yyyyMMdd');

  //API variables
  var end_point = 'https://sellingpartnerapi-eu.amazon.com';

  //Credential variables
  var aws_region = "eu-west-1";
  var service = "execute-api";
  var termination_string = "aws4_request";

  //CanonicalRequest = httpRequestMethod + '\n' + CanonicalURI + '\n' + CanonicalQueryString + '\n' + CanonicalHeaders + '\n' + SignedHeaders + '\n' + HexEncode(Hash(RequestPayload));
  //CanonicalRequest components:
  var httpRequestMethod = 'GET';
  var canonicalURI = '/orders/v0/orders';
  var canonicalQueryString = '?marketplaceId=A1PA6795UKMFR9';
  var canonicalheaders = 'host:' + canonicalURI + '\n' + 'x-amz-access-token:' + access_token + '\n' + 'x-amz-date:' + isoDate;
  var signedheaders = 'host;user-agent;x-amz-access-token;x-amz-date';
  var requestPayloadHashed = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, "");//NEW
  requestPayloadHashed = requestPayloadHashed.map(function(e) {return ("0" + (e < 0 ? e + 256 : e).toString(16)).slice(-2)}).join("");//NEW

  //Building the canonical request
  var canonical_string = httpRequestMethod + '\n' + canonicalURI + '\n' + canonicalQueryString + '\n' + canonicalheaders + '\n' + signedheaders + '\n' + requestPayloadHashed;//UPDATED
  var canonical_signature = Utilities.computeHmacSha256Signature(canonical_string, ACCESS_KEY);
  var canonical_request = canonical_string + '\n' + canonical_signature;
  canonical_request = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, canonical_request);//NEW

  //CredentialScope = Date + AWS region + Service + Termination string;
  //StringToSign = Algorithm + \n + RequestDateTime + \n + CredentialScope + \n + HashedCanonicalRequest;
  var credential_scope = yearMonthDay + '/' + aws_region + '/' + service + '/' + termination_string;
  var string_to_sign = "AWS4-HMAC-SHA256" + '\n' + isoDate + '\n' + credential_scope + '\n' + canonical_request;

  var kSecret = ACCESS_KEY;
  var kDate = Utilities.computeHmacSha256Signature(yearMonthDay, "AWS4" + kSecret);
  var kRegion = Utilities.computeHmacSha256Signature(Utilities.newBlob(aws_region).getBytes(), kDate);
  var kService = Utilities.computeHmacSha256Signature(Utilities.newBlob(service).getBytes(), kRegion);
  var kSigning = Utilities.computeHmacSha256Signature(Utilities.newBlob(termination_string).getBytes(), kService);
  kSigning = kSigning.map(function(e) {return ("0" + (e < 0 ? e + 256 : e).toString(16)).slice(-2)}).join("");
  Logger.log('kSigning: ' + kSigning);

  var signature = Utilities.computeHmacSha256Signature(kSigning, string_to_sign);
  signature = signature.map(function(e) {return ("0" + (e < 0 ? e + 256 : e).toString(16)).slice(-2)}).join("");

  var options = {
    'method': 'GET',
    'payload': {
      'end_point': end_point,
      'path': canonicalURI,
      'query_string': canonicalQueryString
      //Path parameter not needed
    },
    'headers': {
      //'host': end_point,
      'x-amz-access-token': access_token,
      'x-amz-date': isoDate,
      'user-agent': 'GAS Script 1.0 (Javascript)',
      'Authorization': 'AWS4-HMAC-SHA256 Credential=' + ACCESS_ID + '/' + credential_scope + ', SignedHeaders=' + signedheaders + ', Signature=' + signature,
    },
  }
  
  var getOrders = UrlFetchApp.fetch(end_point, options);
  Logger.log(getOrders);
}

PROBLEMS

运行脚本时,我收到以下错误:

    Exception: Request failed for https://sellingpartnerapi-eu.amazon.com returned code 403. Truncated server response: {
{
  "errors": [
    {
      "message": "The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.

The Canonical String for this request should have been
'POST
/

host:sellingpartnerapi-eu.amazon.com
user-agent:Mozilla/5.0 (compatible; Google-Apps-Script; beanserver; +https://script.google.com; id: UAEmdDd-KyWEWcR137UzUzWb1fu3rUgNviHA)
x-amz-access-token:Atza|IwEBSomeAccessToken
x-amz-date:2021-03-10T02:44:01.727Z

host;user-agent;x-amz-access-token;x-amz-date
cf22942946358a7530d8b72df6333e859644aaebb08a1cd825a6af65a8561111'

The String-to-Sign should have been
'AWS4-HMAC-SHA256
20210310T024401Z
20210310/eu-west-1/execute-api/aws4_request
c4c1dcea7026765f52c5265296f9e1cb91b6618928debbc04a393bac89ce8493'
",
     "code": "InvalidSignature"
    }
  ]
}

问题

我对什么是“有效负载”有很大疑问

对于这部分代码:

var canonical_string = httpRequestMethod + '\n' + canonicalURI + '\n' + canonicalQueryString + '\n' + canonicalheaders + '\n' + signedheaders + '\n' + requestPayloadHashed;

我们必须合并有效负载请求的哈希版本requestPayloadHashed.

它还提到:

如果有效负载为空,则使用空字符串作为哈希的输入 功能。

现在我刚刚创建了具有空白值的变量

var requestPayloadHashed = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, "");//NEW
      requestPayloadHashed = requestPayloadHashed.map(function(e) {return ("0" + (e < 0 ? e + 256 : e).toString(16)).slice(-2)}).join("");//NEW

但我不确定我是否遗漏了一些重要的东西。

更新#1

应用田池建议后,我收到以下消息:

{
  "errors": [
    {
      "message": "The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.

The Canonical String for this request should have been
'GET
/orders/v0/orders
marketplaceId=A1PA6795UKMFR9
host:sellingpartnerapi-eu.amazon.com
user-agent:Mozilla/5.0 (compatible; Google-Apps-Script; beanserver; +https://script.google.com; id: UAEmdDd-KyWEWcR137UzUzWb1fu3rUgNviHA)
x-amz-access-token:Atza|IwEBISomeAccessToken
x-amz-date:2021-03-10T03:00:14.411Z

host;user-agent;x-amz-access-token;x-amz-date
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'

The String-to-Sign should have been
'AWS4-HMAC-SHA256
20210310T030014Z
20210310/eu-west-1/execute-api/aws4_request
f1bbc99190ca5a9e9e068ad6a0b2ef6a7aed4a1232095ef8f3d77ad62d0e66ac'
",
     "code": "InvalidSignature"
    }
  ]
}

更新#2有一个网站可以帮助我们对这些连接进行一些测试:

https://mws.amazonservices.de/scratchpad/index.html https://mws.amazonservices.de/scratchpad/index.html

通过使用它,我相信我已经验证了Access Key ID and Secret Key,但是,它要求提供一个 SellerId,这对我来说是新的,并且在API docs https://github.com/amzn/selling-partner-api-docs/blob/main/references/orders-api/ordersV0.md.

我想知道它可以去哪里。

更新#3

我实施了大部分 Tanaike 建议,并尝试将发送到 API 的内容与收到的错误消息保持一致:

这是该脚本的最后一个版本:

function GetOrders(){
  var access_token = AccessToken();

  //Time variables
  var currentDate = new Date();
  var isoDate = currentDate.toISOString();
  var isoString = isoDate.replace(/-/g, "").replace(/:/g, "").replace(/(\.\d{3})/, "");
  var yearMonthDay= Utilities.formatDate(currentDate, 'GTM-5', 'yyyyMMdd');
  Logger.log('isoDate: ' + isoDate)
  //API variables
  var end_point = 'https://sellingpartnerapi-eu.amazon.com';

  //Credential variables
  var aws_region = "eu-west-1";
  var service = "execute-api";
  var termination_string = "aws4_request";

  //CanonicalRequest = httpRequestMethod + '\n' + CanonicalURI + '\n' + CanonicalQueryString + '\n' + CanonicalHeaders + '\n' + SignedHeaders + '\n' + HexEncode(Hash(RequestPayload));
  //CanonicalRequest components:
  var httpRequestMethod = 'GET';
  var canonicalURI = '/orders/v0/orders';
  var canonicalQueryString = '?marketplaceId=A1PA6795UKMFR9';
  var canonicalheaders = 'host:' + "sellingpartnerapi-eu.amazon.com" + '\n' + 'x-amz-access-token:' + access_token + '\n' + 'x-amz-date:' + isoDate;
  var signedheaders = 'host;x-amz-access-token;x-amz-date';//;user-agent
  var requestPayloadHashed = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, "");//NEW
  requestPayloadHashed = requestPayloadHashed.map(function(e) {return ("0" + (e < 0 ? e + 256 : e).toString(16)).slice(-2)}).join("");//NEW

  //Building the canonical request
  var canonical_string = httpRequestMethod + '\n' + canonicalURI + '\n' + "marketplaceId=A1PA6795UKMFR9" + '\n' + canonicalheaders + '\n\n' + signedheaders + '\n' + requestPayloadHashed;//UPDATED
  Logger.log('canonical_string: ' + canonical_string)
  var canonical_signature = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, canonical_string);
  canonical_request = canonical_signature.map(function(e) {return ("0" + (e < 0 ? e + 256 : e).toString(16)).slice(-2)}).join("");
  Logger.log("canonical_request: " + canonical_request)

  //CredentialScope = Date + AWS region + Service + Termination string;
  //StringToSign = Algorithm + \n + RequestDateTime + \n + CredentialScope + \n + HashedCanonicalRequest;
  var credential_scope = yearMonthDay + '/' + aws_region + '/' + service + '/' + termination_string;
  var string_to_sign = "AWS4-HMAC-SHA256" + '\n' + isoString + '\n' + credential_scope + '\n' + canonical_request;
  Logger.log("string_to_sign: " + string_to_sign);
  var kSecret = ACCESS_KEY;
  var kDate = Utilities.computeHmacSha256Signature(yearMonthDay, "AWS4" + kSecret);
  var kRegion = Utilities.computeHmacSha256Signature(toBytes(aws_region), kDate);
  var kService = Utilities.computeHmacSha256Signature(toBytes(service), kRegion);
  var kSigning = Utilities.computeHmacSha256Signature(toBytes(termination_string), kService);
  Logger.log('kSigning: ' + kSigning);

  var signature = hex(Utilities.computeHmacSha256Signature(toBytes(string_to_sign), kSigning));
  Logger.log('signature: ' + signature)
  var options = {
    'method': 'GET',
    'headers': {
      //'host': end_point,
      'x-amz-access-token': access_token,
      'x-amz-date': isoDate,
      //'user-agent': 'Mozilla/5.0 (compatible; Google-Apps-Script; beanserver; +https://script.google.com; id: UAEmdDd-KyWEWcR137UzUzWb1fu3rUgNviHA)',
      'Authorization': 'AWS4-HMAC-SHA256 Credential=' + ACCESS_ID + '/' + credential_scope + ', SignedHeaders=' + signedheaders + ', Signature=' + signature,
    },
    'muteHttpExceptions': true
  }
  
  var getOrders = UrlFetchApp.fetch(end_point + canonicalURI + canonicalQueryString, options);
  Logger.log(getOrders);
}

我现在收到完全与我的访问相关的错误:

{
  "errors": [
    {
      "message": "Access to requested resource is denied.",
     "code": "Unauthorized",
     "details": ""
    }
  ]
}

然而,这可能是因为当我注册应用程序时(指南here https://github.com/amzn/selling-partner-api-docs/blob/main/guides/developer-guide/SellingPartnerApiDeveloperGuide.md#step-6-register-your-application)我使用 IAM 用户而不是 IAM 角色。

指南中说:

重要的。注册您的应用程序时,您注册的 IAM ARN 必须为您附加 IAM 的 IAM 实体提供 步骤 3. 创建 IAM 策略中的策略。在此工作流程中,IAM 实体是步骤 4. 创建 IAM 角色中的 IAM 角色。如果你 使用您的 IAM 用户注册您的应用程序,请确保 IAM 政策附在其上。否则您致电销售合作伙伴 API 将失败。我们建议使用 IAM 注册您的应用程序 角色,如此工作流程中所示,可帮助您更好地控制对 您的 AWS 资源。

因此,我将继续解决该问题,看看是否获得了所需的授权。


修改要点:

  • 如果是UrlFetchApp, when payload被使用,即使当method is GET,它被请求为 POST 请求。看来这是当前的规范。
  • user-agent不能更改为UrlFetchApp.

作为前提条件,当您的授权值是向端点请求的正确值时,您的脚本可以按照上述几点进行修改,如下所示。

我认为您的错误消息可能是由于“GET”和“POST”方法之间的差异造成的。首先,请测试以下修改。当出现错误时,请显示出来。

修改后的脚本:

From:
var options = {
  'method': 'GET',
  'payload': {
    'end_point': end_point,
    'path': canonicalURI,
    'query_string': canonicalQueryString
    //Path parameter not needed
  },
  'headers': {
    //'host': end_point,
    'x-amz-access-token': access_token,
    'x-amz-date': isoDate,
    'user-agent': 'GAS Script 1.0 (Javascript)',
    'Authorization': 'AWS4-HMAC-SHA256 Credential=' + ACCESS_ID + '/' + credential_scope + ', SignedHeaders=' + signedheaders + ', Signature=' + signature,
  },
}

var getOrders = UrlFetchApp.fetch(end_point, options);
To:
var options = {
  'method': 'GET',
  'headers': {
    'x-amz-access-token': access_token,
    'x-amz-date': isoDate,
    'Authorization': 'AWS4-HMAC-SHA256 Credential=' + ACCESS_ID + '/' + credential_scope + ', SignedHeaders=' + signedheaders + ', Signature=' + signature,
  },
}

var getOrders = UrlFetchApp.fetch(end_point + canonicalURI + canonicalQueryString, options);

参考:

  • UrlFetchApp 类的 fetch(url, params) https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app#fetchurl,-params

Added:

From 使用签名版本 4 签署 AWS 请求 https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html,我修改了你的脚本。当我看到你的脚本时,我注意到字节数组包含在字符串值中。我认为这可能也是您问题的原因之一。所以我修改了你的脚本。能否请您确认一下?而且当我看到官方文档时,我确认当字节数组用于Utilities.computeHmacSha256Signature,将字符串值转换为字节数组与样本相同,而不是将字节数组转换为字符串值。

function sample() {
  const hex = bytes => bytes.map(byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
  const digestToHex = data => hex(Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, data));
  const toBytes = data => Utilities.newBlob(data).getBytes();

  const ACCESS_ID = "MyAccessKey";
  const ACCESS_KEY = "MyAccessSecret";
  var access_token = "access_token"; // AccessToken();

  //Time variables
  var currentDate = new Date();
  var isoDate = currentDate.toISOString();
  var yearMonthDay= Utilities.formatDate(currentDate, 'GTM-5', 'yyyyMMdd');

  //API variables
  var end_point = 'https://sellingpartnerapi-eu.amazon.com';

  //Credential variables
  var aws_region = "eu-west-1";
  var service = "execute-api";
  var termination_string = "aws4_request";

  // 1. Create string to sign.
  var httpRequestMethod = 'GET';
  var canonicalURI = '/orders/v0/orders';
  var canonicalQueryString = '?marketplaceId=A1PA6795UKMFR9';
  var canonicalheaders = 'host:' + canonicalURI + '\n' + 'x-amz-access-token:' + access_token + '\n' + 'x-amz-date:' + isoDate;
  var signedheaders = 'host;user-agent;x-amz-access-token;x-amz-date';
  const canonicalRequest = [httpRequestMethod,canonicalURI,canonicalQueryString,canonicalheaders + "\n",signedheaders,digestToHex("")].join("\n");
  const canonical_request = digestToHex(canonicalRequest);
  var credential_scope = yearMonthDay + '/' + aws_region + '/' + service + '/' + termination_string;
  var string_to_sign = "AWS4-HMAC-SHA256" + '\n' + isoDate + '\n' + credential_scope + '\n' + canonical_request;

  // 2. Create derived signing key.
  var kSecret = ACCESS_KEY;
  var kDate = Utilities.computeHmacSha256Signature(yearMonthDay, "AWS4" + kSecret);
  var kRegion = Utilities.computeHmacSha256Signature(toBytes(aws_region), kDate);
  var kService = Utilities.computeHmacSha256Signature(toBytes(service), kRegion);
  var kSigning = Utilities.computeHmacSha256Signature(toBytes(termination_string), kService);

  // 3. Create signature.
  const signature = hex(Utilities.computeHmacSha256Signature(toBytes(string_to_sign), kSigning));

  // 4. Request.
  var options = {
    'method': 'GET',
    'headers': {
      'x-amz-access-token': access_token,
      'x-amz-date': isoDate,
      'Authorization': 'AWS4-HMAC-SHA256 Credential=' + ACCESS_ID + '/' + credential_scope + ', SignedHeaders=' + signedheaders + ', Signature=' + signature,
    },
  }
  var getOrders = UrlFetchApp.fetch(end_point + canonicalURI + canonicalQueryString, options);
  Logger.log(getOrders);
}

Note:

  • 不幸的是,我无法测试上面修改后的脚本。所以当您测试出现错误时,请确认您的值并重新授权。并请显示错误消息。

  • 在此修改中,它假设您的授权值是正确的值。请小心这一点。

  • About const canonicalRequest = [httpRequestMethod,canonicalURI,canonicalQueryString,canonicalheaders + "\n",signedheaders,digestToHex("")].join("\n");,当上面脚本出现错误时,请测试const canonicalRequest = [httpRequestMethod,canonicalURI,canonicalQueryString,canonicalheaders + "\n",""].join("\n");.

参考:

  • 使用签名版本 4 签署 AWS 请求 https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Google Apps 脚本:从亚马逊销售合作伙伴 API 获取订单(签名请求) 的相关文章

随机推荐