Testing a service application locally

Overview

This guide provides two ways of testing a service Connect application on your local machine:

Example scenario

A user has added a product to their cart, and an automatic discount of 10% should be applied to the cart's total price.

Use sample data

Requirements

  • Your application must expose an http server on port 8080.

Sample calls

Input

The service application will be called via POST request and the input is provided as the body payload.

The two example payloads are for creating or updating a Cart.

Create Cart payloadjson
{
"action": "Create",
"resource": {
"typeId": "cart",
"id": "bcaeb291-5151-434c-80a9-c1ce7c88e6e0",
"obj": {
"type": "Cart",
"id": "bcaeb291-5151-434c-80a9-c1ce7c88e6e0",
"version": 1,
"createdAt": "1970-01-01T00:00:00.000Z",
"lastModifiedAt": "1970-01-01T00:00:00.000Z",
"lastModifiedBy": {
"clientId": "hG7GPF-DdGZPEiamZKpOfZPP",
"isPlatformClient": false
},
"createdBy": {
"clientId": "hG7GPF-DdGZPEiamZKpOfZPP",
"isPlatformClient": false
},
"lineItems": [
{
"id": "aab03288-fd14-4bb1-9a23-e506774ac040",
"productId": "56fc033e-25de-484b-bf31-fc0b97143aa9",
"name": {
"en": "Some Product"
},
"productType": {
"typeId": "product-type",
"id": "e1861d9b-6666-4461-8bb5-e579128c9a65",
"version": 1
},
"productSlug": {
"en": "product_slug_fake_connector"
},
"variant": {
"id": 1,
"sku": "SKU-fake",
"prices": [
{
"id": "a09471bc-d9d6-4147-a390-7ac558f93c3c",
"value": {
"type": "centPrecision",
"currencyCode": "EUR",
"centAmount": 4000,
"fractionDigits": 2
}
}
],
"images": [],
"attributes": [],
"assets": []
},
"price": {
"id": "a09471bc-d9d6-4147-a390-7ac558f93c3c",
"value": {
"type": "centPrecision",
"currencyCode": "EUR",
"centAmount": 4000,
"fractionDigits": 2
}
},
"quantity": 1,
"discountedPricePerQuantity": [],
"perMethodTaxRate": [],
"addedAt": "2023-06-05T13:36:40.555Z",
"lastModifiedAt": "2023-06-05T13:36:40.555Z",
"state": [
{
"quantity": 1,
"state": {
"typeId": "state",
"id": "90506fd6-479c-4cdc-9302-aff56b4c67b1"
}
}
],
"priceMode": "Platform",
"lineItemMode": "Standard",
"totalPrice": {
"type": "centPrecision",
"currencyCode": "EUR",
"centAmount": 4000,
"fractionDigits": 2
},
"taxedPricePortions": []
}
],
"cartState": "Active",
"totalPrice": {
"type": "centPrecision",
"currencyCode": "EUR",
"centAmount": 4000,
"fractionDigits": 2
},
"shippingMode": "Single",
"shipping": [],
"customLineItems": [],
"discountCodes": [],
"directDiscounts": [],
"inventoryMode": "None",
"taxMode": "Platform",
"taxRoundingMode": "HalfEven",
"taxCalculationMode": "LineItemLevel",
"deleteDaysAfterLastModification": 90,
"refusedGifts": [],
"origin": "Customer",
"itemShippingAddresses": [],
"totalLineItemQuantity": 1
}
}
}
Update Cart payloadjson
{
"action": "Update",
"resource": {
"typeId": "cart",
"id": "0a1552e1-101e-4840-b401-744c67a331c1",
"obj": {
"type": "Cart",
"id": "0a1552e1-101e-4840-b401-744c67a331c1",
"version": 7,
"createdAt": "2023-06-05T13:41:22.428Z",
"lastModifiedAt": "2023-06-05T13:42:20.904Z",
"lastModifiedBy": {
"clientId": "hG7GPF-DdGZPEiamZKpOfZPP",
"isPlatformClient": false
},
"createdBy": {
"clientId": "hG7GPF-DdGZPEiamZKpOfZPP",
"isPlatformClient": false
},
"lineItems": [
{
"id": "1c27a1db-289d-4934-89c1-c67b04db0496",
"productId": "56fc033e-25de-484b-bf31-fc0b97143aa9",
"name": {
"en": "Some Product"
},
"productType": {
"typeId": "product-type",
"id": "e1861d9b-6666-4461-8bb5-e579128c9a65",
"version": 1
},
"productSlug": {
"en": "product_slug_fake_connector"
},
"variant": {
"id": 1,
"sku": "SKU-fake",
"prices": [
{
"id": "00b0e361-7b8b-464a-b25f-2b78715a1418",
"value": {
"type": "centPrecision",
"currencyCode": "USD",
"centAmount": 3100,
"fractionDigits": 2
}
},
{
"id": "a09471bc-d9d6-4147-a390-7ac558f93c3c",
"value": {
"type": "centPrecision",
"currencyCode": "EUR",
"centAmount": 4000,
"fractionDigits": 2
}
}
],
"images": [],
"attributes": [],
"assets": []
},
"price": {
"id": "a09471bc-d9d6-4147-a390-7ac558f93c3c",
"value": {
"type": "centPrecision",
"currencyCode": "EUR",
"centAmount": 4000,
"fractionDigits": 2
}
},
"quantity": 3,
"discountedPricePerQuantity": [],
"perMethodTaxRate": [],
"addedAt": "2023-06-05T13:41:22.421Z",
"lastModifiedAt": "2023-06-05T13:42:51.770Z",
"state": [
{
"quantity": 3,
"state": {
"typeId": "state",
"id": "90506fd6-479c-4cdc-9302-aff56b4c67b1"
}
}
],
"priceMode": "Platform",
"lineItemMode": "Standard",
"totalPrice": {
"type": "centPrecision",
"currencyCode": "EUR",
"centAmount": 12000,
"fractionDigits": 2
},
"taxedPricePortions": []
}
],
"cartState": "Active",
"totalPrice": {
"type": "centPrecision",
"currencyCode": "EUR",
"centAmount": 12000,
"fractionDigits": 2
},
"shippingMode": "Single",
"shipping": [],
"customLineItems": [],
"discountCodes": [],
"directDiscounts": [],
"inventoryMode": "None",
"taxMode": "Platform",
"taxRoundingMode": "HalfEven",
"taxCalculationMode": "LineItemLevel",
"deleteDaysAfterLastModification": 90,
"refusedGifts": [],
"origin": "Customer",
"itemShippingAddresses": [],
"totalLineItemQuantity": 3
}
}
}

