3. commercetools Composable API
9 minute read
Build Outcome
By the end of this section we will have:
- Used the commercetools auth API to retrieve an API access token
- Used the commercetools commerce API to add a Product Type
- Used the commercetools commerce API to add a test product
- Updated our Azure HTTP trigger function
- Updated the Marketplacer webhook destination to our Azure Function
- Created a product end to end using our webhook and Azure function
commercetools API Docs
While we will go through all the steps required in this tutorial, for a full overview of the commercetools APIs, please refer to these docs.Steps
Companion Video
The companion video for this section can be found here.Create an API Access Token
All calls to the commercetools API require an access token, this token can be obtained by making a call to the commercetools auth api which we will perform in this section.
Session Management
As commercetools access tokens expire in 48 hours (172800 seconds), the expectation in a production scenario would be to manage these tokens in some kind of session management service.
For this iteration of the tutorial, session management is out of scope, so we will be manually managing our access tokens.
In order to make a call to the auth API endpoint you will need the following information (you should have obtained these when you set up a commercetools api client):
projectKey
clientId
clientSecret
scope
- The scope in this example would be:
manage_project:{projectKey}
- The scope in this example would be:
region
(this is part of the API endpoint route)- For more information on regions refer to the commercetools docs
Warning
This information should be kept secure, and should not be shared publicly.1: We then make a POST request to the auth endpoint with the URL structured in the following way:
https://{clientID}:{clientSecret}@auth.{region}.commercetools.com/oauth/token?grant_type=client_credentials&scope={scope}
So with (made up) values, a fully formed example using cURL would be:
curl -XPOST https://abc1234:xyz789@auth.australia-southeast1.gcp.commercetools.com/oauth/token
?grant_type=client_credentials&scope=manage_project:my-example-project
This should return with our API access token:
{
"access_token": "VSfs9gBbUDWc-VA8-ae_-Te98b9rk7YO",
"token_type": "Bearer",
"expires_in": 172800,
"scope": "manage_project:my-example-project"
}
Retain and secure the access_token
as we will be using this in subsequent sections.
Session Management Redux
As previously mentioned we won’t be building session management into our solution, so when your access token expires, you will have to re-run the steps above to get a new token.Create a Product Type
Our first step in exercising the commercetools commerce API will be to create a Product Type. Product Types are a set of attributes that act as a template for a group of similar products.
Your commercetools project must have a Product Type before products can be created.
The JSON payload we’ll POST to the product-types
endpoint is shown below, (this is referred to as a ProductTypeDraft
in commercetools.)
{
"name": "Clothing",
"description": "All clothing products"
}
2: We can make a request to create this Product Type using cURL in the following way:
curl -XPOST https://api.{region}.commercetools.com/{projectKey}/product-types
-H "Content-Type: application/json"
-H "Authorization: Bearer {access_token}"
-d '{ "name": "Clothing", "description": "Al clothing products"}'
Note the use of the
projectKey
in the URL as well as theaccess_token
as bearer token in the Authorization header
If successful this should respond with:
{
"id": "9e1630ea-b9d7-467e-bc06-6f708a42ac9e",
"version": 1,
"versionModifiedAt": "2023-07-12T01:07:04.712Z",
"createdAt": "2023-07-12T01:07:04.712Z",
"lastModifiedAt": "2023-07-12T01:07:04.712Z",
"lastModifiedBy": {
"clientId": "abc123",
"isPlatformClient": false
},
"createdBy": {
"clientId": "abc123",
"isPlatformClient": false
},
"name": "Clothing",
"description": "All clothing products",
"classifier": "Complex",
"attributes": []
}
Retain the id
as we will be using this in subsequent sections.
Create a Product
While we will be creating products using our Azure function, (via the CreateCommerceToolsProductAsync
method), it’s worth testing this functionality with a direct call to the products endpoint.
The payload for our call is as follows:
{
"name": {
"en": "Mens standard T-Shirt"
},
"productType": {
"id": "9e1630ea-b9d7-467e-bc06-6f708a42ac9e"
},
"slug": {
"en": "mens-standard-tshirt"
}
}
Briefly stepping back to our Azure Function and looking at our CTProductCreateDto
, you can now see the parallels:
public partial class CTProductCreateDto
{
[JsonProperty("name")]
public Name Name { get; set; }
[JsonProperty("productType")]
public ProductType ProductType { get; set; }
[JsonProperty("slug")]
public Name Slug { get; set; }
}
public partial class Name
{
[JsonProperty("en")]
public string En { get; set; }
}
public partial class ProductType
{
[JsonProperty("id")]
public string Id { get; set; }
}
3: The call to the products
endpoint is now straightforward, the only point of note is that we are using the Product Type we generated in the prior section.
An example call to the product create API using cURL would be:
curl -XPOST https://api.{region}.commercetools.com/{projectKey}/products
-H "Content-Type: application/json"
-H "Authorization: Bearer {access_token}"
-d '{ "name": { "en": "Mens standard T-Shirt" }, "productType": { "id": "9e1630ea-b9d7-467e-bc06-6f708a42ac9e" }, "slug": { "en": "mens-standard-tshirt" } }'
A success response would be similar to the following:
{
"id": "6c27a757-54f9-4e20-8c16-494eb000f40e",
"version": 1,
"versionModifiedAt": "2023-07-04T04:03:20.628Z",
"lastMessageSequenceNumber": 1,
"createdAt": "2023-07-04T04:03:20.628Z",
"lastModifiedAt": "2023-07-04T04:03:20.628Z",
"lastModifiedBy": {
"clientId": "abc123",
"isPlatformClient": false
},
"createdBy": {
"clientId": "abc123",
"isPlatformClient": false
},
"productType": {
"typeId": "product-type",
"id": "a17c418a-26d6-4dd3-a7c3-1fad4c44b66d"
},
"masterData": {
"current": {
"name": {
"en": "Mens standard T-Shirt"
},
"categories": [],
"categoryOrderHints": {},
"slug": {
"en": "mens-standard-tshirt"
},
"masterVariant": {
"id": 1,
"prices": [],
"images": [],
"attributes": [],
"assets": []
},
"variants": [],
"searchKeywords": {}
},
"staged": {
"name": {
"en": "Mens standard T-Shirt"
},
"categories": [],
"categoryOrderHints": {},
"slug": {
"en": "mens-standard-tshirt"
},
"masterVariant": {
"id": 1,
"prices": [],
"images": [],
"attributes": [],
"assets": []
},
"variants": [],
"searchKeywords": {}
},
"published": false,
"hasStagedChanges": false
},
"lastVariantId": 1
}
Updating the Azure function
Updating the config
Having generated:
- The API Access Token
- The Product Type
4: We can now move back to our Azure project in VS Code and update the values in the the local.settings.json file, specifically:
ct_bearer_token
- this should contain the value of the access tokenct_product_type_id
- this should contain the Product Type we generated
Don’t forget to save the file
5: Likewise, go back to the Azure portal, and update those configuration elements for our function on Azure.
Again, don’t forget to save your changes.
Updating the code
Having updated the config of our function, we now need to uncomment the calls to CreateCommerceToolsProductAsync
in the Run
method.
6: Update the Run
method as follows:
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log)
{
if (req.Headers["AuthKey"] != "marketplacerletmein")
return new OkObjectResult("Good try...");
log.LogInformation("C# HTTP trigger function processed a request.");
var payload = JsonConvert.DeserializeObject<dynamic>(await new StreamReader(req.Body).ReadToEndAsync());
Console.WriteLine($"---> Payload ID: {payload.id}");
if (payload.event_name != "create")
{
return new OkObjectResult("Not a create event");
}
Console.WriteLine($"---> Product Title: {payload.payload.data.node.title}");
var newProduct = new CTProductCreateDto
{
Name = new Name { En = payload.payload.data.node.title },
ProductType = new ProductType { Id = Environment.GetEnvironmentVariable("ct_product_type_id") },
Slug = new Name { En = payload.payload.data.node.legacyId }
};
if (await CreateCommerceToolsProductAsync(newProduct))
return new OkObjectResult("Product Created");
return new BadRequestObjectResult("Something went wrong...");
}
Don’t forget to save your changes.
Local Testing
Unique Slug
In this section we are going to make a local call to the Azure function, which in turn will make a call to the commercetools API.
One of the conditions of creating an Advert in commercetools is that the slug
value is unique, passing the same value for a subsequent advert will cause a 400 response from commercetools, therefore make sure this value is unique per call.
Note that the Marketplacer value we are mapping through to slug
is legacyId
.
7: Run up the function locally as per the steps found here.
8: And once again using the API client of your choice, make a call to the function. An example using cURL is shown below (note the new value for legacyId
in our body payload):
curl -XPOST http://localhost:{your assigned port}/api/HttpTrigger1
-H 'AuthKey: <your key>'
-H "Content-type: application/json"
-d '{"id":"V2ViaG9va0V2ZW50LTI5","event_name":"create","payload":{"data":{"node":{"id":"QWR2ZXJ0LTEwMDAwMjU5NA==","title":"Platinum T-Shirt","legacyId":10034000,"variants":{"nodes":[{"id":"VmFyaWFudC0zNjQ4","sku":"cp4u-0001","countOnHand":1000}]},"__typename":"Advert","description":"This is a great phone"}}}}'
This should result in a successful product creation:
To further “prove” that a product has in fact been created you could use the commercetools API to return all products. In the interests of time, we’ll leave that as a separate exercise.
Deploy Azure function changes
10: In order that we can test the whole process end to end, we of course need to deploy our Azure function changes. To do so follow the steps we have already performed here.
Update the webhook in Marketplacer
When we created our webhook originally, we used a placeholder endpoint using a tool called webhook.site
in order that we could test our webhook, the time has come to update that endpoint to our Azure function.
11: Back in the Azure portal, ensure that your function resource is selected, and click Functions:
12:: We should only have 1 function listed: HttpTrigger1, click this to continue:
13: Select Get Function Url then copy the URL, save this for now.
14: Login back into the Marketplacer Operator Portal, select Configuration then Webhooks to bring up your list of your webhooks, then click the pencil icon next to the webhook you want to edit:
15: Update the URL field with our Azure endpoint, and don’t forget to save, (also make sure that the webhook is enabled).
Testing end to end
Now comes the moment of truth where we want to perform an end to end test of the Product flow functionality!
Before we do that, please check that you have done the following:
- Updated the config in Azure (and have saved it!)
- Specifically, ensure that the commercetools
access-token
has not expired
- Specifically, ensure that the commercetools
- Updated the code in our Azure function and re-deployed it
- Checked the Azure function is running
- You have updated the URL endpoint in the Marketplacer Webhook and that it is enabled
Having confirmed all of the above, create another advert in Marketplacer, using whatever method you used previously.
16: To check the status of the webhook, return to your list of webhooks and click the cross icon beside your advert create webhook:
17: This should reveal that a webhook has been sent (a HTTP 200 has been returned). To determine the body response from the Azure function, click on the cross icon next to the sent event:
18: Check that the body of the response from the Azure webhook should be Product Created, remembering that we do also send HTTP 200 responses under other conditions.
Congratulations! We have set up the first leg of our Connected Integration!
Important Please Read
In this first iteration of the tutorial this is the only “Product Create” functionality we are going to build into our Azure function, primarily for reasons of brevity.
With this being the case, the product we have created via the Azure function will not be in a state where it can be purchased as part of the order flow we’ll cover in the next section - we’d need to add further detail for that to occur: e.g. adding pricing, variants etc.
Therefore when it comes to the order creation flow of this tutorial, we are going to leverage an existing, well-formed product that we obtained when we opted to use sample project data when we set up our instance of commercetools.
Next Up: Azure Service Bus