diff --git a/.gitignore b/.gitignore index 2163ca8..ab0ce6a 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,7 @@ build # Ignore Docker env file docker.env + +# Ignore examples files +**/target/ +**/generated/ diff --git a/README.md b/README.md index 907116e..2783403 100644 --- a/README.md +++ b/README.md @@ -8,21 +8,188 @@ ## Overview -[//]: # (TODO: Add overview mentioning the purpose of the module, supported REST API versions, and other high-level details.) +[HubSpot](https://www.hubspot.com) is an AI-powered customer relationship management (CRM) platform. + +The `ballerinax/hubspot.crm.commerce.orders` package offers APIs to connect and interact with [HubSpot API for CRM Commerce Orders](https://developers.hubspot.com/docs/reference/api/crm/commerce/orders) endpoints, specifically based on [HubSpot CRM Commerce Orders REST API](https://developers.hubspot.com/docs/reference/api). ## Setup guide -[//]: # (TODO: Add detailed steps to obtain credentials and configure the module.) +To use the HubSpot Commerce Orders connector, you must have access to the HubSpot API through a HubSpot developer account and a HubSpot App under it. Therefore you need to register for a developer account at HubSpot if you don't have one already. + +### Step 1: Create/Login to a HubSpot Developer Account + +If you have an account already, go to the [HubSpot developer portal](https://app.hubspot.com/) + +If you don't have a HubSpot Developer Account you can sign up to a free account [here](https://developers.hubspot.com/get-started) + +### Step 2 (Optional): Create a Developer Test Account + +Within app developer accounts, you can create [developer test account](https://developers.hubspot.com/beta-docs/getting-started/account-types#developer-test-accounts) under your account to test apps and integrations without affecting any real HubSpot data. + +>**Note:** These accounts are only for development and testing purposes. In production you should not use Developer Test Accounts. + +1. Go to Test Account section from the left sidebar. + + ![Hubspot Developer Portal](./docs/setup/resources/test_acc_1.png) + +2. Click Create developer test account. + + ![Hubspot Developer Test Account](./docs/setup/resources/test_acc_2.png) + +3. In the dialogue box, give a name to your test account and click create. + + ![Hubspot Developer Test Account](./docs/setup/resources/test_acc_3.png) + +### Step 3: Create a HubSpot App under your account. + +1. In your developer account, navigate to the "Apps" section. Click on "Create App" + + ![Hubspot Create App](./docs/setup/resources/create_app_1.png) + +2. Provide the necessary details, including the app name and description. + +### Step 4: Configure the Authentication Flow. + +1. Move to the Auth Tab. (Second tab next to App Info) + + ![Hubspot Developer Config Auth](./docs/setup/resources/auth_section.png) + +2. In the Scopes section, add the following scope for your app using the "Add new scope" button. + + - `crm.objects.orders.read` + - `crm.objects.orders.write` + - `crm.objects.deals.read` + - `crm.objects.deals.write` + - `crm.objects.carts.read` + - `crm.objects.commercepayments.read` + - `crm.objects.companies.read` + - `crm.objects.contacts.read` + - `crm.objects.invoices.read` + - `crm.objects.line_items.read` + - `crm.objects.quotes.read` + - `crm.objects.subscriptions.read` + + ![Hubspot Developer App Add Scopes](./docs/setup/resources/scopes.png) + +3. Add your Redirect URI in the relevant section. You can also use `localhost` addresses for local development purposes. Click Create App. + + ![Hubspot Create Developer App](./docs/setup/resources/create_app_final.png) + +### Step 5: Get your Client ID and Client Secret + +- Navigate to the Auth section of your app. Make sure to save the provided Client ID and Client Secret. + + ![Hubspot Get Credentials](./docs/setup/resources/get_credentials.png) + +### Step 6: Setup Authentication Flow + +Before proceeding with the Quickstart, ensure you have obtained the Access Token using the following steps: + +1. Create an authorization URL using the following format: + + ``` + https://app.hubspot.com/oauth/authorize?client_id=&scope=&redirect_uri= + ``` + + Replace the ``, `` and `` with your specific value. + +![Hubspot Get Auth Code](./docs/setup/resources/install_app.png) + +3. A code will be displayed in the browser. Copy the code. + +4. Run the following curl command. Replace the ``, ` and `` with your specific value. Use the code you received in the above step 3 as the ``. + + - Linux/macOS + + ```bash + curl --request POST \ + --url https://api.hubapi.com/oauth/v1/token \ + --header 'content-type: application/x-www-form-urlencoded' \ + --data 'grant_type=authorization_code&code=&redirect_uri=&client_id=&client_secret=' + ``` + + - Windows + + ```bash + curl --request POST ^ + --url https://api.hubapi.com/oauth/v1/token ^ + --header 'content-type: application/x-www-form-urlencoded' ^ + --data 'grant_type=authorization_code&code=&redirect_uri=&client_id=&client_secret=' + ``` + + This command will return the access token necessary for API calls. + + ```json + { + "token_type": "bearer", + "refresh_token": "", + "access_token": "", + "expires_in": 1800 + } + ``` + +5. Store the access token securely for use in your application. ## Quickstart -[//]: # (TODO: Add a quickstart guide to demonstrate a basic functionality of the module, including sample code snippets.) +To use the `HubSpot Commerce Orders` connector in your Ballerina application, update the `.bal` file as follows: + +### Step 1: Import the module + +Import the `hubspot.crm.commerce.orders` module and `oauth2` module. + +```ballerina +import ballerina/oauth2; +import ballerinax/hubspot.crm.commerce.orders as orders; +``` + +### Step 2: Instantiate a new connector + +1. Create a `Config.toml` file and, configure the obtained credentials in the above steps as follows: + + ```toml + clientId = + clientSecret = + refreshToken = + ``` + +2. Instantiate a `orders:ConnectionConfig` with the obtained credentials and initialize the connector with it. + + ```ballerina + configurable string clientId = ?; + configurable string clientSecret = ?; + configurable string refreshToken = ?; + + final orders:ConnectionConfig config = { + auth : { + clientId, + clientSecret, + refreshToken, + credentialBearer: oauth2:POST_BODY_BEARER + } + }; + final orders:Client baseClient = check new (config); + ``` + +### Step 3: Invoke the connector operation + +Now, utilize the available connector operations. A sample usecase is shown below. + +#### Read an order by ID + +```ballerina +public function main() returns error? { + orders:CollectionResponseSimplePublicObjectWithAssociationsForwardPaging listOfOrdersResponse = check baseClient->/orders; +} +``` ## Examples The `HubSpot CRM Commerce Orders` connector provides practical examples illustrating usage in various scenarios. Explore these [examples](https://github.com/module-ballerinax-hubspot.crm.commerce.orders/tree/main/examples/), covering the following use cases: -[//]: # (TODO: Add examples) +1. [Batch Operations](https://github.com/roshanemoraes/module-ballerinax-hubspot.crm.commerce.orders/tree/main/examples/batch-operations) - Perform Batch operations on Orders in HubSpot +2. [Order Management](https://github.com/roshanemoraes/module-ballerinax-hubspot.crm.commerce.orders/tree/main/examples/order-management) - Perford CRUD operations on Orders in HubSpot +3. [Search Operation](https://github.com/roshanemoraes/module-ballerinax-hubspot.crm.commerce.orders/tree/main/examples/search-operation) - Perform Search operations on Orders in HubSpot ## Build from the source diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index a73ebf8..ff2f1dd 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -5,12 +5,12 @@ name = "hubspot.crm.commerce.orders" version = "1.0.0" license = ["Apache-2.0"] authors = ["Ballerina"] -keywords = [] -# icon = "icon.png" # TODO: update icon.png +keywords = ["hubspot","crm", "commerce", "orders"] +icon = "icon.png" repository = "https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.commerce.orders" [build-options] observabilityIncluded = true -[platform.java21] +[platform.java17] graalvmCompatible = true diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml new file mode 100644 index 0000000..c0fdbd6 --- /dev/null +++ b/ballerina/Dependencies.toml @@ -0,0 +1,327 @@ +# AUTO-GENERATED FILE. DO NOT MODIFY. + +# This file is auto-generated by Ballerina for managing dependency versions. +# It should not be modified by hand. + +[ballerina] +dependencies-toml-version = "2" +distribution-version = "2201.10.3" + +[[package]] +org = "ballerina" +name = "auth" +version = "2.12.0" +dependencies = [ + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.array"}, + {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "log"} +] + +[[package]] +org = "ballerina" +name = "cache" +version = "3.8.0" +dependencies = [ + {org = "ballerina", name = "constraint"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "task"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "constraint" +version = "1.5.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "crypto" +version = "2.7.2" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "file" +version = "1.10.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "os"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "http" +version = "2.12.4" +dependencies = [ + {org = "ballerina", name = "auth"}, + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "constraint"}, + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "file"}, + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "jwt"}, + {org = "ballerina", name = "lang.array"}, + {org = "ballerina", name = "lang.decimal"}, + {org = "ballerina", name = "lang.int"}, + {org = "ballerina", name = "lang.regexp"}, + {org = "ballerina", name = "lang.runtime"}, + {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "lang.value"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "mime"}, + {org = "ballerina", name = "oauth2"}, + {org = "ballerina", name = "observe"}, + {org = "ballerina", name = "time"}, + {org = "ballerina", name = "url"} +] +modules = [ + {org = "ballerina", packageName = "http", moduleName = "http"}, + {org = "ballerina", packageName = "http", moduleName = "http.httpscerr"} +] + +[[package]] +org = "ballerina" +name = "io" +version = "1.6.3" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"} +] + +[[package]] +org = "ballerina" +name = "jballerina.java" +version = "0.0.0" + +[[package]] +org = "ballerina" +name = "jwt" +version = "2.13.0" +dependencies = [ + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.int"}, + {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "lang.__internal" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.object"} +] + +[[package]] +org = "ballerina" +name = "lang.array" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.__internal"} +] + +[[package]] +org = "ballerina" +name = "lang.decimal" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.error" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.int" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.__internal"}, + {org = "ballerina", name = "lang.object"} +] + +[[package]] +org = "ballerina" +name = "lang.object" +version = "0.0.0" + +[[package]] +org = "ballerina" +name = "lang.regexp" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.runtime" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.string" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.regexp"} +] + +[[package]] +org = "ballerina" +name = "lang.value" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "log" +version = "2.10.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"}, + {org = "ballerina", name = "observe"} +] + +[[package]] +org = "ballerina" +name = "mime" +version = "2.10.1" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.int"}, + {org = "ballerina", name = "log"} +] + +[[package]] +org = "ballerina" +name = "oauth2" +version = "2.12.0" +dependencies = [ + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "time"}, + {org = "ballerina", name = "url"} +] +modules = [ + {org = "ballerina", packageName = "oauth2", moduleName = "oauth2"} +] + +[[package]] +org = "ballerina" +name = "observe" +version = "1.3.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "os" +version = "1.8.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "task" +version = "2.5.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "test" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.array"}, + {org = "ballerina", name = "lang.error"} +] +modules = [ + {org = "ballerina", packageName = "test", moduleName = "test"} +] + +[[package]] +org = "ballerina" +name = "time" +version = "2.5.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "url" +version = "2.4.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] +modules = [ + {org = "ballerina", packageName = "url", moduleName = "url"} +] + +[[package]] +org = "ballerinai" +name = "observe" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "observe"} +] +modules = [ + {org = "ballerinai", packageName = "observe", moduleName = "observe"} +] + +[[package]] +org = "ballerinax" +name = "hubspot.crm.commerce.orders" +version = "1.0.0" +dependencies = [ + {org = "ballerina", name = "http"}, + {org = "ballerina", name = "oauth2"}, + {org = "ballerina", name = "test"}, + {org = "ballerina", name = "url"}, + {org = "ballerinai", name = "observe"} +] +modules = [ + {org = "ballerinax", packageName = "hubspot.crm.commerce.orders", moduleName = "hubspot.crm.commerce.orders"} +] diff --git a/ballerina/Module.md b/ballerina/Module.md index 4362712..af86f3f 100644 --- a/ballerina/Module.md +++ b/ballerina/Module.md @@ -1,17 +1,194 @@ +# Ballerina HubSpot CRM Commerce Orders connector + +[![Build](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.commerce.orders/actions/workflows/ci.yml/badge.svg)](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.commerce.orders/actions/workflows/ci.yml) +[![Trivy](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.commerce.orders/actions/workflows/trivy-scan.yml/badge.svg)](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.commerce.orders/actions/workflows/trivy-scan.yml) +[![GraalVM Check](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.commerce.orders/actions/workflows/build-with-bal-test-graalvm.yml/badge.svg)](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.commerce.orders/actions/workflows/build-with-bal-test-graalvm.yml) +[![GitHub Last Commit](https://img.shields.io/github/last-commit/ballerina-platform/module-ballerinax-hubspot.crm.commerce.orders.svg)](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.commerce.orders/commits/master) +[![GitHub Issues](https://img.shields.io/github/issues/ballerina-platform/ballerina-library/module/hubspot.crm.commerce.orders.svg?label=Open%20Issues)](https://github.com/ballerina-platform/ballerina-library/labels/module%hubspot.crm.commerce.orders) + ## Overview -[//]: # (TODO: Add overview mentioning the purpose of the module, supported REST API versions, and other high-level details.) +[HubSpot](https://www.hubspot.com) is an AI-powered customer relationship management (CRM) platform. + +The `ballerinax/hubspot.crm.commerce.orders` package offers APIs to connect and interact with [HubSpot API for CRM Commerce Orders](https://developers.hubspot.com/docs/reference/api/crm/commerce/orders) endpoints, specifically based on [HubSpot CRM Commerce Orders API v3 OpenAPI spec](https://github.com/HubSpot/HubSpot-public-api-spec-collection/blob/main/PublicApiSpecs/CRM/Orders/Rollouts/424/v3/orders.json). ## Setup guide -[//]: # (TODO: Add detailed steps to obtain credentials and configure the module.) +To use the HubSpot Commerce Orders connector, you must have access to the HubSpot API through a HubSpot developer account and a HubSpot App under it. Therefore you need to register for a developer account at HubSpot if you don't have one already. + +### Step 1: Create/Login to a HubSpot Developer Account + +If you have an account already, go to the [HubSpot developer portal](https://app.hubspot.com/) + +If you don't have a HubSpot Developer Account you can sign up to a free account [here](https://developers.hubspot.com/get-started) + +### Step 2 (Optional): Create a Developer Test Account + +Within app developer accounts, you can create [developer test account](https://developers.hubspot.com/beta-docs/getting-started/account-types#developer-test-accounts) under your account to test apps and integrations without affecting any real HubSpot data. + +> **Note:** These accounts are only for development and testing purposes. In production you should not use Developer Test Accounts. + +1. Go to Test Account section from the left sidebar. + + ![Hubspot Developer Portal](../docs/setup/resources/test_acc_1.png) + +2. Click Create developer test account. + + ![Hubspot Developer Test Account](../docs/setup/resources/test_acc_2.png) + +3. In the dialogue box, give a name to your test account and click create. + + ![Hubspot Developer Test Account](../docs/setup/resources/test_acc_3.png) + +### Step 3: Create a HubSpot App under your account. + +1. In your developer account, navigate to the "Apps" section. Click on "Create App" + + ![Hubspot Create App](../docs/setup/resources/create_app_1.png) + +2. Provide the necessary details, including the app name and description. + +### Step 4: Configure the Authentication Flow. + +1. Move to the Auth Tab. (Second tab next to App Info) + + ![Hubspot Developer Config Auth](../docs/setup/resources/auth_section.png) + +2. In the Scopes section, add the following scope for your app using the "Add new scope" button. + + - `crm.objects.orders.read` + - `crm.objects.orders.write` + - `crm.objects.deals.read` + - `crm.objects.deals.write` + - `crm.objects.carts.read` + - `crm.objects.commercepayments.read` + - `crm.objects.companies.read` + - `crm.objects.contacts.read` + - `crm.objects.invoices.read` + - `crm.objects.line_items.read` + - `crm.objects.quotes.read` + - `crm.objects.subscriptions.read` + + ![Hubspot Developer App Add Scopes](../docs/setup/resources/scopes.png) + +3. Add your Redirect URI in the relevant section. You can also use `localhost` addresses for local development purposes. Click Create App. + + ![Hubspot Create Developer App](../docs/setup/resources/create_app_final.png) + +### Step 5: Get your Client ID and Client Secret + +- Navigate to the Auth section of your app. Make sure to save the provided Client ID and Client Secret. + + ![Hubspot Get Credentials](../docs/setup/resources/get_credentials.png) + +### Step 6: Setup Authentication Flow + +Before proceeding with the Quickstart, ensure you have obtained the Access Token using the following steps: + +1. Create an authorization URL using the following format: + + ``` + https://app.hubspot.com/oauth/authorize?client_id=&scope=&redirect_uri= + ``` + + Replace the ``, `` and `` with your specific value. + +> **NOTE:** If you are using a `localhost` redirect url, make sure to have a listener running at the relevant port before executing the next step. 2. Paste it in the browser and select your developer test account to install the app when prompted. + +![Hubspot Get Auth Code](../docs/setup/resources/install_app.png) + +3. A code will be displayed in the browser. Copy the code. + +4. Run the following curl command. Replace the ``, ` and `` with your specific value. Use the code you received in the above step 3 as the ``. + + - Linux/macOS + + ```bash + curl --request POST \ + --url https://api.hubapi.com/oauth/v1/token \ + --header 'content-type: application/x-www-form-urlencoded' \ + --data 'grant_type=authorization_code&code=&redirect_uri=&client_id=&client_secret=' + ``` + + - Windows + + ```bash + curl --request POST ^ + --url https://api.hubapi.com/oauth/v1/token ^ + --header 'content-type: application/x-www-form-urlencoded' ^ + --data 'grant_type=authorization_code&code=&redirect_uri=&client_id=&client_secret=' + ``` + + This command will return the access token necessary for API calls. + + ```json + { + "token_type": "bearer", + "refresh_token": "", + "access_token": "", + "expires_in": 1800 + } + ``` + +5. Store the access token securely for use in your application. ## Quickstart -[//]: # (TODO: Add a quickstart guide to demonstrate a basic functionality of the module, including sample code snippets.) +To use the `HubSpot Commerce Orders` connector in your Ballerina application, update the `.bal` file as follows: + +### Step 1: Import the module + +Import the `hubspot.crm.commerce.orders` module and `oauth2` module. + +```ballerina +import ballerina/oauth2; +import ballerinax/hubspot.crm.commerce.orders as orders; +``` + +### Step 2: Instantiate a new connector + +1. Create a `Config.toml` file and, configure the obtained credentials in the above steps as follows: + + ```toml + clientId = + clientSecret = + refreshToken = + ``` + +2. Instantiate a `orders:ConnectionConfig` with the obtained credentials and initialize the connector with it. + + ```ballerina + configurable string clientId = ?; + configurable string clientSecret = ?; + configurable string refreshToken = ?; + + final orders:ConnectionConfig config = { + auth : { + clientId, + clientSecret, + refreshToken, + credentialBearer: oauth2:POST_BODY_BEARER + } + }; + final orders:Client baseClient = check new (config); + ``` + +### Step 3: Invoke the connector operation + +Now, utilize the available connector operations. A sample usecase is shown below. + +#### Read an order by ID + +```ballerina +public function main() returns error? { + orders:CollectionResponseSimplePublicObjectWithAssociationsForwardPaging listOfOrdersResponse = check baseClient->/orders; +} +``` ## Examples The `HubSpot CRM Commerce Orders` connector provides practical examples illustrating usage in various scenarios. Explore these [examples](https://github.com/module-ballerinax-hubspot.crm.commerce.orders/tree/main/examples/), covering the following use cases: -[//]: # (TODO: Add examples) +1. [Batch Operations](https://github.com/roshanemoraes/module-ballerinax-hubspot.crm.commerce.orders/tree/main/examples/batch-operations) - Perform Batch operations on Orders in HubSpot +2. [Order Management](https://github.com/roshanemoraes/module-ballerinax-hubspot.crm.commerce.orders/tree/main/examples/order-management) - Perford CRUD operations on Orders in HubSpot +3. [Search Operation](https://github.com/roshanemoraes/module-ballerinax-hubspot.crm.commerce.orders/tree/main/examples/search-operation) - Perform Search operations on Orders in HubSpot diff --git a/ballerina/Package.md b/ballerina/Package.md index 4362712..af86f3f 100644 --- a/ballerina/Package.md +++ b/ballerina/Package.md @@ -1,17 +1,194 @@ +# Ballerina HubSpot CRM Commerce Orders connector + +[![Build](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.commerce.orders/actions/workflows/ci.yml/badge.svg)](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.commerce.orders/actions/workflows/ci.yml) +[![Trivy](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.commerce.orders/actions/workflows/trivy-scan.yml/badge.svg)](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.commerce.orders/actions/workflows/trivy-scan.yml) +[![GraalVM Check](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.commerce.orders/actions/workflows/build-with-bal-test-graalvm.yml/badge.svg)](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.commerce.orders/actions/workflows/build-with-bal-test-graalvm.yml) +[![GitHub Last Commit](https://img.shields.io/github/last-commit/ballerina-platform/module-ballerinax-hubspot.crm.commerce.orders.svg)](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.commerce.orders/commits/master) +[![GitHub Issues](https://img.shields.io/github/issues/ballerina-platform/ballerina-library/module/hubspot.crm.commerce.orders.svg?label=Open%20Issues)](https://github.com/ballerina-platform/ballerina-library/labels/module%hubspot.crm.commerce.orders) + ## Overview -[//]: # (TODO: Add overview mentioning the purpose of the module, supported REST API versions, and other high-level details.) +[HubSpot](https://www.hubspot.com) is an AI-powered customer relationship management (CRM) platform. + +The `ballerinax/hubspot.crm.commerce.orders` package offers APIs to connect and interact with [HubSpot API for CRM Commerce Orders](https://developers.hubspot.com/docs/reference/api/crm/commerce/orders) endpoints, specifically based on [HubSpot CRM Commerce Orders API v3 OpenAPI spec](https://github.com/HubSpot/HubSpot-public-api-spec-collection/blob/main/PublicApiSpecs/CRM/Orders/Rollouts/424/v3/orders.json). ## Setup guide -[//]: # (TODO: Add detailed steps to obtain credentials and configure the module.) +To use the HubSpot Commerce Orders connector, you must have access to the HubSpot API through a HubSpot developer account and a HubSpot App under it. Therefore you need to register for a developer account at HubSpot if you don't have one already. + +### Step 1: Create/Login to a HubSpot Developer Account + +If you have an account already, go to the [HubSpot developer portal](https://app.hubspot.com/) + +If you don't have a HubSpot Developer Account you can sign up to a free account [here](https://developers.hubspot.com/get-started) + +### Step 2 (Optional): Create a Developer Test Account + +Within app developer accounts, you can create [developer test account](https://developers.hubspot.com/beta-docs/getting-started/account-types#developer-test-accounts) under your account to test apps and integrations without affecting any real HubSpot data. + +> **Note:** These accounts are only for development and testing purposes. In production you should not use Developer Test Accounts. + +1. Go to Test Account section from the left sidebar. + + ![Hubspot Developer Portal](../docs/setup/resources/test_acc_1.png) + +2. Click Create developer test account. + + ![Hubspot Developer Test Account](../docs/setup/resources/test_acc_2.png) + +3. In the dialogue box, give a name to your test account and click create. + + ![Hubspot Developer Test Account](../docs/setup/resources/test_acc_3.png) + +### Step 3: Create a HubSpot App under your account. + +1. In your developer account, navigate to the "Apps" section. Click on "Create App" + + ![Hubspot Create App](../docs/setup/resources/create_app_1.png) + +2. Provide the necessary details, including the app name and description. + +### Step 4: Configure the Authentication Flow. + +1. Move to the Auth Tab. (Second tab next to App Info) + + ![Hubspot Developer Config Auth](../docs/setup/resources/auth_section.png) + +2. In the Scopes section, add the following scope for your app using the "Add new scope" button. + + - `crm.objects.orders.read` + - `crm.objects.orders.write` + - `crm.objects.deals.read` + - `crm.objects.deals.write` + - `crm.objects.carts.read` + - `crm.objects.commercepayments.read` + - `crm.objects.companies.read` + - `crm.objects.contacts.read` + - `crm.objects.invoices.read` + - `crm.objects.line_items.read` + - `crm.objects.quotes.read` + - `crm.objects.subscriptions.read` + + ![Hubspot Developer App Add Scopes](../docs/setup/resources/scopes.png) + +3. Add your Redirect URI in the relevant section. You can also use `localhost` addresses for local development purposes. Click Create App. + + ![Hubspot Create Developer App](../docs/setup/resources/create_app_final.png) + +### Step 5: Get your Client ID and Client Secret + +- Navigate to the Auth section of your app. Make sure to save the provided Client ID and Client Secret. + + ![Hubspot Get Credentials](../docs/setup/resources/get_credentials.png) + +### Step 6: Setup Authentication Flow + +Before proceeding with the Quickstart, ensure you have obtained the Access Token using the following steps: + +1. Create an authorization URL using the following format: + + ``` + https://app.hubspot.com/oauth/authorize?client_id=&scope=&redirect_uri= + ``` + + Replace the ``, `` and `` with your specific value. + +> **NOTE:** If you are using a `localhost` redirect url, make sure to have a listener running at the relevant port before executing the next step. 2. Paste it in the browser and select your developer test account to install the app when prompted. + +![Hubspot Get Auth Code](../docs/setup/resources/install_app.png) + +3. A code will be displayed in the browser. Copy the code. + +4. Run the following curl command. Replace the ``, ` and `` with your specific value. Use the code you received in the above step 3 as the ``. + + - Linux/macOS + + ```bash + curl --request POST \ + --url https://api.hubapi.com/oauth/v1/token \ + --header 'content-type: application/x-www-form-urlencoded' \ + --data 'grant_type=authorization_code&code=&redirect_uri=&client_id=&client_secret=' + ``` + + - Windows + + ```bash + curl --request POST ^ + --url https://api.hubapi.com/oauth/v1/token ^ + --header 'content-type: application/x-www-form-urlencoded' ^ + --data 'grant_type=authorization_code&code=&redirect_uri=&client_id=&client_secret=' + ``` + + This command will return the access token necessary for API calls. + + ```json + { + "token_type": "bearer", + "refresh_token": "", + "access_token": "", + "expires_in": 1800 + } + ``` + +5. Store the access token securely for use in your application. ## Quickstart -[//]: # (TODO: Add a quickstart guide to demonstrate a basic functionality of the module, including sample code snippets.) +To use the `HubSpot Commerce Orders` connector in your Ballerina application, update the `.bal` file as follows: + +### Step 1: Import the module + +Import the `hubspot.crm.commerce.orders` module and `oauth2` module. + +```ballerina +import ballerina/oauth2; +import ballerinax/hubspot.crm.commerce.orders as orders; +``` + +### Step 2: Instantiate a new connector + +1. Create a `Config.toml` file and, configure the obtained credentials in the above steps as follows: + + ```toml + clientId = + clientSecret = + refreshToken = + ``` + +2. Instantiate a `orders:ConnectionConfig` with the obtained credentials and initialize the connector with it. + + ```ballerina + configurable string clientId = ?; + configurable string clientSecret = ?; + configurable string refreshToken = ?; + + final orders:ConnectionConfig config = { + auth : { + clientId, + clientSecret, + refreshToken, + credentialBearer: oauth2:POST_BODY_BEARER + } + }; + final orders:Client baseClient = check new (config); + ``` + +### Step 3: Invoke the connector operation + +Now, utilize the available connector operations. A sample usecase is shown below. + +#### Read an order by ID + +```ballerina +public function main() returns error? { + orders:CollectionResponseSimplePublicObjectWithAssociationsForwardPaging listOfOrdersResponse = check baseClient->/orders; +} +``` ## Examples The `HubSpot CRM Commerce Orders` connector provides practical examples illustrating usage in various scenarios. Explore these [examples](https://github.com/module-ballerinax-hubspot.crm.commerce.orders/tree/main/examples/), covering the following use cases: -[//]: # (TODO: Add examples) +1. [Batch Operations](https://github.com/roshanemoraes/module-ballerinax-hubspot.crm.commerce.orders/tree/main/examples/batch-operations) - Perform Batch operations on Orders in HubSpot +2. [Order Management](https://github.com/roshanemoraes/module-ballerinax-hubspot.crm.commerce.orders/tree/main/examples/order-management) - Perford CRUD operations on Orders in HubSpot +3. [Search Operation](https://github.com/roshanemoraes/module-ballerinax-hubspot.crm.commerce.orders/tree/main/examples/search-operation) - Perform Search operations on Orders in HubSpot diff --git a/ballerina/client.bal b/ballerina/client.bal index 66cdc3f..e2e6a8e 100644 --- a/ballerina/client.bal +++ b/ballerina/client.bal @@ -1,4 +1,7 @@ -// Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). +// AUTO-GENERATED FILE. DO NOT MODIFY. +// This file is auto-generated by the Ballerina OpenAPI tool. + +// Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except @@ -13,3 +16,234 @@ // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. + +import ballerina/http; + +public isolated client class Client { + final http:Client clientEp; + final readonly & ApiKeysConfig? apiKeyConfig; + # Gets invoked to initialize the `connector`. + # + # + config - The configurations to be used when initializing the `connector` + # + serviceUrl - URL of the target service + # + return - An error if connector initialization failed + public isolated function init(ConnectionConfig config, string serviceUrl = "https://api.hubapi.com/crm/v3/objects/orders") returns error? { + http:ClientConfiguration httpClientConfig = {httpVersion: config.httpVersion, timeout: config.timeout, forwarded: config.forwarded, poolConfig: config.poolConfig, compression: config.compression, circuitBreaker: config.circuitBreaker, retryConfig: config.retryConfig, validation: config.validation}; + do { + if config.http1Settings is ClientHttp1Settings { + ClientHttp1Settings settings = check config.http1Settings.ensureType(ClientHttp1Settings); + httpClientConfig.http1Settings = {...settings}; + } + if config.http2Settings is http:ClientHttp2Settings { + httpClientConfig.http2Settings = check config.http2Settings.ensureType(http:ClientHttp2Settings); + } + if config.cache is http:CacheConfig { + httpClientConfig.cache = check config.cache.ensureType(http:CacheConfig); + } + if config.responseLimits is http:ResponseLimitConfigs { + httpClientConfig.responseLimits = check config.responseLimits.ensureType(http:ResponseLimitConfigs); + } + if config.secureSocket is http:ClientSecureSocket { + httpClientConfig.secureSocket = check config.secureSocket.ensureType(http:ClientSecureSocket); + } + if config.proxy is http:ProxyConfig { + httpClientConfig.proxy = check config.proxy.ensureType(http:ProxyConfig); + } + } + if config.auth is ApiKeysConfig { + self.apiKeyConfig = (config.auth).cloneReadOnly(); + } else { + httpClientConfig.auth = config.auth; + self.apiKeyConfig = (); + } + http:Client httpEp = check new (serviceUrl, httpClientConfig); + self.clientEp = httpEp; + return; + } + + # Archive + # + # + headers - Headers to be sent with the request + # + return - No content + resource isolated function delete [string orderId](map headers = {}) returns http:Response|error { + string resourcePath = string `/${getEncodedUri(orderId)}`; + map headerValues = {...headers}; + if self.apiKeyConfig is ApiKeysConfig { + headerValues["private-app"] = self.apiKeyConfig?.private\-app; + } + map httpHeaders = getMapForHeaders(headerValues); + return self.clientEp->delete(resourcePath, headers = httpHeaders); + } + + # List + # + # + headers - Headers to be sent with the request + # + queries - Queries to be sent with the request + # + return - successful operation + resource isolated function get .(map headers = {}, *GetCrmV3ObjectsOrdersQueries queries) returns CollectionResponseSimplePublicObjectWithAssociationsForwardPaging|error { + string resourcePath = string `/`; + map headerValues = {...headers}; + if self.apiKeyConfig is ApiKeysConfig { + headerValues["private-app"] = self.apiKeyConfig?.private\-app; + } + map queryParamEncoding = {"properties": {style: FORM, explode: true}, "propertiesWithHistory": {style: FORM, explode: true}, "associations": {style: FORM, explode: true}}; + resourcePath = resourcePath + check getPathForQueryParam(queries, queryParamEncoding); + map httpHeaders = getMapForHeaders(headerValues); + return self.clientEp->get(resourcePath, httpHeaders); + } + + # Read + # + # + headers - Headers to be sent with the request + # + queries - Queries to be sent with the request + # + return - successful operation + resource isolated function get [string orderId](map headers = {}, *GetCrmV3ObjectsOrdersOrderidQueries queries) returns SimplePublicObjectWithAssociations|error { + string resourcePath = string `/${getEncodedUri(orderId)}`; + map headerValues = {...headers}; + if self.apiKeyConfig is ApiKeysConfig { + headerValues["private-app"] = self.apiKeyConfig?.private\-app; + } + map queryParamEncoding = {"properties": {style: FORM, explode: true}, "propertiesWithHistory": {style: FORM, explode: true}, "associations": {style: FORM, explode: true}}; + resourcePath = resourcePath + check getPathForQueryParam(queries, queryParamEncoding); + map httpHeaders = getMapForHeaders(headerValues); + return self.clientEp->get(resourcePath, httpHeaders); + } + + # Update + # + # + headers - Headers to be sent with the request + # + queries - Queries to be sent with the request + # + return - successful operation + resource isolated function patch [string orderId](SimplePublicObjectInput payload, map headers = {}, *PatchCrmV3ObjectsOrdersOrderidQueries queries) returns SimplePublicObject|error { + string resourcePath = string `/${getEncodedUri(orderId)}`; + map headerValues = {...headers}; + if self.apiKeyConfig is ApiKeysConfig { + headerValues["private-app"] = self.apiKeyConfig?.private\-app; + } + resourcePath = resourcePath + check getPathForQueryParam(queries); + map httpHeaders = getMapForHeaders(headerValues); + http:Request request = new; + json jsonBody = payload.toJson(); + request.setPayload(jsonBody, "application/json"); + return self.clientEp->patch(resourcePath, request, httpHeaders); + } + + # Create + # + # + headers - Headers to be sent with the request + # + return - successful operation + resource isolated function post .(SimplePublicObjectInputForCreate payload, map headers = {}) returns SimplePublicObject|error { + string resourcePath = string `/`; + map headerValues = {...headers}; + if self.apiKeyConfig is ApiKeysConfig { + headerValues["private-app"] = self.apiKeyConfig?.private\-app; + } + map httpHeaders = getMapForHeaders(headerValues); + http:Request request = new; + json jsonBody = payload.toJson(); + request.setPayload(jsonBody, "application/json"); + return self.clientEp->post(resourcePath, request, httpHeaders); + } + + # Archive a batch of orders by ID + # + # + headers - Headers to be sent with the request + # + return - No content + resource isolated function post batch/archive(BatchInputSimplePublicObjectId payload, map headers = {}) returns http:Response|error { + string resourcePath = string `/batch/archive`; + map headerValues = {...headers}; + if self.apiKeyConfig is ApiKeysConfig { + headerValues["private-app"] = self.apiKeyConfig?.private\-app; + } + map httpHeaders = getMapForHeaders(headerValues); + http:Request request = new; + json jsonBody = payload.toJson(); + request.setPayload(jsonBody, "application/json"); + return self.clientEp->post(resourcePath, request, httpHeaders); + } + + # Create a batch of orders + # + # + headers - Headers to be sent with the request + # + return - successful operation + resource isolated function post batch/create(BatchInputSimplePublicObjectInputForCreate payload, map headers = {}) returns BatchResponseSimplePublicObject|BatchResponseSimplePublicObjectWithErrors|error { + string resourcePath = string `/batch/create`; + map headerValues = {...headers}; + if self.apiKeyConfig is ApiKeysConfig { + headerValues["private-app"] = self.apiKeyConfig?.private\-app; + } + map httpHeaders = getMapForHeaders(headerValues); + http:Request request = new; + json jsonBody = payload.toJson(); + request.setPayload(jsonBody, "application/json"); + return self.clientEp->post(resourcePath, request, httpHeaders); + } + + # Read a batch of orders by internal ID, or unique property values + # + # + headers - Headers to be sent with the request + # + queries - Queries to be sent with the request + # + return - successful operation + resource isolated function post batch/read(BatchReadInputSimplePublicObjectId payload, map headers = {}, *PostCrmV3ObjectsOrdersBatchReadQueries queries) returns BatchResponseSimplePublicObject|BatchResponseSimplePublicObjectWithErrors|error { + string resourcePath = string `/batch/read`; + map headerValues = {...headers}; + if self.apiKeyConfig is ApiKeysConfig { + headerValues["private-app"] = self.apiKeyConfig?.private\-app; + } + resourcePath = resourcePath + check getPathForQueryParam(queries); + map httpHeaders = getMapForHeaders(headerValues); + http:Request request = new; + json jsonBody = payload.toJson(); + request.setPayload(jsonBody, "application/json"); + return self.clientEp->post(resourcePath, request, httpHeaders); + } + + # Update a batch of orders + # + # + headers - Headers to be sent with the request + # + return - successful operation + resource isolated function post batch/update(BatchInputSimplePublicObjectBatchInput payload, map headers = {}) returns BatchResponseSimplePublicObject|BatchResponseSimplePublicObjectWithErrors|error { + string resourcePath = string `/batch/update`; + map headerValues = {...headers}; + if self.apiKeyConfig is ApiKeysConfig { + headerValues["private-app"] = self.apiKeyConfig?.private\-app; + } + map httpHeaders = getMapForHeaders(headerValues); + http:Request request = new; + json jsonBody = payload.toJson(); + request.setPayload(jsonBody, "application/json"); + return self.clientEp->post(resourcePath, request, httpHeaders); + } + + # Create or update a batch of orders by unique property values + # + # + headers - Headers to be sent with the request + # + return - successful operation + resource isolated function post batch/upsert(BatchInputSimplePublicObjectBatchInputUpsert payload, map headers = {}) returns BatchResponseSimplePublicUpsertObject|BatchResponseSimplePublicUpsertObjectWithErrors|error { + string resourcePath = string `/batch/upsert`; + map headerValues = {...headers}; + if self.apiKeyConfig is ApiKeysConfig { + headerValues["private-app"] = self.apiKeyConfig?.private\-app; + } + map httpHeaders = getMapForHeaders(headerValues); + http:Request request = new; + json jsonBody = payload.toJson(); + request.setPayload(jsonBody, "application/json"); + return self.clientEp->post(resourcePath, request, httpHeaders); + } + + # + headers - Headers to be sent with the request + # + return - successful operation + resource isolated function post search(PublicObjectSearchRequest payload, map headers = {}) returns CollectionResponseWithTotalSimplePublicObjectForwardPaging|error { + string resourcePath = string `/search`; + map headerValues = {...headers}; + if self.apiKeyConfig is ApiKeysConfig { + headerValues["private-app"] = self.apiKeyConfig?.private\-app; + } + map httpHeaders = getMapForHeaders(headerValues); + http:Request request = new; + json jsonBody = payload.toJson(); + request.setPayload(jsonBody, "application/json"); + return self.clientEp->post(resourcePath, request, httpHeaders); + } +} diff --git a/ballerina/icon.png b/ballerina/icon.png new file mode 100644 index 0000000..96f47b5 Binary files /dev/null and b/ballerina/icon.png differ diff --git a/ballerina/tests/mock_service.bal b/ballerina/tests/mock_service.bal new file mode 100644 index 0000000..48732f1 --- /dev/null +++ b/ballerina/tests/mock_service.bal @@ -0,0 +1,75 @@ +// Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/http; + +service on new http:Listener(9090) { + resource function post crm/v3/objects/orders/batch/create(@http:Payload + BatchInputSimplePublicObjectInputForCreate payload) returns BatchResponseSimplePublicObject { + return { + completedAt: "2025-01-08T16:43:05.686Z", + startedAt: "2025-01-08T16:43:05.384Z", + results: [ + { + createdAt: "2025-01-08T16:43:05.440Z", + archived: false, + id: "395591235948", + properties: { + "hs_currency_code": "USD", + "hs_exchange_rate": "1.0", + "hs_lastmodifieddate": "2025-01-08T16:43:05.440Z", + "hs_object_source_id": "6457564", + "hs_createdate": "2025-01-08T16:43:05.440Z", + "hs_object_id": "395591235948", + "hs_object_source": "INTEGRATION", + "hs_object_source_label": "INTEGRATION" + }, + updatedAt: "2025-01-08T16:43:05.440Z" + } + ], + status: "COMPLETE" + }; + } + + resource function post crm/v3/objects/orders/batch/upsert(@http:Payload + BatchInputSimplePublicObjectBatchInputUpsert payload) returns BatchResponseSimplePublicUpsertObject { + return { + completedAt: "2025-01-08T16:43:06.213Z", + startedAt: "2025-01-08T16:43:06.140Z", + results: [ + { + createdAt: "2025-01-07T06:02:00.230Z", + archived: false, + "new": false, + id: "395261910382", + properties: { + "hs_currency_code": "USD", + "hs_lastmodifieddate": "2025-01-07T06:02:00.964Z", + "hs_object_source_id": "6457564", + "hs_createdate": "2025-01-07T06:02:00.230Z", + "hs_object_id": "395261910382", + "hs_billing_address_city": "mumbai", + "hs_object_source": "INTEGRATION", + "hs_billing_address_country": "india", + "hs_object_source_label": "INTEGRATION" + }, + updatedAt: "2025-01-07T06:02:00.964Z" + } + ], + status: "COMPLETE" + }; + }; +}; diff --git a/ballerina/tests/mock_test.bal b/ballerina/tests/mock_test.bal new file mode 100644 index 0000000..ddb4a21 --- /dev/null +++ b/ballerina/tests/mock_test.bal @@ -0,0 +1,88 @@ +// Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; + +final Client orderClient = check new Client(config, serviceUrl = "http://localhost:9090/crm/v3/objects/orders"); + +@test:Config {} +isolated function mockTestForCreatingABatchOfOrders() returns error? { + BatchResponseSimplePublicObject response = check orderClient->/batch/create.post( + { + inputs: [ + { + associations: [ + { + types: [ + { + associationCategory: "HUBSPOT_DEFINED", + associationTypeId: 512 + } + ], + to: { + id: "31440573867" + } + } + ], + properties: { + "hs_currency_code": "USD" + } + } + ] + } + ); + test:assertEquals(response.status, "COMPLETE"); +} + +@test:Config {} +isolated function mockTestForCreatingBatchOfOrdersByUniqueProperty() returns error? { + BatchResponseSimplePublicUpsertObject|BatchResponseSimplePublicUpsertObjectWithErrors response = + check orderClient->/batch/upsert.post( + payload = { + inputs: [ + { + idProperty: "my_unique_property_1", + id: "unique_value", + properties: { + "hs_billing_address_city": "mumbai", + "hs_billing_address_country": "india", + "hs_currency_code": "USD" + } + } + ] + } + ); + var expectedResult = { + createdAt: "2025-01-07T06:02:00.230Z", + archived: false, + "new": false, + id: "395261910382", + properties: { + "hs_currency_code": "USD", + "hs_lastmodifieddate": "2025-01-07T06:02:00.964Z", + "hs_object_source_id": "6457564", + "hs_createdate": "2025-01-07T06:02:00.230Z", + "hs_object_id": "395261910382", + "hs_billing_address_city": "mumbai", + "hs_object_source": "INTEGRATION", + "hs_billing_address_country": "india", + "hs_object_source_label": "INTEGRATION" + }, + updatedAt: "2025-01-07T06:02:00.964Z" + }; + test:assertEquals(response.results[0], expectedResult); + test:assertEquals(response.status, "COMPLETE"); +} diff --git a/ballerina/tests/test.bal b/ballerina/tests/test.bal new file mode 100644 index 0000000..2ce82b5 --- /dev/null +++ b/ballerina/tests/test.bal @@ -0,0 +1,250 @@ +// Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/http; +import ballerina/oauth2; +import ballerina/test; + +configurable string clientId = ?; +configurable string clientSecret = ?; +configurable string refreshToken = ?; + +OAuth2RefreshTokenGrantConfig auth = { + clientId, + clientSecret, + refreshToken, + credentialBearer: oauth2:POST_BODY_BEARER +}; + +ConnectionConfig config = {auth: auth}; +final Client baseClient = check new Client(config); + +@test:Config {} +function testPostOrdersSearch() returns error? { + PublicObjectSearchRequest payload = { + query: "1", + 'limit: 0, + after: "2", + sorts: [ + "3" + ], + properties: ["hs_lastmodifieddate", "hs_createdate", "hs_object_id", "updatedAt"], + filterGroups: [ + { + filters: [ + { + propertyName: "hs_source_store", + value: "REI - Portland", + operator: "EQ" + } + ] + } + ] + }; + CollectionResponseWithTotalSimplePublicObjectForwardPaging response = + check baseClient->/search.post(payload = payload); + test:assertTrue(response.total >= 0); +} + +@test:Config {} +function testPostOrdersBatchRead() returns error? { + + BatchReadInputSimplePublicObjectId payload = { + propertiesWithHistory: [ + "hs_lastmodifieddate" + ], + inputs: [ + { + id: "394961395351" + } + ], + properties: ["hs_lastmodifieddate", "hs_createdate", "hs_object_id", "updatedAt"] + }; + BatchResponseSimplePublicObject|BatchResponseSimplePublicObjectWithErrors response = + check baseClient->/batch/read.post(payload = payload); + if response.status != "PENDING" && response.status != "PROCESSING" + && response.status != "CANCELED" && response.status != "COMPLETE" { + test:assertFail("invalid status type"); + } + test:assertFalse(response.completedAt is "", "completedAt should not be empty"); + test:assertFalse(response.startedAt is "", "startedAt should not be empty"); +} + +@test:Config {} +function testDeleteObjectsOrdersByOrderId() returns error? { + string orderId = "10"; + + http:Response response = check baseClient->/[orderId].delete(); + test:assertTrue(response.statusCode == 204); +} + +@test:Config {} +function testPatchObjectsOrdersByOrderId() returns error? { + string orderId = "395972319872"; + SimplePublicObjectInput payload = + { + objectWriteTraceId: "10", + properties: { + "hs_lastmodifieddate": "2024-03-27T20:03:05.890Z", + "hs_shipping_tracking_number": "123098521091" + } + }; + SimplePublicObject response = check baseClient->/[orderId].patch(payload = payload); + test:assertFalse(response?.id is "", "id should not be empty"); + test:assertFalse(response?.createdAt is "", "creation time should not be empty"); + test:assertFalse(response?.updatedAt is "", "updated time should not be empty"); +} + +@test:Config {} +function testGetObjectsOrdersByOrderId() returns error? { + string orderId = "395972319872"; + + SimplePublicObjectWithAssociations response = check baseClient->/[orderId]; + test:assertFalse(response?.createdAt is "", "creation time should not be empty"); + test:assertFalse(response?.updatedAt is "", "updated time should not be empty"); + +} + +@test:Config {} +function testPostordersBatchUpsert() returns error? { + BatchInputSimplePublicObjectBatchInputUpsert payload = { + inputs: [ + { + idProperty: "my_unique_property_1", + id: "unique_value", + properties: { + "hs_billing_address_city": "mumbai", + "hs_billing_address_country": "india", + "hs_currency_code": "USD" + + } + } + ] + }; + BatchResponseSimplePublicUpsertObject|BatchResponseSimplePublicUpsertObjectWithErrors response = + check baseClient->/batch/upsert.post(payload = payload); + test:assertTrue(response.status == "COMPLETE"); + test:assertFalse(response?.completedAt is "", "creation time should not be empty"); + test:assertFalse(response?.startedAt is "", "start time should not be empty"); +} + +@test:Config {} +function testPostOrdersBatchCreate() returns error? { + + BatchInputSimplePublicObjectInputForCreate payload = { + inputs: [ + { + associations: [ + { + types: [ + { + associationCategory: "HUBSPOT_DEFINED", + associationTypeId: 512 + } + ], + to: { + id: "31440573867" + } + } + ], + properties: { + "hs_currency_code": "USD" + } + } + ] + }; + BatchResponseSimplePublicObject response = check baseClient->/batch/create.post(payload = payload); + test:assertFalse(response.completedAt is "", "completedAt should not be empty"); + test:assertFalse(response.startedAt is "", "startedAt should not be empty"); +} + +@test:Config {} +function testPostObjectsOrdersBatchUpdate() returns error? { + BatchInputSimplePublicObjectBatchInput payload = + { + inputs: [ + { + id: "395267361897", + properties: { + "hs_currency_code": "USD" + } + } + ] + }; + BatchResponseSimplePublicObject|BatchResponseSimplePublicObjectWithErrors response = + check baseClient->/batch/update.post(payload = payload); + test:assertFalse(response.completedAt is "", "completedAt should not be empty"); + test:assertFalse(response.startedAt is "", "startedAt should not be empty"); +} + +@test:Config {} +function testPostObjectsOrders() returns error? { + + SimplePublicObjectInputForCreate payload = + { + associations: [ + { + to: { + id: "31440573867" + }, + types: [ + { + associationCategory: "HUBSPOT_DEFINED", + associationTypeId: 512 + } + ] + } + ], + objectWriteTraceId: null, + properties: { + "hs_order_name": "Camping supplies", + "hs_currency_code": "USD", + "hs_source_store": "REI - Portland", + "hs_fulfillment_status": "Packing", + "hs_shipping_address_city": "Portland", + "hs_shipping_address_state": "Maine", + "hs_shipping_address_street": "123 Fake Street" + } + }; + SimplePublicObject response = check baseClient->/.post(payload = payload); + + test:assertFalse(response.createdAt is "", "createdAt should not be empty"); + test:assertFalse(response.updatedAt is "", "updateAt should not be empty"); + +} + +@test:Config {} +function testGetObjectsOrders() returns error? { + CollectionResponseSimplePublicObjectWithAssociationsForwardPaging response = check baseClient->/; + + foreach SimplePublicObjectWithAssociations result in response.results { + test:assertFalse(result.createdAt is "", "createdAt should not be empty"); + test:assertFalse(result.updatedAt is "", "updatedAt should not be empty"); + } +} + +@test:Config {} +function testPostOrdersBatchArchive() returns error? { + BatchInputSimplePublicObjectId payload = { + inputs: [ + { + id: "10" + } + ] + }; + http:Response response = check baseClient->/batch/archive.post(payload = payload); + test:assertTrue(response.statusCode == 204); +} diff --git a/ballerina/types.bal b/ballerina/types.bal new file mode 100644 index 0000000..7beb596 --- /dev/null +++ b/ballerina/types.bal @@ -0,0 +1,350 @@ +// AUTO-GENERATED FILE. DO NOT MODIFY. +// This file is auto-generated by the Ballerina OpenAPI tool. + +import ballerina/http; + +public type StandardError record { + record {} subCategory?; + record {|string[]...;|} context; + record {|string...;|} links; + string id?; + string category; + string message; + ErrorDetail[] errors; + string status; +}; + +public type CollectionResponseAssociatedId record { + Paging paging?; + AssociatedId[] results; +}; + +# Represents the Queries record for the operation: get-/crm/v3/objects/orders +public type GetCrmV3ObjectsOrdersQueries record { + # A comma separated list of object types to retrieve associated IDs for. If any of the specified associations do not exist, they will be ignored. + string[] associations?; + # Whether to return only results that have been archived. + boolean archived = false; + # A comma separated list of the properties to be returned along with their history of previous values. If any of the specified properties are not present on the requested object(s), they will be ignored. Usage of this parameter will reduce the maximum number of objects that can be read by a single request. + string[] propertiesWithHistory?; + # The maximum number of results to display per page. + int:Signed32 'limit = 10; + # The paging cursor token of the last successfully read resource will be returned as the `paging.next.after` JSON property of a paged response containing more results. + string after?; + # A comma separated list of the properties to be returned in the response. If any of the specified properties are not present on the requested object(s), they will be ignored. + string[] properties?; +}; + +public type PublicAssociationsForObject record { + AssociationSpec[] types; + PublicObjectId to; +}; + +public type BatchResponseSimplePublicObject record { + string completedAt; + string requestedAt?; + string startedAt; + record {|string...;|} links?; + SimplePublicObject[] results; + "PENDING"|"PROCESSING"|"CANCELED"|"COMPLETE" status; +}; + +public type FilterGroup record { + Filter[] filters; +}; + +public type ErrorDetail record { + # A specific category that contains more specific detail about the error + string subCategory?; + # The status code associated with the error detail + string code?; + # The name of the field or parameter in which the error was found. + string 'in?; + # Context about the error condition + record {|string[]...;|} context?; + # A human readable message describing the error along with remediation steps where appropriate + string message; +}; + +# Represents the Queries record for the operation: get-/crm/v3/objects/orders/{orderId} +public type GetCrmV3ObjectsOrdersOrderidQueries record { + # A comma separated list of object types to retrieve associated IDs for. If any of the specified associations do not exist, they will be ignored. + string[] associations?; + # Whether to return only results that have been archived. + boolean archived = false; + # A comma separated list of the properties to be returned along with their history of previous values. If any of the specified properties are not present on the requested object(s), they will be ignored. + string[] propertiesWithHistory?; + # The name of a property whose values are unique for this object type + string idProperty?; + # A comma separated list of the properties to be returned in the response. If any of the specified properties are not present on the requested object(s), they will be ignored. + string[] properties?; +}; + +public type ForwardPaging record { + NextPage next?; +}; + +public type SimplePublicObjectId record { + string id; +}; + +public type BatchResponseSimplePublicUpsertObjectWithErrors record { + string completedAt; + int:Signed32 numErrors?; + string requestedAt?; + string startedAt; + record {|string...;|} links?; + SimplePublicUpsertObject[] results; + StandardError[] errors?; + "PENDING"|"PROCESSING"|"CANCELED"|"COMPLETE" status; +}; + +public type BatchReadInputSimplePublicObjectId record { + string[] propertiesWithHistory; + string idProperty?; + SimplePublicObjectId[] inputs; + string[] properties; +}; + +public type BatchResponseSimplePublicUpsertObject record { + string completedAt; + string requestedAt?; + string startedAt; + record {|string...;|} links?; + SimplePublicUpsertObject[] results; + "PENDING"|"PROCESSING"|"CANCELED"|"COMPLETE" status; +}; + +public type ValueWithTimestamp record { + string sourceId?; + string sourceType; + string sourceLabel?; + int:Signed32 updatedByUserId?; + string value; + string timestamp; +}; + +public type BatchInputSimplePublicObjectId record { + SimplePublicObjectId[] inputs; +}; + +# OAuth2 Refresh Token Grant Configs +public type OAuth2RefreshTokenGrantConfig record {| + *http:OAuth2RefreshTokenGrantConfig; + # Refresh URL + string refreshUrl = "https://api.hubapi.com/oauth/v1/token"; +|}; + +# Represents the Queries record for the operation: post-/crm/v3/objects/orders/batch/read +public type PostCrmV3ObjectsOrdersBatchReadQueries record { + # Whether to return only results that have been archived. + boolean archived = false; +}; + +public type BatchInputSimplePublicObjectBatchInputUpsert record { + SimplePublicObjectBatchInputUpsert[] inputs; +}; + +public type CollectionResponseWithTotalSimplePublicObjectForwardPaging record { + int:Signed32 total; + ForwardPaging paging?; + SimplePublicObject[] results; +}; + +public type SimplePublicObject record { + string createdAt; + boolean archived?; + string archivedAt?; + record {|ValueWithTimestamp[]...;|} propertiesWithHistory?; + string id; + record {|string?...;|} properties; + string updatedAt; +}; + +# Provides a set of configurations for controlling the behaviours when communicating with a remote HTTP endpoint. +@display {label: "Connection Config"} +public type ConnectionConfig record {| + # Provides Auth configurations needed when communicating with a remote HTTP endpoint. + http:BearerTokenConfig|OAuth2RefreshTokenGrantConfig|ApiKeysConfig auth; + # The HTTP version understood by the client + http:HttpVersion httpVersion = http:HTTP_2_0; + # Configurations related to HTTP/1.x protocol + ClientHttp1Settings http1Settings?; + # Configurations related to HTTP/2 protocol + http:ClientHttp2Settings http2Settings?; + # The maximum time to wait (in seconds) for a response before closing the connection + decimal timeout = 60; + # The choice of setting `forwarded`/`x-forwarded` header + string forwarded = "disable"; + # Configurations associated with request pooling + http:PoolConfiguration poolConfig?; + # HTTP caching related configurations + http:CacheConfig cache?; + # Specifies the way of handling compression (`accept-encoding`) header + http:Compression compression = http:COMPRESSION_AUTO; + # Configurations associated with the behaviour of the Circuit Breaker + http:CircuitBreakerConfig circuitBreaker?; + # Configurations associated with retrying + http:RetryConfig retryConfig?; + # Configurations associated with inbound response size limits + http:ResponseLimitConfigs responseLimits?; + # SSL/TLS-related options + http:ClientSecureSocket secureSocket?; + # Proxy server related options + http:ProxyConfig proxy?; + # Enables the inbound payload validation functionality which provided by the constraint package. Enabled by default + boolean validation = true; +|}; + +public type PublicObjectId record { + string id; +}; + +# Represents the Queries record for the operation: patch-/crm/v3/objects/orders/{orderId} +public type PatchCrmV3ObjectsOrdersOrderidQueries record { + # The name of a property whose values are unique for this object type + string idProperty?; +}; + +public type Paging record { + NextPage next?; + PreviousPage prev?; +}; + +public type PublicObjectSearchRequest record { + string query?; + int:Signed32 'limit?; + string after?; + string[] sorts?; + string[] properties?; + FilterGroup[] filterGroups?; +}; + +public type SimplePublicObjectBatchInputUpsert record { + string idProperty?; + string objectWriteTraceId?; + string id; + record {|string...;|} properties; +}; + +public type BatchResponseSimplePublicObjectWithErrors record { + string completedAt; + int:Signed32 numErrors?; + string requestedAt?; + string startedAt; + record {|string...;|} links?; + SimplePublicObject[] results; + StandardError[] errors?; + "PENDING"|"PROCESSING"|"CANCELED"|"COMPLETE" status; +}; + +# Proxy server configurations to be used with the HTTP client endpoint. +public type ProxyConfig record {| + # Host name of the proxy server + string host = ""; + # Proxy server port + int port = 0; + # Proxy server username + string userName = ""; + # Proxy server password + @display {label: "", kind: "password"} + string password = ""; +|}; + +public type SimplePublicObjectInput record { + string objectWriteTraceId?; + record {|string...;|} properties; +}; + +public type CollectionResponseSimplePublicObjectWithAssociationsForwardPaging record { + ForwardPaging paging?; + SimplePublicObjectWithAssociations[] results; +}; + +public type AssociationSpec record { + "HUBSPOT_DEFINED"|"USER_DEFINED"|"INTEGRATOR_DEFINED" associationCategory; + int:Signed32 associationTypeId; +}; + +public type SimplePublicObjectWithAssociations record { + record {|CollectionResponseAssociatedId...;|} associations?; + string createdAt; + boolean archived?; + string archivedAt?; + record {|ValueWithTimestamp[]...;|} propertiesWithHistory?; + string id; + record {|string?...;|} properties; + string updatedAt; +}; + +public type Filter record { + string highValue?; + string propertyName; + string[] values?; + string value?; + # null + "EQ"|"NEQ"|"LT"|"LTE"|"GT"|"GTE"|"BETWEEN"|"IN"|"NOT_IN"|"HAS_PROPERTY"|"NOT_HAS_PROPERTY"|"CONTAINS_TOKEN"|"NOT_CONTAINS_TOKEN" operator; +}; + +# Provides settings related to HTTP/1.x protocol. +public type ClientHttp1Settings record {| + # Specifies whether to reuse a connection for multiple requests + http:KeepAlive keepAlive = http:KEEPALIVE_AUTO; + # The chunking behaviour of the request + http:Chunking chunking = http:CHUNKING_AUTO; + # Proxy server related options + ProxyConfig proxy?; +|}; + +public type BatchInputSimplePublicObjectBatchInput record { + SimplePublicObjectBatchInput[] inputs; +}; + +public type BatchInputSimplePublicObjectInputForCreate record { + SimplePublicObjectInputForCreate[] inputs; +}; + +public type PreviousPage record { + string before; + string link?; +}; + +public type SimplePublicUpsertObject record { + string createdAt; + boolean archived?; + string archivedAt?; + boolean 'new; + record {|ValueWithTimestamp[]...;|} propertiesWithHistory?; + string id; + record {|string...;|} properties; + string updatedAt; +}; + +public type SimplePublicObjectBatchInput record { + string idProperty?; + string objectWriteTraceId?; + string id; + record {|string...;|} properties; +}; + +public type NextPage record { + string link?; + string after; +}; + +public type AssociatedId record { + string id; + string 'type; +}; + +# Provides API key configurations needed when communicating with a remote HTTP endpoint. +public type ApiKeysConfig record {| + string private\-app\-legacy; + string private\-app; +|}; + +public type SimplePublicObjectInputForCreate record { + PublicAssociationsForObject[] associations; + string objectWriteTraceId?; + record {|string...;|} properties; +}; diff --git a/ballerina/utils.bal b/ballerina/utils.bal new file mode 100644 index 0000000..1b492b2 --- /dev/null +++ b/ballerina/utils.bal @@ -0,0 +1,218 @@ +// AUTO-GENERATED FILE. DO NOT MODIFY. +// This file is auto-generated by the Ballerina OpenAPI tool. + +import ballerina/url; + +type SimpleBasicType string|boolean|int|float|decimal; + +# Represents encoding mechanism details. +type Encoding record { + # Defines how multiple values are delimited + string style = FORM; + # Specifies whether arrays and objects should generate as separate fields + boolean explode = true; + # Specifies the custom content type + string contentType?; + # Specifies the custom headers + map headers?; +}; + +enum EncodingStyle { + DEEPOBJECT, FORM, SPACEDELIMITED, PIPEDELIMITED +} + +final Encoding & readonly defaultEncoding = {}; + +# Serialize the record according to the deepObject style. +# +# + parent - Parent record name +# + anyRecord - Record to be serialized +# + return - Serialized record as a string +isolated function getDeepObjectStyleRequest(string parent, record {} anyRecord) returns string { + string[] recordArray = []; + foreach [string, anydata] [key, value] in anyRecord.entries() { + if value is SimpleBasicType { + recordArray.push(parent + "[" + key + "]" + "=" + getEncodedUri(value.toString())); + } else if value is SimpleBasicType[] { + recordArray.push(getSerializedArray(parent + "[" + key + "]" + "[]", value, DEEPOBJECT, true)); + } else if value is record {} { + string nextParent = parent + "[" + key + "]"; + recordArray.push(getDeepObjectStyleRequest(nextParent, value)); + } else if value is record {}[] { + string nextParent = parent + "[" + key + "]"; + recordArray.push(getSerializedRecordArray(nextParent, value, DEEPOBJECT)); + } + recordArray.push("&"); + } + _ = recordArray.pop(); + return string:'join("", ...recordArray); +} + +# Serialize the record according to the form style. +# +# + parent - Parent record name +# + anyRecord - Record to be serialized +# + explode - Specifies whether arrays and objects should generate separate parameters +# + return - Serialized record as a string +isolated function getFormStyleRequest(string parent, record {} anyRecord, boolean explode = true) returns string { + string[] recordArray = []; + if explode { + foreach [string, anydata] [key, value] in anyRecord.entries() { + if value is SimpleBasicType { + recordArray.push(key, "=", getEncodedUri(value.toString())); + } else if value is SimpleBasicType[] { + recordArray.push(getSerializedArray(key, value, explode = explode)); + } else if value is record {} { + recordArray.push(getFormStyleRequest(parent, value, explode)); + } + recordArray.push("&"); + } + _ = recordArray.pop(); + } else { + foreach [string, anydata] [key, value] in anyRecord.entries() { + if value is SimpleBasicType { + recordArray.push(key, ",", getEncodedUri(value.toString())); + } else if value is SimpleBasicType[] { + recordArray.push(getSerializedArray(key, value, explode = false)); + } else if value is record {} { + recordArray.push(getFormStyleRequest(parent, value, explode)); + } + recordArray.push(","); + } + _ = recordArray.pop(); + } + return string:'join("", ...recordArray); +} + +# Serialize arrays. +# +# + arrayName - Name of the field with arrays +# + anyArray - Array to be serialized +# + style - Defines how multiple values are delimited +# + explode - Specifies whether arrays and objects should generate separate parameters +# + return - Serialized array as a string +isolated function getSerializedArray(string arrayName, anydata[] anyArray, string style = "form", boolean explode = true) returns string { + string key = arrayName; + string[] arrayValues = []; + if anyArray.length() > 0 { + if style == FORM && !explode { + arrayValues.push(key, "="); + foreach anydata i in anyArray { + arrayValues.push(getEncodedUri(i.toString()), ","); + } + } else if style == SPACEDELIMITED && !explode { + arrayValues.push(key, "="); + foreach anydata i in anyArray { + arrayValues.push(getEncodedUri(i.toString()), "%20"); + } + } else if style == PIPEDELIMITED && !explode { + arrayValues.push(key, "="); + foreach anydata i in anyArray { + arrayValues.push(getEncodedUri(i.toString()), "|"); + } + } else if style == DEEPOBJECT { + foreach anydata i in anyArray { + arrayValues.push(key, "[]", "=", getEncodedUri(i.toString()), "&"); + } + } else { + foreach anydata i in anyArray { + arrayValues.push(key, "=", getEncodedUri(i.toString()), "&"); + } + } + _ = arrayValues.pop(); + } + return string:'join("", ...arrayValues); +} + +# Serialize the array of records according to the form style. +# +# + parent - Parent record name +# + value - Array of records to be serialized +# + style - Defines how multiple values are delimited +# + explode - Specifies whether arrays and objects should generate separate parameters +# + return - Serialized record as a string +isolated function getSerializedRecordArray(string parent, record {}[] value, string style = FORM, boolean explode = true) returns string { + string[] serializedArray = []; + if style == DEEPOBJECT { + int arayIndex = 0; + foreach var recordItem in value { + serializedArray.push(getDeepObjectStyleRequest(parent + "[" + arayIndex.toString() + "]", recordItem), "&"); + arayIndex = arayIndex + 1; + } + } else { + if !explode { + serializedArray.push(parent, "="); + } + foreach var recordItem in value { + serializedArray.push(getFormStyleRequest(parent, recordItem, explode), ","); + } + } + _ = serializedArray.pop(); + return string:'join("", ...serializedArray); +} + +# Get Encoded URI for a given value. +# +# + value - Value to be encoded +# + return - Encoded string +isolated function getEncodedUri(anydata value) returns string { + string|error encoded = url:encode(value.toString(), "UTF8"); + if encoded is string { + return encoded; + } else { + return value.toString(); + } +} + +# Generate query path with query parameter. +# +# + queryParam - Query parameter map +# + encodingMap - Details on serialization mechanism +# + return - Returns generated Path or error at failure of client initialization +isolated function getPathForQueryParam(map queryParam, map encodingMap = {}) returns string|error { + string[] param = []; + if queryParam.length() > 0 { + param.push("?"); + foreach var [key, value] in queryParam.entries() { + if value is () { + _ = queryParam.remove(key); + continue; + } + Encoding encodingData = encodingMap.hasKey(key) ? encodingMap.get(key) : defaultEncoding; + if value is SimpleBasicType { + param.push(key, "=", getEncodedUri(value.toString())); + } else if value is SimpleBasicType[] { + param.push(getSerializedArray(key, value, encodingData.style, encodingData.explode)); + } else if value is record {} { + if encodingData.style == DEEPOBJECT { + param.push(getDeepObjectStyleRequest(key, value)); + } else { + param.push(getFormStyleRequest(key, value, encodingData.explode)); + } + } else { + param.push(key, "=", value.toString()); + } + param.push("&"); + } + _ = param.pop(); + } + string restOfPath = string:'join("", ...param); + return restOfPath; +} + +# Generate header map for given header values. +# +# + headerParam - Headers map +# + return - Returns generated map or error at failure of client initialization +isolated function getMapForHeaders(map headerParam) returns map { + map headerMap = {}; + foreach var [key, value] in headerParam.entries() { + if value is SimpleBasicType[] { + headerMap[key] = from SimpleBasicType data in value + select data.toString(); + } else { + headerMap[key] = value.toString(); + } + } + return headerMap; +} diff --git a/build-config/resources/Ballerina.toml b/build-config/resources/Ballerina.toml index 79e4f03..e0d7569 100644 --- a/build-config/resources/Ballerina.toml +++ b/build-config/resources/Ballerina.toml @@ -5,12 +5,12 @@ name = "hubspot.crm.commerce.orders" version = "@toml.version@" license = ["Apache-2.0"] authors = ["Ballerina"] -keywords = [] # TODO: Add keywords -# icon = "icon.png" # TODO: Add icon +keywords = ["hubspot","crm", "commerce", "orders"] +icon = "icon.png" repository = "https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.commerce.orders" [build-options] observabilityIncluded = true -[platform.java21] +[platform.java17] graalvmCompatible = true diff --git a/docs/license.txt b/docs/license.txt index 921a7a1..adfc413 100644 --- a/docs/license.txt +++ b/docs/license.txt @@ -1,7 +1,7 @@ // AUTO-GENERATED FILE. DO NOT MODIFY. // This file is auto-generated by the Ballerina OpenAPI tool. -// Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). +// Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/docs/setup/resources/auth_section.png b/docs/setup/resources/auth_section.png new file mode 100644 index 0000000..14306db Binary files /dev/null and b/docs/setup/resources/auth_section.png differ diff --git a/docs/setup/resources/create_app_1.png b/docs/setup/resources/create_app_1.png new file mode 100644 index 0000000..dc6c160 Binary files /dev/null and b/docs/setup/resources/create_app_1.png differ diff --git a/docs/setup/resources/create_app_2.png b/docs/setup/resources/create_app_2.png new file mode 100644 index 0000000..88713af Binary files /dev/null and b/docs/setup/resources/create_app_2.png differ diff --git a/docs/setup/resources/create_app_final.png b/docs/setup/resources/create_app_final.png new file mode 100644 index 0000000..88713af Binary files /dev/null and b/docs/setup/resources/create_app_final.png differ diff --git a/docs/setup/resources/get_credentials.png b/docs/setup/resources/get_credentials.png new file mode 100644 index 0000000..0c587a9 Binary files /dev/null and b/docs/setup/resources/get_credentials.png differ diff --git a/docs/setup/resources/install_app.png b/docs/setup/resources/install_app.png new file mode 100644 index 0000000..8fd341e Binary files /dev/null and b/docs/setup/resources/install_app.png differ diff --git a/docs/setup/resources/scope_set.png b/docs/setup/resources/scope_set.png new file mode 100644 index 0000000..a35f022 Binary files /dev/null and b/docs/setup/resources/scope_set.png differ diff --git a/docs/setup/resources/scopes.png b/docs/setup/resources/scopes.png new file mode 100644 index 0000000..093b66c Binary files /dev/null and b/docs/setup/resources/scopes.png differ diff --git a/docs/setup/resources/test_acc_1.png b/docs/setup/resources/test_acc_1.png new file mode 100644 index 0000000..d9317bd Binary files /dev/null and b/docs/setup/resources/test_acc_1.png differ diff --git a/docs/setup/resources/test_acc_2.png b/docs/setup/resources/test_acc_2.png new file mode 100644 index 0000000..7d3acfc Binary files /dev/null and b/docs/setup/resources/test_acc_2.png differ diff --git a/docs/setup/resources/test_acc_3.png b/docs/setup/resources/test_acc_3.png new file mode 100644 index 0000000..5301aa7 Binary files /dev/null and b/docs/setup/resources/test_acc_3.png differ diff --git a/docs/spec/openapi.json b/docs/spec/openapi.json new file mode 100644 index 0000000..6e84621 --- /dev/null +++ b/docs/spec/openapi.json @@ -0,0 +1,1743 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Orders", + "description": "", + "version": "v3", + "x-hubspot-product-tier-requirements": { + "marketing": "FREE", + "sales": "FREE", + "service": "FREE", + "cms": "FREE" + }, + "x-hubspot-documentation-banner": "NONE", + "x-hubspot-api-use-case": "When a buyer adds a set of products to their cart and makes a purchase, store it as an individual order. You can then update the order with tracking information once the shipping label has been printed.", + "x-hubspot-related-documentation": [ + { + "name": "Orders overview", + "url": "https://developers.hubspot.com/beta-docs/guides/api/crm/commerce/orders" + } + ], + "x-hubspot-introduction": "Use the orders API to create and manage data related to ecommerce purchases in HubSpot. This can be especially useful for keeping HubSpot data synced with external ecommerce platforms, such as Shopify and NetSuite." + }, + "servers": [ + { + "url": "https://api.hubapi.com/crm/v3/objects/orders" + } + ], + "tags": [ + { + "name": "Batch" + }, + { + "name": "Basic" + }, + { + "name": "Search" + } + ], + "paths": { + "/batch/read": { + "post": { + "tags": ["Batch"], + "summary": "Read a batch of orders by internal ID, or unique property values", + "operationId": "post-/crm/v3/objects/orders/batch/read", + "parameters": [ + { + "name": "archived", + "in": "query", + "description": "Whether to return only results that have been archived.", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "boolean", + "default": false + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchReadInputSimplePublicObjectId" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchResponseSimplePublicObject" + } + } + } + }, + "207": { + "description": "multiple statuses", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchResponseSimplePublicObjectWithErrors" + } + } + } + }, + "default": { + "$ref": "#/components/responses/Error" + } + }, + "security": [ + { + "oauth2": ["crm.objects.orders.read"] + }, + { + "private_apps": ["crm.objects.orders.read"] + } + ] + } + }, + "/{orderId}": { + "get": { + "tags": ["Basic"], + "summary": "Read", + "description": "Read an Object identified by `{orderId}`. `{orderId}` refers to the internal object ID by default, or optionally any unique property value as specified by the `idProperty` query param. Control what is returned via the `properties` query param.", + "operationId": "get-/crm/v3/objects/orders/{orderId}", + "parameters": [ + { + "name": "orderId", + "in": "path", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + }, + { + "name": "properties", + "in": "query", + "description": "A comma separated list of the properties to be returned in the response. If any of the specified properties are not present on the requested object(s), they will be ignored.", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "propertiesWithHistory", + "in": "query", + "description": "A comma separated list of the properties to be returned along with their history of previous values. If any of the specified properties are not present on the requested object(s), they will be ignored.", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "associations", + "in": "query", + "description": "A comma separated list of object types to retrieve associated IDs for. If any of the specified associations do not exist, they will be ignored.", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "archived", + "in": "query", + "description": "Whether to return only results that have been archived.", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "idProperty", + "in": "query", + "description": "The name of a property whose values are unique for this object type", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimplePublicObjectWithAssociations" + } + } + } + }, + "default": { + "$ref": "#/components/responses/Error" + } + }, + "security": [ + { + "oauth2": ["crm.objects.orders.read"] + }, + { + "private_apps": ["crm.objects.orders.read"] + } + ] + }, + "delete": { + "tags": ["Basic"], + "summary": "Archive", + "description": "Move an Object identified by `{orderId}` to the recycling bin.", + "operationId": "delete-/crm/v3/objects/orders/{orderId}", + "parameters": [ + { + "name": "orderId", + "in": "path", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content", + "content": {} + }, + "default": { + "$ref": "#/components/responses/Error" + } + }, + "security": [ + { + "oauth2": ["crm.objects.orders.write"] + }, + { + "private_apps": ["crm.objects.orders.write"] + } + ] + }, + "patch": { + "tags": ["Basic"], + "summary": "Update", + "description": "Perform a partial update of an Object identified by `{orderId}`. `{orderId}` refers to the internal object ID by default, or optionally any unique property value as specified by the `idProperty` query param. Provided property values will be overwritten. Read-only and non-existent properties will be ignored. Properties values can be cleared by passing an empty string.", + "operationId": "patch-/crm/v3/objects/orders/{orderId}", + "parameters": [ + { + "name": "orderId", + "in": "path", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + }, + { + "name": "idProperty", + "in": "query", + "description": "The name of a property whose values are unique for this object type", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimplePublicObjectInput" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimplePublicObject" + } + } + } + }, + "default": { + "$ref": "#/components/responses/Error" + } + }, + "security": [ + { + "oauth2": ["crm.objects.orders.write"] + }, + { + "private_apps": ["crm.objects.orders.write"] + } + ] + } + }, + "/batch/archive": { + "post": { + "tags": ["Batch"], + "summary": "Archive a batch of orders by ID", + "operationId": "post-/crm/v3/objects/orders/batch/archive", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchInputSimplePublicObjectId" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "No content", + "content": {} + }, + "default": { + "$ref": "#/components/responses/Error" + } + }, + "security": [ + { + "oauth2": ["crm.objects.orders.write"] + }, + { + "private_apps": ["crm.objects.orders.write"] + } + ] + } + }, + "/batch/create": { + "post": { + "tags": ["Batch"], + "summary": "Create a batch of orders", + "operationId": "post-/crm/v3/objects/orders/batch/create", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchInputSimplePublicObjectInputForCreate" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchResponseSimplePublicObject" + } + } + } + }, + "207": { + "description": "multiple statuses", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchResponseSimplePublicObjectWithErrors" + } + } + } + }, + "default": { + "$ref": "#/components/responses/Error" + } + }, + "security": [ + { + "oauth2": ["crm.objects.orders.write"] + }, + { + "private_apps": ["crm.objects.orders.write"] + } + ] + } + }, + "/batch/update": { + "post": { + "tags": ["Batch"], + "summary": "Update a batch of orders", + "operationId": "post-/crm/v3/objects/orders/batch/update", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchInputSimplePublicObjectBatchInput" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchResponseSimplePublicObject" + } + } + } + }, + "207": { + "description": "multiple statuses", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchResponseSimplePublicObjectWithErrors" + } + } + } + }, + "default": { + "$ref": "#/components/responses/Error" + } + }, + "security": [ + { + "oauth2": ["crm.objects.orders.write"] + }, + { + "private_apps": ["crm.objects.orders.write"] + } + ] + } + }, + "/": { + "get": { + "tags": ["Basic"], + "summary": "List", + "description": "Read a page of orders. Control what is returned via the `properties` query param.", + "operationId": "get-/crm/v3/objects/orders", + "parameters": [ + { + "name": "limit", + "in": "query", + "description": "The maximum number of results to display per page.", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "integer", + "format": "int32", + "default": 10 + } + }, + { + "name": "after", + "in": "query", + "description": "The paging cursor token of the last successfully read resource will be returned as the `paging.next.after` JSON property of a paged response containing more results.", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "string" + } + }, + { + "name": "properties", + "in": "query", + "description": "A comma separated list of the properties to be returned in the response. If any of the specified properties are not present on the requested object(s), they will be ignored.", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "propertiesWithHistory", + "in": "query", + "description": "A comma separated list of the properties to be returned along with their history of previous values. If any of the specified properties are not present on the requested object(s), they will be ignored. Usage of this parameter will reduce the maximum number of objects that can be read by a single request.", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "associations", + "in": "query", + "description": "A comma separated list of object types to retrieve associated IDs for. If any of the specified associations do not exist, they will be ignored.", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "archived", + "in": "query", + "description": "Whether to return only results that have been archived.", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CollectionResponseSimplePublicObjectWithAssociationsForwardPaging" + } + } + } + }, + "default": { + "$ref": "#/components/responses/Error" + } + }, + "security": [ + { + "oauth2": ["crm.objects.orders.read"] + }, + { + "private_apps": ["crm.objects.orders.read"] + } + ] + }, + "post": { + "tags": ["Basic"], + "summary": "Create", + "description": "Create a order with the given properties and return a copy of the object, including the ID. Documentation and examples for creating standard orders is provided.", + "operationId": "post-/crm/v3/objects/orders", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimplePublicObjectInputForCreate" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimplePublicObject" + } + } + } + }, + "default": { + "$ref": "#/components/responses/Error" + } + }, + "security": [ + { + "oauth2": ["crm.objects.orders.write"] + }, + { + "private_apps": ["crm.objects.orders.write"] + } + ] + } + }, + "/batch/upsert": { + "post": { + "tags": ["Batch"], + "summary": "Create or update a batch of orders by unique property values", + "operationId": "post-/crm/v3/objects/orders/batch/upsert", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchInputSimplePublicObjectBatchInputUpsert" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchResponseSimplePublicUpsertObject" + } + } + } + }, + "207": { + "description": "multiple statuses", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchResponseSimplePublicUpsertObjectWithErrors" + } + } + } + }, + "default": { + "$ref": "#/components/responses/Error" + } + }, + "security": [ + { + "oauth2": ["crm.objects.orders.write"] + }, + { + "private_apps": ["crm.objects.orders.write"] + } + ] + } + }, + "/search": { + "post": { + "tags": ["Search"], + "operationId": "post-/crm/v3/objects/orders/search", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicObjectSearchRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CollectionResponseWithTotalSimplePublicObjectForwardPaging" + } + } + } + }, + "default": { + "$ref": "#/components/responses/Error" + } + }, + "security": [ + { + "oauth2": ["crm.objects.orders.read"] + }, + { + "private_apps": ["crm.objects.orders.read"] + } + ], + "x-hubspot-rate-limit-exemptions": ["ten-secondly"] + } + } + }, + "components": { + "schemas": { + "StandardError": { + "required": [ + "category", + "context", + "errors", + "links", + "message", + "status" + ], + "type": "object", + "properties": { + "subCategory": { + "type": "object", + "properties": {} + }, + "context": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "links": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "id": { + "type": "string" + }, + "category": { + "type": "string" + }, + "message": { + "type": "string" + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ErrorDetail" + } + }, + "status": { + "type": "string" + } + } + }, + "CollectionResponseAssociatedId": { + "required": ["results"], + "type": "object", + "properties": { + "paging": { + "$ref": "#/components/schemas/Paging" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AssociatedId" + } + } + } + }, + "PublicAssociationsForObject": { + "required": ["to", "types"], + "type": "object", + "properties": { + "types": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AssociationSpec" + } + }, + "to": { + "$ref": "#/components/schemas/PublicObjectId" + } + } + }, + "BatchResponseSimplePublicObject": { + "required": ["completedAt", "results", "startedAt", "status"], + "type": "object", + "properties": { + "completedAt": { + "type": "string", + "format": "date-time" + }, + "requestedAt": { + "type": "string", + "format": "date-time" + }, + "startedAt": { + "type": "string", + "format": "date-time" + }, + "links": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SimplePublicObject" + } + }, + "status": { + "type": "string", + "enum": ["PENDING", "PROCESSING", "CANCELED", "COMPLETE"] + } + } + }, + "FilterGroup": { + "required": ["filters"], + "type": "object", + "properties": { + "filters": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Filter" + } + } + } + }, + "ErrorDetail": { + "required": ["message"], + "type": "object", + "properties": { + "subCategory": { + "type": "string", + "description": "A specific category that contains more specific detail about the error" + }, + "code": { + "type": "string", + "description": "The status code associated with the error detail" + }, + "in": { + "type": "string", + "description": "The name of the field or parameter in which the error was found." + }, + "context": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + }, + "description": "Context about the error condition", + "example": { + "missingScopes": ["scope1", "scope2"] + } + }, + "message": { + "type": "string", + "description": "A human readable message describing the error along with remediation steps where appropriate" + } + } + }, + "ForwardPaging": { + "type": "object", + "properties": { + "next": { + "$ref": "#/components/schemas/NextPage" + } + } + }, + "SimplePublicObjectId": { + "required": ["id"], + "type": "object", + "properties": { + "id": { + "type": "string" + } + } + }, + "BatchResponseSimplePublicUpsertObjectWithErrors": { + "required": ["completedAt", "results", "startedAt", "status"], + "type": "object", + "properties": { + "completedAt": { + "type": "string", + "format": "date-time" + }, + "numErrors": { + "type": "integer", + "format": "int32" + }, + "requestedAt": { + "type": "string", + "format": "date-time" + }, + "startedAt": { + "type": "string", + "format": "date-time" + }, + "links": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SimplePublicUpsertObject" + } + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/StandardError" + } + }, + "status": { + "type": "string", + "enum": ["PENDING", "PROCESSING", "CANCELED", "COMPLETE"] + } + } + }, + "BatchReadInputSimplePublicObjectId": { + "required": ["inputs", "properties", "propertiesWithHistory"], + "type": "object", + "properties": { + "propertiesWithHistory": { + "type": "array", + "items": { + "type": "string" + } + }, + "idProperty": { + "type": "string" + }, + "inputs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SimplePublicObjectId" + } + }, + "properties": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "BatchResponseSimplePublicUpsertObject": { + "required": ["completedAt", "results", "startedAt", "status"], + "type": "object", + "properties": { + "completedAt": { + "type": "string", + "format": "date-time" + }, + "requestedAt": { + "type": "string", + "format": "date-time" + }, + "startedAt": { + "type": "string", + "format": "date-time" + }, + "links": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SimplePublicUpsertObject" + } + }, + "status": { + "type": "string", + "enum": ["PENDING", "PROCESSING", "CANCELED", "COMPLETE"] + } + } + }, + "BatchInputSimplePublicObjectId": { + "required": ["inputs"], + "type": "object", + "properties": { + "inputs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SimplePublicObjectId" + } + } + } + }, + "ValueWithTimestamp": { + "required": ["sourceType", "timestamp", "value"], + "type": "object", + "properties": { + "sourceId": { + "type": "string" + }, + "sourceType": { + "type": "string" + }, + "sourceLabel": { + "type": "string" + }, + "updatedByUserId": { + "type": "integer", + "format": "int32" + }, + "value": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + } + } + }, + "BatchInputSimplePublicObjectBatchInputUpsert": { + "required": ["inputs"], + "type": "object", + "properties": { + "inputs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SimplePublicObjectBatchInputUpsert" + } + } + } + }, + "CollectionResponseWithTotalSimplePublicObjectForwardPaging": { + "required": ["results", "total"], + "type": "object", + "properties": { + "total": { + "type": "integer", + "format": "int32" + }, + "paging": { + "$ref": "#/components/schemas/ForwardPaging" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SimplePublicObject" + } + } + } + }, + "SimplePublicObject": { + "required": ["createdAt", "id", "properties", "updatedAt"], + "type": "object", + "properties": { + "createdAt": { + "type": "string", + "format": "date-time" + }, + "archived": { + "type": "boolean", + "example": false + }, + "archivedAt": { + "type": "string", + "format": "date-time" + }, + "propertiesWithHistory": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ValueWithTimestamp" + } + } + }, + "id": { + "type": "string", + "example": "512" + }, + "properties": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": { + "property_date": "1572480000000", + "property_radio": "option_1", + "property_number": "17", + "property_string": "value", + "property_checkbox": "false", + "property_dropdown": "choice_b", + "property_multiple_checkboxes": "chocolate;strawberry" + } + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + }, + "example": { + "id": "512", + "properties": { + "createdate": "2019-10-30T03:30:17.883Z", + "hs_currency_code": "USD", + "hs_external_created_date": "1709814663", + "hs_lastmodifieddate": "2019-12-07T16:50:06.678Z", + "hs_order_name": "Order Name", + "hs_pipeline": "Pipeline ID", + "hs_pipeline_stage": "Pipeline Stage ID", + "hs_source_store": "Store Name", + "hs_total_price": "100.00" + }, + "createdAt": "2019-10-30T03:30:17.883Z", + "updatedAt": "2019-12-07T16:50:06.678Z", + "archived": false + } + }, + "PublicObjectId": { + "required": ["id"], + "type": "object", + "properties": { + "id": { + "type": "string" + } + } + }, + "Paging": { + "type": "object", + "properties": { + "next": { + "$ref": "#/components/schemas/NextPage" + }, + "prev": { + "$ref": "#/components/schemas/PreviousPage" + } + } + }, + "PublicObjectSearchRequest": { + "type": "object", + "properties": { + "query": { + "type": "string" + }, + "limit": { + "type": "integer", + "format": "int32" + }, + "after": { + "type": "string" + }, + "sorts": { + "type": "array", + "items": { + "type": "string" + } + }, + "properties": { + "type": "array", + "items": { + "type": "string" + } + }, + "filterGroups": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FilterGroup" + } + } + } + }, + "Error": { + "required": ["category", "correlationId", "message"], + "type": "object", + "properties": { + "subCategory": { + "type": "string", + "description": "A specific category that contains more specific detail about the error" + }, + "context": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + }, + "description": "Context about the error condition", + "example": { + "missingScopes": ["scope1", "scope2"], + "invalidPropertyName": ["propertyValue"] + } + }, + "correlationId": { + "type": "string", + "description": "A unique identifier for the request. Include this value with any error reports or support tickets", + "format": "uuid", + "example": "aeb5f871-7f07-4993-9211-075dc63e7cbf" + }, + "links": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "A map of link names to associated URIs containing documentation about the error or recommended remediation steps", + "example": { + "knowledge-base": "https://www.hubspot.com/products/service/knowledge-base" + } + }, + "message": { + "type": "string", + "description": "A human readable message describing the error along with remediation steps where appropriate", + "example": "Invalid input (details will vary based on the error)" + }, + "category": { + "type": "string", + "description": "The error category", + "example": "VALIDATION_ERROR" + }, + "errors": { + "type": "array", + "description": "further information about the error", + "items": { + "$ref": "#/components/schemas/ErrorDetail" + } + } + }, + "example": { + "message": "Invalid input (details will vary based on the error)", + "correlationId": "aeb5f871-7f07-4993-9211-075dc63e7cbf", + "category": "VALIDATION_ERROR", + "links": { + "knowledge-base": "https://www.hubspot.com/products/service/knowledge-base" + } + } + }, + "SimplePublicObjectBatchInputUpsert": { + "required": ["id", "properties"], + "type": "object", + "properties": { + "idProperty": { + "type": "string" + }, + "objectWriteTraceId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "properties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "BatchResponseSimplePublicObjectWithErrors": { + "required": ["completedAt", "results", "startedAt", "status"], + "type": "object", + "properties": { + "completedAt": { + "type": "string", + "format": "date-time" + }, + "numErrors": { + "type": "integer", + "format": "int32" + }, + "requestedAt": { + "type": "string", + "format": "date-time" + }, + "startedAt": { + "type": "string", + "format": "date-time" + }, + "links": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SimplePublicObject" + } + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/StandardError" + } + }, + "status": { + "type": "string", + "enum": ["PENDING", "PROCESSING", "CANCELED", "COMPLETE"] + } + } + }, + "SimplePublicObjectInput": { + "required": ["properties"], + "type": "object", + "properties": { + "objectWriteTraceId": { + "type": "string" + }, + "properties": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "example": { + "property_date": "1572480000000", + "property_radio": "option_1", + "property_number": "17", + "property_string": "value", + "property_checkbox": "false", + "property_dropdown": "choice_b", + "property_multiple_checkboxes": "chocolate;strawberry" + } + } + }, + "example": { + "properties": { + "hs_pipeline": "Pipeline ID", + "hs_order_name": "Order Name", + "hs_total_price": "100.00", + "hs_source_store": "Store Name", + "hs_currency_code": "USD", + "hs_pipeline_stage": "Pipeline Stage ID", + "hs_external_created_date": "1709814663" + }, + "associations": [ + { + "to": { + "id": "1" + }, + "types": [ + { + "associationCategory": "HUBSPOT_DEFINED", + "associationTypeId": 2 + } + ] + } + ] + } + }, + "CollectionResponseSimplePublicObjectWithAssociationsForwardPaging": { + "required": ["results"], + "type": "object", + "properties": { + "paging": { + "$ref": "#/components/schemas/ForwardPaging" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SimplePublicObjectWithAssociations" + } + } + } + }, + "AssociationSpec": { + "required": ["associationCategory", "associationTypeId"], + "type": "object", + "properties": { + "associationCategory": { + "type": "string", + "enum": ["HUBSPOT_DEFINED", "USER_DEFINED", "INTEGRATOR_DEFINED"] + }, + "associationTypeId": { + "type": "integer", + "format": "int32" + } + } + }, + "SimplePublicObjectWithAssociations": { + "required": ["createdAt", "id", "properties", "updatedAt"], + "type": "object", + "properties": { + "associations": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/CollectionResponseAssociatedId" + } + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "archived": { + "type": "boolean" + }, + "archivedAt": { + "type": "string", + "format": "date-time" + }, + "propertiesWithHistory": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ValueWithTimestamp" + } + } + }, + "id": { + "type": "string" + }, + "properties": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + } + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + }, + "example": { + "properties": { + "createdate": "2019-10-30T03:30:17.883Z", + "hs_currency_code": "USD", + "hs_external_created_date": "1709814663", + "hs_lastmodifieddate": "2019-12-07T16:50:06.678Z", + "hs_order_name": "Order Name", + "hs_pipeline": "Pipeline ID", + "hs_pipeline_stage": "Pipeline Stage ID", + "hs_source_store": "Store Name", + "hs_total_price": "100.00" + } + } + }, + "Filter": { + "required": ["operator", "propertyName"], + "type": "object", + "properties": { + "highValue": { + "type": "string" + }, + "propertyName": { + "type": "string" + }, + "values": { + "type": "array", + "items": { + "type": "string" + } + }, + "value": { + "type": "string" + }, + "operator": { + "type": "string", + "description": "null", + "enum": [ + "EQ", + "NEQ", + "LT", + "LTE", + "GT", + "GTE", + "BETWEEN", + "IN", + "NOT_IN", + "HAS_PROPERTY", + "NOT_HAS_PROPERTY", + "CONTAINS_TOKEN", + "NOT_CONTAINS_TOKEN" + ] + } + } + }, + "BatchInputSimplePublicObjectBatchInput": { + "required": ["inputs"], + "type": "object", + "properties": { + "inputs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SimplePublicObjectBatchInput" + } + } + } + }, + "BatchInputSimplePublicObjectInputForCreate": { + "required": ["inputs"], + "type": "object", + "properties": { + "inputs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SimplePublicObjectInputForCreate" + } + } + } + }, + "PreviousPage": { + "required": ["before"], + "type": "object", + "properties": { + "before": { + "type": "string" + }, + "link": { + "type": "string" + } + } + }, + "SimplePublicUpsertObject": { + "required": ["createdAt", "id", "new", "properties", "updatedAt"], + "type": "object", + "properties": { + "createdAt": { + "type": "string", + "format": "date-time" + }, + "archived": { + "type": "boolean" + }, + "archivedAt": { + "type": "string", + "format": "date-time" + }, + "new": { + "type": "boolean" + }, + "propertiesWithHistory": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ValueWithTimestamp" + } + } + }, + "id": { + "type": "string" + }, + "properties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + } + }, + "SimplePublicObjectBatchInput": { + "required": ["id", "properties"], + "type": "object", + "properties": { + "idProperty": { + "type": "string", + "example": "my_unique_property_name" + }, + "objectWriteTraceId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "properties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "example": { + "id": "1", + "properties": { + "hs_pipeline": "Pipeline ID", + "hs_order_name": "Order Name", + "hs_total_price": "100.00", + "hs_source_store": "Store Name", + "hs_currency_code": "USD", + "hs_pipeline_stage": "Pipeline Stage ID", + "hs_external_created_date": "1709814663" + } + } + }, + "AssociatedId": { + "required": ["id", "type"], + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, + "NextPage": { + "required": ["after"], + "type": "object", + "properties": { + "link": { + "type": "string", + "example": "?after=NTI1Cg%3D%3D" + }, + "after": { + "type": "string", + "example": "NTI1Cg%3D%3D" + } + }, + "example": { + "after": "NTI1Cg%3D%3D", + "link": "?after=NTI1Cg%3D%3D" + } + }, + "SimplePublicObjectInputForCreate": { + "required": ["associations", "properties"], + "type": "object", + "properties": { + "associations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PublicAssociationsForObject" + } + }, + "objectWriteTraceId": { + "type": "string" + }, + "properties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "example": { + "properties": { + "hs_pipeline": "Pipeline ID", + "hs_order_name": "Order Name", + "hs_total_price": "100.00", + "hs_source_store": "Store Name", + "hs_currency_code": "USD", + "hs_pipeline_stage": "Pipeline Stage ID", + "hs_external_created_date": "1709814663" + }, + "associations": [ + { + "to": { + "id": "1" + }, + "types": [ + { + "associationCategory": "HUBSPOT_DEFINED", + "associationTypeId": 2 + } + ] + } + ] + } + } + }, + "responses": { + "Error": { + "description": "An error occurred.", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "securitySchemes": { + "oauth2_legacy": { + "type": "oauth2", + "flows": { + "authorizationCode": { + "authorizationUrl": "https://app.hubspot.com/oauth/authorize", + "tokenUrl": "https://api.hubapi.com/oauth/v1/token", + "scopes": { + "crm.objects.goals.read": "Read goals", + "tickets": "Read and write tickets", + "crm.objects.custom.read": "View custom object records", + "e-commerce": "e-commerce", + "crm.objects.custom.write": "Change custom object records", + "media_bridge.read": "Read media and media events" + } + } + } + }, + "oauth2": { + "type": "oauth2", + "flows": { + "authorizationCode": { + "authorizationUrl": "https://app.hubspot.com/oauth/authorize", + "tokenUrl": "https://api.hubapi.com/oauth/v1/token", + "scopes": { + "crm.objects.companies.write": " ", + "crm.objects.contacts.write": " ", + "crm.objects.users.write": "Write User CRM objects", + "crm.objects.commercepayments.read": "Read the COMMERCE_PAYMENT object.", + "crm.objects.leads.write": "Modify lead objects", + "crm.objects.subscriptions.read": "Read Commerce Subscriptions", + "crm.objects.carts.read": "Read carts", + "crm.objects.orders.write": "Write orders", + "crm.objects.quotes.read": "Quotes", + "crm.objects.services.read": "Read services", + "crm.objects.orders.read": "Read Orders", + "crm.objects.contacts.read": " ", + "crm.objects.listings.read": "Read listings", + "crm.objects.carts.write": "Write cart", + "crm.objects.courses.write": "Write courses", + "crm.objects.quotes.write": "Quotes", + "crm.objects.users.read": "Read User CRM objects", + "crm.objects.companies.read": " ", + "crm.objects.appointments.read": "Read appointments", + "crm.objects.partner-clients.write": "Modify Partner Client CRM objects", + "crm.objects.leads.read": "Read lead objects", + "crm.objects.appointments.write": "Write appointments", + "crm.objects.services.write": "Write services", + "crm.objects.line_items.read": "Line Items", + "crm.objects.courses.read": "Read courses", + "crm.objects.deals.read": " ", + "crm.objects.invoices.read": "Read invoices objects", + "crm.objects.partner-clients.read": "View Partner Client CRM objects", + "crm.objects.deals.write": " ", + "crm.objects.line_items.write": "Line Items", + "crm.objects.listings.write": "Write listings" + } + } + } + }, + "private_apps_legacy": { + "type": "apiKey", + "name": "private-app-legacy", + "in": "header" + }, + "private_apps": { + "type": "apiKey", + "name": "private-app", + "in": "header" + } + } + } +} diff --git a/docs/spec/sanitations.md b/docs/spec/sanitations.md index 27ac1b0..6aff55f 100644 --- a/docs/spec/sanitations.md +++ b/docs/spec/sanitations.md @@ -1,24 +1,41 @@ -_Author_: \ -_Created_: \ -_Updated_: \ +_Author_: @roshanemoraes +_Created_: 2024/12/18 +_Updated_: 2024/12/18 _Edition_: Swan Lake # Sanitation for OpenAPI specification -This document records the sanitation done on top of the official OpenAPI specification from HubSpot CRM Commerce Orders. -The OpenAPI specification is obtained from (TODO: Add source link). +This document records the sanitation done on top of the official OpenAPI specification from HubSpot CRM Commerce Orders. +The OpenAPI specification is obtained from [Hubspot Github Public API Spec Collection](https://github.com/HubSpot/HubSpot-public-api-spec-collection/blob/main/PublicApiSpecs/CRM/Feedback%20Submissions/Rollouts/424/v3/feedbackSubmissions.json). These changes are done in order to improve the overall usability, and as workarounds for some known language limitations. -[//]: # (TODO: Add sanitation details) -1. -2. -3. +1. Change the `url` property of the servers object + +- **Original**: + `https://api.hubspot.com` + +- **Updated**: + `https://api.hubapi.com/crm/v3/objects/orders` + +- **Reason**: This change of adding the common prefix `/crm/v3/objects/orders` to the base url makes it easier to access endpoints using the client. + +2. Update the API Paths + +- **Original**: Paths included common prefix above in each endpoint. (eg: `/crm/v3/objects/orders/search`) + +- **Updated**: Common prefix is now removed from the endpoints as it is included in the base URL. + + - **Original**: `/crm/v3/objects/orders/search` + - **Updated**: `/search` + +- **Reason**: This change simplifies the API paths, making them shorter and more readable. ## OpenAPI cli command The following command was used to generate the Ballerina client from the OpenAPI specification. The command should be executed from the repository root directory. ```bash -# TODO: Add OpenAPI CLI command used to generate the client +bal openapi -i docs/spec/openapi.json --mode client -o ballerina ``` -Note: The license year is hardcoded to 2024, change if necessary. + +Note: The license year is hardcoded to 2025, change if necessary. diff --git a/examples/README.md b/examples/README.md index 8806ac8..1f9ed23 100644 --- a/examples/README.md +++ b/examples/README.md @@ -2,13 +2,32 @@ The `ballerinax/hubspot.crm.commerce.orders` connector provides practical examples illustrating usage in various scenarios. -[//]: # (TODO: Add examples) -1. -2. - ## Prerequisites -[//]: # (TODO: Add prerequisites) +- **Ballerina:** Download and install Ballerina from [here](https://ballerina.io/downloads/). +- **HubSpot developer account:** Create a HubSpot developer account and create an app to obtain the necessary credentials. Refer to the [Setup Guide](../ballerina/Package.md) for instructions. +- **`hubspot.crm.commerce.orders` module:** Import the `ballerinax/hubspot.crm.commerce.orders` module into your Ballerina project and configure it with the obtained credentials. Refer to the [Config.toml.template](./order-creation/Config.toml.template) file for creating the `Config.toml` file. + +``` +import ballerinax/hubspot.crm.commerce.orders as orders; + +configurable string clientId = ?; +configurable string clientSecret = ?; +configurable string refreshToken = ?; + +public function main() returns error?{ + orders:ConnectionConfig config = { + auth: { + clientId: clientId, + clientSecret: clientSecret, + refreshToken: refreshToken, + credentialBearer: oauth2:POST_BODY_BEARER + } + }; + + final orders:Client baseClient = check new orders:Client(config, serviceUrl); +} +``` ## Running an example diff --git a/examples/batch-operations/.devcontainer.json b/examples/batch-operations/.devcontainer.json new file mode 100644 index 0000000..75bd926 --- /dev/null +++ b/examples/batch-operations/.devcontainer.json @@ -0,0 +1,8 @@ +{ + "image": "ballerina/ballerina-devcontainer:2201.10.3", + "customizations": { + "vscode": { + "extensions": ["WSO2.ballerina"] + } + } +} diff --git a/examples/batch-operations/Ballerina.toml b/examples/batch-operations/Ballerina.toml new file mode 100644 index 0000000..f800788 --- /dev/null +++ b/examples/batch-operations/Ballerina.toml @@ -0,0 +1,8 @@ +[package] +org = "wso2" +name = "batch_operations" +version = "0.1.0" +distribution = "2201.10.3" + +[build-options] +observabilityIncluded = true diff --git a/examples/batch-operations/Batch Operations.md b/examples/batch-operations/Batch Operations.md new file mode 100644 index 0000000..09a875c --- /dev/null +++ b/examples/batch-operations/Batch Operations.md @@ -0,0 +1,58 @@ +# Batch Operations Example for HubSpot Orders + +This Ballerina example demonstrates how to perform batch operations (create and update) on orders in HubSpot using the `ballerinax/hubspot.crm.commerce.orders` connector. + +## Prerequisites + +- **Ballerina Swan Lake:** Install from [here](https://ballerina.io/downloads/). +- **HubSpot Developer Account:** Obtain API credentials (Client ID, Client Secret, Refresh Token). + +## Setup + +1. Create a `Config.toml` file with your HubSpot credentials: + + ```toml + clientId = "" + clientSecret = "" + refreshToken = "" + ``` + +2. Place the `Config.toml` in the root directory of this example. + +## Running the Example + +Navigate to this example's directory and execute: + +* To build the example: + + ```bash + bal build + ``` + +* To run the example: + + ```bash + bal run + ``` + +## Code Overview + +- **`main()`:** Initializes the HubSpot client and triggers batch operations. +- **`handleBatchOperations()`:** Manages batch creation and updating of orders. +- **`batchCreateOrders()`:** Creates a batch of new orders in HubSpot. +- **`batchUpdateOrders()`:** Updates properties of a batch of existing orders. + +## Dependencies + +- `ballerina/io` +- `ballerina/oauth2` +- `ballerinax/hubspot.crm.commerce.orders` + +## Notes + +- Ensure the `Config.toml` file is properly configured with your HubSpot API credentials. +- Dependencies should be set up in your project. + +## License + +Licensed under the Apache License 2.0. See [LICENSE](http://www.apache.org/licenses/LICENSE-2.0) for details. diff --git a/examples/batch-operations/Config.toml.template b/examples/batch-operations/Config.toml.template new file mode 100644 index 0000000..6f94451 --- /dev/null +++ b/examples/batch-operations/Config.toml.template @@ -0,0 +1,3 @@ +clientId = "" +clientSecret = "" +refreshToken = "" diff --git a/examples/batch-operations/Dependencies.toml b/examples/batch-operations/Dependencies.toml new file mode 100644 index 0000000..8523bec --- /dev/null +++ b/examples/batch-operations/Dependencies.toml @@ -0,0 +1,314 @@ +# AUTO-GENERATED FILE. DO NOT MODIFY. + +# This file is auto-generated by Ballerina for managing dependency versions. +# It should not be modified by hand. + +[ballerina] +dependencies-toml-version = "2" +distribution-version = "2201.10.3" + +[[package]] +org = "ballerina" +name = "auth" +version = "2.12.0" +dependencies = [ + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.array"}, + {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "log"} +] + +[[package]] +org = "ballerina" +name = "cache" +version = "3.8.0" +dependencies = [ + {org = "ballerina", name = "constraint"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "task"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "constraint" +version = "1.5.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "crypto" +version = "2.7.2" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "file" +version = "1.10.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "os"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "http" +version = "2.12.4" +dependencies = [ + {org = "ballerina", name = "auth"}, + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "constraint"}, + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "file"}, + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "jwt"}, + {org = "ballerina", name = "lang.array"}, + {org = "ballerina", name = "lang.decimal"}, + {org = "ballerina", name = "lang.int"}, + {org = "ballerina", name = "lang.regexp"}, + {org = "ballerina", name = "lang.runtime"}, + {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "lang.value"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "mime"}, + {org = "ballerina", name = "oauth2"}, + {org = "ballerina", name = "observe"}, + {org = "ballerina", name = "time"}, + {org = "ballerina", name = "url"} +] + +[[package]] +org = "ballerina" +name = "io" +version = "1.6.3" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"} +] +modules = [ + {org = "ballerina", packageName = "io", moduleName = "io"} +] + +[[package]] +org = "ballerina" +name = "jballerina.java" +version = "0.0.0" + +[[package]] +org = "ballerina" +name = "jwt" +version = "2.13.0" +dependencies = [ + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.int"}, + {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "lang.__internal" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.object"} +] + +[[package]] +org = "ballerina" +name = "lang.array" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.__internal"} +] + +[[package]] +org = "ballerina" +name = "lang.decimal" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.int" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.__internal"}, + {org = "ballerina", name = "lang.object"} +] + +[[package]] +org = "ballerina" +name = "lang.object" +version = "0.0.0" + +[[package]] +org = "ballerina" +name = "lang.regexp" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.runtime" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.string" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.regexp"} +] + +[[package]] +org = "ballerina" +name = "lang.value" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "log" +version = "2.10.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"}, + {org = "ballerina", name = "observe"} +] + +[[package]] +org = "ballerina" +name = "mime" +version = "2.10.1" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.int"}, + {org = "ballerina", name = "log"} +] + +[[package]] +org = "ballerina" +name = "oauth2" +version = "2.12.0" +dependencies = [ + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "time"}, + {org = "ballerina", name = "url"} +] +modules = [ + {org = "ballerina", packageName = "oauth2", moduleName = "oauth2"} +] + +[[package]] +org = "ballerina" +name = "observe" +version = "1.3.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "os" +version = "1.8.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "task" +version = "2.5.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "time" +version = "2.5.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "url" +version = "2.4.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerinai" +name = "observe" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "observe"} +] +modules = [ + {org = "ballerinai", packageName = "observe", moduleName = "observe"} +] + +[[package]] +org = "ballerinax" +name = "hubspot.crm.commerce.orders" +version = "1.0.0" +dependencies = [ + {org = "ballerina", name = "http"}, + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "oauth2"}, + {org = "ballerina", name = "url"}, + {org = "ballerinai", name = "observe"} +] +modules = [ + {org = "ballerinax", packageName = "hubspot.crm.commerce.orders", moduleName = "hubspot.crm.commerce.orders"} +] + +[[package]] +org = "wso2" +name = "batch_operations" +version = "0.1.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "oauth2"}, + {org = "ballerinai", name = "observe"}, + {org = "ballerinax", name = "hubspot.crm.commerce.orders"} +] +modules = [ + {org = "wso2", packageName = "batch_operations", moduleName = "batch_operations"} +] diff --git a/examples/batch-operations/main.bal b/examples/batch-operations/main.bal new file mode 100644 index 0000000..1f25719 --- /dev/null +++ b/examples/batch-operations/main.bal @@ -0,0 +1,113 @@ +// Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/io; +import ballerina/oauth2; +import ballerinax/hubspot.crm.commerce.orders as orders; + +// Configuration for HubSpot API client credentials +configurable string clientId = ?; +configurable string clientSecret = ?; +configurable string refreshToken = ?; + +public function main() returns error? { + // Initialize the HubSpot client with the given configuration + orders:ConnectionConfig config = { + auth: { + clientId, + clientSecret, + refreshToken, + credentialBearer: oauth2:POST_BODY_BEARER + } + }; + final orders:Client hubspotClient = check new orders:Client( + config, serviceUrl = "https://api.hubapi.com/crm/v3/objects"); + io:println("HubSpot Client initialized successfully."); + + // Handle batch operations + check handleBatchOperations(hubspotClient); +} + +// Function to handle all batch operations +function handleBatchOperations(orders:Client hubspotClient) returns error? { + io:println("Starting Batch Operations..."); + + // Perform batch create orders + check batchCreateOrders(hubspotClient); + + // Perform batch update orders + check batchUpdateOrders(hubspotClient); + + io:println("Batch Operations Completed."); +} + +// Function to create a batch of orders +function batchCreateOrders(orders:Client hubspotClient) returns error? { + orders:BatchInputSimplePublicObjectInputForCreate batchCreateRequest = { + inputs: [ + { + associations: [ + { + types: [ + { + "associationCategory": "HUBSPOT_DEFINED", + "associationTypeId": 512 + } + ], + to: { + id: "31440573867" + } + } + ], + properties: { + "hs_currency_code": "USD" + } + } + ] + }; + + orders:BatchResponseSimplePublicObject|error response = + hubspotClient->/orders/batch/create.post(batchCreateRequest); + if response is orders:BatchResponseSimplePublicObject { + io:println("Batch of orders created successfully."); + } else { + io:println("Failed to create batch of orders."); + return error("Batch creation failed."); + } +} + +// Function to update a batch of orders +function batchUpdateOrders(orders:Client hubspotClient) returns error? { + orders:BatchInputSimplePublicObjectBatchInput batchUpdateRequest = { + inputs: [ + { + id: "394961395351", + properties: { + "hs_fulfillment_status": "Delivered" + } + } + ] + }; + + orders:BatchResponseSimplePublicObject|error response = + hubspotClient->/orders/batch/update.post(batchUpdateRequest); + if response is orders:BatchResponseSimplePublicObject { + io:println("Batch of orders updated successfully."); + } else { + io:println("Failed to update batch of orders."); + return error("Batch update failed."); + } +} diff --git a/examples/order-management/.devcontainer.json b/examples/order-management/.devcontainer.json new file mode 100644 index 0000000..75bd926 --- /dev/null +++ b/examples/order-management/.devcontainer.json @@ -0,0 +1,8 @@ +{ + "image": "ballerina/ballerina-devcontainer:2201.10.3", + "customizations": { + "vscode": { + "extensions": ["WSO2.ballerina"] + } + } +} diff --git a/examples/order-management/Ballerina.toml b/examples/order-management/Ballerina.toml new file mode 100644 index 0000000..8be3a10 --- /dev/null +++ b/examples/order-management/Ballerina.toml @@ -0,0 +1,8 @@ +[package] +org = "wso2" +name = "order_management" +version = "0.1.0" +distribution = "2201.10.3" + +[build-options] +observabilityIncluded = true diff --git a/examples/order-management/Config.toml.template b/examples/order-management/Config.toml.template new file mode 100644 index 0000000..6f94451 --- /dev/null +++ b/examples/order-management/Config.toml.template @@ -0,0 +1,3 @@ +clientId = "" +clientSecret = "" +refreshToken = "" diff --git a/examples/order-management/Dependencies.toml b/examples/order-management/Dependencies.toml new file mode 100644 index 0000000..4150669 --- /dev/null +++ b/examples/order-management/Dependencies.toml @@ -0,0 +1,319 @@ +# AUTO-GENERATED FILE. DO NOT MODIFY. + +# This file is auto-generated by Ballerina for managing dependency versions. +# It should not be modified by hand. + +[ballerina] +dependencies-toml-version = "2" +distribution-version = "2201.10.3" + +[[package]] +org = "ballerina" +name = "auth" +version = "2.12.0" +dependencies = [ + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.array"}, + {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "log"} +] + +[[package]] +org = "ballerina" +name = "cache" +version = "3.8.0" +dependencies = [ + {org = "ballerina", name = "constraint"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "task"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "constraint" +version = "1.5.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "crypto" +version = "2.7.2" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "file" +version = "1.10.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "os"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "http" +version = "2.12.4" +dependencies = [ + {org = "ballerina", name = "auth"}, + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "constraint"}, + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "file"}, + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "jwt"}, + {org = "ballerina", name = "lang.array"}, + {org = "ballerina", name = "lang.decimal"}, + {org = "ballerina", name = "lang.int"}, + {org = "ballerina", name = "lang.regexp"}, + {org = "ballerina", name = "lang.runtime"}, + {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "lang.value"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "mime"}, + {org = "ballerina", name = "oauth2"}, + {org = "ballerina", name = "observe"}, + {org = "ballerina", name = "time"}, + {org = "ballerina", name = "url"} +] +modules = [ + {org = "ballerina", packageName = "http", moduleName = "http"}, + {org = "ballerina", packageName = "http", moduleName = "http.httpscerr"} +] + +[[package]] +org = "ballerina" +name = "io" +version = "1.6.3" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"} +] +modules = [ + {org = "ballerina", packageName = "io", moduleName = "io"} +] + +[[package]] +org = "ballerina" +name = "jballerina.java" +version = "0.0.0" + +[[package]] +org = "ballerina" +name = "jwt" +version = "2.13.0" +dependencies = [ + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.int"}, + {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "lang.__internal" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.object"} +] + +[[package]] +org = "ballerina" +name = "lang.array" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.__internal"} +] + +[[package]] +org = "ballerina" +name = "lang.decimal" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.int" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.__internal"}, + {org = "ballerina", name = "lang.object"} +] + +[[package]] +org = "ballerina" +name = "lang.object" +version = "0.0.0" + +[[package]] +org = "ballerina" +name = "lang.regexp" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.runtime" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.string" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.regexp"} +] + +[[package]] +org = "ballerina" +name = "lang.value" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "log" +version = "2.10.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"}, + {org = "ballerina", name = "observe"} +] + +[[package]] +org = "ballerina" +name = "mime" +version = "2.10.1" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.int"}, + {org = "ballerina", name = "log"} +] + +[[package]] +org = "ballerina" +name = "oauth2" +version = "2.12.0" +dependencies = [ + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "time"}, + {org = "ballerina", name = "url"} +] +modules = [ + {org = "ballerina", packageName = "oauth2", moduleName = "oauth2"} +] + +[[package]] +org = "ballerina" +name = "observe" +version = "1.3.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "os" +version = "1.8.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "task" +version = "2.5.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "time" +version = "2.5.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "url" +version = "2.4.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerinai" +name = "observe" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "observe"} +] +modules = [ + {org = "ballerinai", packageName = "observe", moduleName = "observe"} +] + +[[package]] +org = "ballerinax" +name = "hubspot.crm.commerce.orders" +version = "1.0.0" +dependencies = [ + {org = "ballerina", name = "http"}, + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "oauth2"}, + {org = "ballerina", name = "url"}, + {org = "ballerinai", name = "observe"} +] +modules = [ + {org = "ballerinax", packageName = "hubspot.crm.commerce.orders", moduleName = "hubspot.crm.commerce.orders"} +] + +[[package]] +org = "wso2" +name = "order_management" +version = "0.1.0" +dependencies = [ + {org = "ballerina", name = "http"}, + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "oauth2"}, + {org = "ballerinai", name = "observe"}, + {org = "ballerinax", name = "hubspot.crm.commerce.orders"} +] +modules = [ + {org = "wso2", packageName = "order_management", moduleName = "order_management"} +] diff --git a/examples/order-management/Order Management.md b/examples/order-management/Order Management.md new file mode 100644 index 0000000..aff8773 --- /dev/null +++ b/examples/order-management/Order Management.md @@ -0,0 +1,60 @@ +# Order Management Example for HubSpot Orders + +This Ballerina example demonstrates how to manage orders in HubSpot using the `ballerinax/hubspot.crm.commerce.orders` connector. The example covers creating, reading, updating, and deleting orders. + +## Prerequisites + +- **Ballerina Swan Lake:** Install from [here](https://ballerina.io/downloads/). +- **HubSpot Developer Account:** Obtain API credentials (Client ID, Client Secret, Refresh Token). + +## Setup + +1. Create a `Config.toml` file with your HubSpot credentials: + + ```toml + clientId = "" + clientSecret = "" + refreshToken = "" + ``` + +2. Place the `Config.toml` in the root directory of this example. + +## Running the Example + +Navigate to this example's directory and execute: + +* To build the example: + + ```bash + bal build + ``` + +* To run the example: + + ```bash + bal run + ``` + +## Code Overview + +- **`main()`:** Initializes the HubSpot client and triggers order management operations. +- **`handleOrderManagement()`:** Manages the lifecycle of an order (create, read, update, delete). +- **`createOrder()`:** Creates a new order in HubSpot. +- **`readOrder()`:** Retrieves details of a specific order. +- **`updateOrder()`:** Updates properties of an existing order. +- **`deleteOrder()`:** Deletes an order from HubSpot. + +## Dependencies + +- `ballerina/io` +- `ballerina/oauth2` +- `ballerinax/hubspot.crm.commerce.orders` + +## Notes + +- Ensure the `Config.toml` file is properly configured with your HubSpot API credentials. +- Dependencies should be set up in your project. + +## License + +Licensed under the Apache License 2.0. See [LICENSE](http://www.apache.org/licenses/LICENSE-2.0) for details. diff --git a/examples/order-management/main.bal b/examples/order-management/main.bal new file mode 100644 index 0000000..7ab99f1 --- /dev/null +++ b/examples/order-management/main.bal @@ -0,0 +1,126 @@ +// Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/http; +import ballerina/io; +import ballerina/oauth2; +import ballerinax/hubspot.crm.commerce.orders as orders; + +// Configuration for HubSpot API client credentials +configurable string clientId = ?; +configurable string clientSecret = ?; +configurable string refreshToken = ?; + +public function main() returns error? { + // Initialize the HubSpot client with the given configuration + orders:ConnectionConfig config = { + auth: { + clientId, + clientSecret, + refreshToken, + credentialBearer: oauth2:POST_BODY_BEARER + } + }; + final orders:Client hubspotClient = check new orders:Client( + config, serviceUrl = "https://api.hubapi.com/crm/v3/objects"); + io:println("HubSpot Client initialized successfully."); + check handleOrderManagement(hubspotClient); +} + +function handleOrderManagement(orders:Client hubspotClient) returns error? { + io:println("Starting Order Management..."); + string|error? newOrderId = ""; + newOrderId = createOrder(hubspotClient); + if newOrderId is string { + check readOrder(hubspotClient, newOrderId); + check updateOrder(hubspotClient, newOrderId); + check deleteOrder(hubspotClient, newOrderId); + } else { + io:println("Failed to create order."); + io:println("Order Management Exited With Error."); + } + io:println("Order Management Completed."); +} + +// Create a new order +function createOrder(orders:Client hubspotClient) returns string|error? { + orders:SimplePublicObjectInputForCreate newOrder = + { + associations: [ + { + to: { + id: "31440573867" + }, + types: [ + { + associationCategory: "HUBSPOT_DEFINED", + associationTypeId: 512 + } + ] + } + ], + objectWriteTraceId: null, + properties: { + "hs_order_name": "Camping supplies", + "hs_currency_code": "USD", + "hs_source_store": "REI - Portland", + "hs_fulfillment_status": "Packing", + "hs_shipping_address_city": "Portland", + "hs_shipping_address_state": "Maine", + "hs_shipping_address_street": "123 Fake Street" + } + }; + orders:SimplePublicObject|error response = hubspotClient->/orders.post(newOrder); + if response is orders:SimplePublicObject { + io:println("Order created successfully with ID: ", response.id); + return response.id; + } else { + io:println("Failed to create order."); + return error("Failed to create order."); + } +} + +function readOrder(orders:Client hubspotClient, string orderId) returns error? { + var response = hubspotClient->/orders/[orderId]; + if response is orders:SimplePublicObjectWithAssociations { + io:println("Order details retrieved: ", response); + } else { + io:println("Failed to retrieve order with ID: ", orderId); + } +} + +function updateOrder(orders:Client hubspotClient, string orderId) returns error? { + orders:SimplePublicObjectInput updateDetails = { + properties: { + "hs_fulfillment_status": "Shipped" + } + }; + var response = hubspotClient->/orders/[orderId].patch(updateDetails); + if response is orders:SimplePublicObject { + io:println("Order updated successfully with ID: ", response.id); + } else { + io:println("Failed to update order with ID: ", orderId); + } +} + +function deleteOrder(orders:Client hubspotClient, string orderId) returns error? { + var response = hubspotClient->/orders/[orderId].delete(); + if response is http:Response { + io:println("Order deleted successfully with status: ", response.statusCode); + } else { + io:println("Failed to delete order with ID: ", orderId); + } +} diff --git a/examples/search-operation/.devcontainer.json b/examples/search-operation/.devcontainer.json new file mode 100644 index 0000000..75bd926 --- /dev/null +++ b/examples/search-operation/.devcontainer.json @@ -0,0 +1,8 @@ +{ + "image": "ballerina/ballerina-devcontainer:2201.10.3", + "customizations": { + "vscode": { + "extensions": ["WSO2.ballerina"] + } + } +} diff --git a/examples/search-operation/Ballerina.toml b/examples/search-operation/Ballerina.toml new file mode 100644 index 0000000..cfbb6dc --- /dev/null +++ b/examples/search-operation/Ballerina.toml @@ -0,0 +1,8 @@ +[package] +org = "wso2" +name = "search_operation" +version = "0.1.0" +distribution = "2201.10.3" + +[build-options] +observabilityIncluded = true diff --git a/examples/search-operation/Config.toml.template b/examples/search-operation/Config.toml.template new file mode 100644 index 0000000..6f94451 --- /dev/null +++ b/examples/search-operation/Config.toml.template @@ -0,0 +1,3 @@ +clientId = "" +clientSecret = "" +refreshToken = "" diff --git a/examples/search-operation/Dependencies.toml b/examples/search-operation/Dependencies.toml new file mode 100644 index 0000000..fda4e80 --- /dev/null +++ b/examples/search-operation/Dependencies.toml @@ -0,0 +1,314 @@ +# AUTO-GENERATED FILE. DO NOT MODIFY. + +# This file is auto-generated by Ballerina for managing dependency versions. +# It should not be modified by hand. + +[ballerina] +dependencies-toml-version = "2" +distribution-version = "2201.10.3" + +[[package]] +org = "ballerina" +name = "auth" +version = "2.12.0" +dependencies = [ + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.array"}, + {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "log"} +] + +[[package]] +org = "ballerina" +name = "cache" +version = "3.8.0" +dependencies = [ + {org = "ballerina", name = "constraint"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "task"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "constraint" +version = "1.5.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "crypto" +version = "2.7.2" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "file" +version = "1.10.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "os"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "http" +version = "2.12.4" +dependencies = [ + {org = "ballerina", name = "auth"}, + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "constraint"}, + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "file"}, + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "jwt"}, + {org = "ballerina", name = "lang.array"}, + {org = "ballerina", name = "lang.decimal"}, + {org = "ballerina", name = "lang.int"}, + {org = "ballerina", name = "lang.regexp"}, + {org = "ballerina", name = "lang.runtime"}, + {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "lang.value"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "mime"}, + {org = "ballerina", name = "oauth2"}, + {org = "ballerina", name = "observe"}, + {org = "ballerina", name = "time"}, + {org = "ballerina", name = "url"} +] + +[[package]] +org = "ballerina" +name = "io" +version = "1.6.3" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"} +] +modules = [ + {org = "ballerina", packageName = "io", moduleName = "io"} +] + +[[package]] +org = "ballerina" +name = "jballerina.java" +version = "0.0.0" + +[[package]] +org = "ballerina" +name = "jwt" +version = "2.13.0" +dependencies = [ + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.int"}, + {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "lang.__internal" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.object"} +] + +[[package]] +org = "ballerina" +name = "lang.array" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.__internal"} +] + +[[package]] +org = "ballerina" +name = "lang.decimal" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.int" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.__internal"}, + {org = "ballerina", name = "lang.object"} +] + +[[package]] +org = "ballerina" +name = "lang.object" +version = "0.0.0" + +[[package]] +org = "ballerina" +name = "lang.regexp" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.runtime" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.string" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.regexp"} +] + +[[package]] +org = "ballerina" +name = "lang.value" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "log" +version = "2.10.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"}, + {org = "ballerina", name = "observe"} +] + +[[package]] +org = "ballerina" +name = "mime" +version = "2.10.1" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.int"}, + {org = "ballerina", name = "log"} +] + +[[package]] +org = "ballerina" +name = "oauth2" +version = "2.12.0" +dependencies = [ + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "time"}, + {org = "ballerina", name = "url"} +] +modules = [ + {org = "ballerina", packageName = "oauth2", moduleName = "oauth2"} +] + +[[package]] +org = "ballerina" +name = "observe" +version = "1.3.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "os" +version = "1.8.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "task" +version = "2.5.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "time" +version = "2.5.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "url" +version = "2.4.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerinai" +name = "observe" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "observe"} +] +modules = [ + {org = "ballerinai", packageName = "observe", moduleName = "observe"} +] + +[[package]] +org = "ballerinax" +name = "hubspot.crm.commerce.orders" +version = "1.0.0" +dependencies = [ + {org = "ballerina", name = "http"}, + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "oauth2"}, + {org = "ballerina", name = "url"}, + {org = "ballerinai", name = "observe"} +] +modules = [ + {org = "ballerinax", packageName = "hubspot.crm.commerce.orders", moduleName = "hubspot.crm.commerce.orders"} +] + +[[package]] +org = "wso2" +name = "search_operation" +version = "0.1.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "oauth2"}, + {org = "ballerinai", name = "observe"}, + {org = "ballerinax", name = "hubspot.crm.commerce.orders"} +] +modules = [ + {org = "wso2", packageName = "search_operation", moduleName = "search_operation"} +] diff --git a/examples/search-operation/Search Operations.md b/examples/search-operation/Search Operations.md new file mode 100644 index 0000000..83f8715 --- /dev/null +++ b/examples/search-operation/Search Operations.md @@ -0,0 +1,57 @@ +# Search Operations Example for HubSpot Orders + +This Ballerina example demonstrates how to perform search operations on orders in HubSpot using the `ballerinax/hubspot.crm.commerce.orders` connector. + +## Prerequisites + +- **Ballerina Swan Lake:** Install from [here](https://ballerina.io/downloads/). +- **HubSpot Developer Account:** Obtain API credentials (Client ID, Client Secret, Refresh Token). + +## Setup + +1. Create a `Config.toml` file with your HubSpot credentials: + + ```toml + clientId = "" + clientSecret = "" + refreshToken = "" + ``` + +2. Place the `Config.toml` in the root directory of this example. + +## Running the Example + +Navigate to this example's directory and execute: + +* To build the example: + + ```bash + bal build + ``` + +* To run the example: + + ```bash + bal run + ``` + +## Code Overview + +- **`main()`:** Initializes the HubSpot client and triggers search operations. +- **`handleSearchOperations()`:** Manages the search operation for orders. +- **`searchOrders()`:** Searches for orders based on specified criteria. + +## Dependencies + +- `ballerina/io` +- `ballerina/oauth2` +- `ballerinax/hubspot.crm.commerce.orders` + +## Notes + +- Ensure the `Config.toml` file is properly configured with your HubSpot API credentials. +- Dependencies should be set up in your project. + +## License + +Licensed under the Apache License 2.0. See [LICENSE](http://www.apache.org/licenses/LICENSE-2.0) for details. diff --git a/examples/search-operation/main.bal b/examples/search-operation/main.bal new file mode 100644 index 0000000..1bd2b9f --- /dev/null +++ b/examples/search-operation/main.bal @@ -0,0 +1,80 @@ +// Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/io; +import ballerina/oauth2; +import ballerinax/hubspot.crm.commerce.orders as orders; + +// Configuration for HubSpot API client credentials +configurable string clientId = ?; +configurable string clientSecret = ?; +configurable string refreshToken = ?; + +public function main() returns error? { + // Initialize the HubSpot client with the given configuration + orders:ConnectionConfig config = { + auth: { + clientId, + clientSecret, + refreshToken, + credentialBearer: oauth2:POST_BODY_BEARER + } + }; + final orders:Client hubspotClient = check new orders:Client( + config, serviceUrl = "https://api.hubapi.com/crm/v3/objects"); + io:println("HubSpot Client initialized successfully."); + + // Handle search operations + check handleSearchOperations(hubspotClient); +} + +// Function to handle all search operations +function handleSearchOperations(orders:Client hubspotClient) returns error? { + io:println("Starting Search Operations..."); + + // Perform order search + check searchOrders(hubspotClient); + + io:println("Search Operations Completed."); +} + +// Function to search for orders based on specific criteria +function searchOrders(orders:Client hubspotClient) returns error? { + orders:PublicObjectSearchRequest searchRequest = { + query: "example", + properties: ["hs_order_name", "hs_currency_code"], + filterGroups: [ + { + filters: [ + { + propertyName: "hs_order_name", + operator: "EQ", + value: "New Order Example" + } + ] + } + ] + }; + + orders:CollectionResponseWithTotalSimplePublicObjectForwardPaging|error response = + hubspotClient->/orders/search.post(searchRequest); + if response is orders:CollectionResponseWithTotalSimplePublicObjectForwardPaging { + io:println("Search results: ", response.results.length(), " orders found."); + } else { + io:println("No orders found matching search criteria."); + return error("Search operation failed."); + } +}