Call your application

Call your application endpoint including the Create Cart or Update Cart in the payload:

curl -X POST http://localhost:8080/{endpoint} \
-H 'Content-Type: application/json' \
-d '{Create/Update Cart payload}'

Response

Service applications are expected to use the API Extension response and return a list of actions (mutations) on the triggered resource. You should check if the list of returned update actions follows your application's logic. In this example, it should return an update action for applying a 10% discount to the Cart as follows:

{
"actions": [
{
"action": "setDirectDiscounts",
"discounts": [
{
"value": {
"type": "relative",
"permyriad": 1000
},
"target": {
"type": "lineItems",
"predicate": "1=1"
}
}
]
}
]
}

Use an API Extension

Requirements

  • Your application must expose an http server on port 8080.
  • Have a local install of ngrok or similar software.

Objectives of this guide

By the end of this guide you will have:

  • Created an API Extension that is triggered when a Cart is created or updated.
  • Created a service Connect application that is called when the API Extension is triggered.

Expose your application

To set up an API Extension our service must be accessible from the Composable Commerce API. The following example uses ngrok to expose your machine with a public Domain Name System (DNS), but you can use similar applications/tools to do so.

# example
ngrok http 8080
# response: https://public-sample.ngrok-free.app

Set up the API Extension extension

With a public DNS, we can now create an API Extension on Composable Commerce. As mentioned in our use case, this example will trigger the API Extension every time a Cart is created or updated.

Example call to create an API Extensionshell
curl --location 'https://api.{region}.commercetools.com/{projectKey}/extensions' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <token>' \
--data '{
"destination" : {
"type" : "HTTP",
"url" : "https://public-sample.ngrok-free.app"
},
"triggers" : [ {
"resourceTypeId" : "cart",
"actions" : [ "Create", "Update" ]
} ],
"key" : "my-extension"
}'

Trigger the API Extension

Send an API call to Composable Commerce to create or update an existing Cart:

Example call to create a Cartshell
curl --location 'https://api.{region}.commercetools.com/{projectKey}/carts' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <token>' \
--data '{
"currency": "EUR",
"shipping": [],
"customShipping": [],
"lineItems": [{
"productId": {productId},
"variantId": 1
}]
}'

Review the results

You should now review your application's logs and the resource to confirm if the application was successful.