├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── Auth.php ├── Client.php ├── Errors │ ├── AuthorizationException.php │ ├── ResponseException.php │ ├── TiktokShopException.php │ └── TokenException.php ├── Resource.php ├── Resources │ ├── AffiliateCreator.php │ ├── AffiliatePartner.php │ ├── AffiliateSeller.php │ ├── Analytics.php │ ├── Authorization.php │ ├── CustomerService.php │ ├── Event.php │ ├── Finance.php │ ├── FulfilledByTiktok.php │ ├── Fulfillment.php │ ├── GlobalProduct.php │ ├── Logistic.php │ ├── Order.php │ ├── Product.php │ ├── Promotion.php │ ├── ReturnRefund.php │ ├── Seller.php │ └── Supplychain.php └── Webhook.php └── tests ├── AuthTest.php ├── ClientTest.php ├── ResourceTest.php ├── Resources ├── AffiliateCreatorTest.php ├── AffiliatePartnerTest.php ├── AffiliateSellerTest.php ├── AnalyticsTest.php ├── AuthorizationTest.php ├── CustomerServiceTest.php ├── EventTest.php ├── FinanceTest.php ├── FulfilledByTiktokTest.php ├── FulfillmentTest.php ├── GlobalProductTest.php ├── LogisticTest.php ├── OrderTest.php ├── ProductTest.php ├── PromotionTest.php ├── ReturnRefundTest.php ├── SellerTest.php └── SupplychainTest.php ├── TestResource.php └── WebhookTest.php /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: [] 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | tests: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | fail-fast: true 19 | matrix: 20 | php: [ '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3' ] 21 | 22 | name: PHP ${{ matrix.php }} 23 | 24 | steps: 25 | - name: Checkout code 26 | uses: actions/checkout@v3 27 | 28 | - name: Setup PHP 29 | uses: shivammathur/setup-php@v2 30 | with: 31 | php-version: ${{ matrix.php }} 32 | tools: composer:v2 33 | coverage: xdebug 34 | 35 | - name: Cache Composer packages 36 | id: composer-cache 37 | uses: actions/cache@v3 38 | with: 39 | path: vendor 40 | key: ${{ runner.os }}-php-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }} 41 | restore-keys: | 42 | ${{ runner.os }}-php-${{ matrix.php }}- 43 | 44 | - name: Install dependencies 45 | run: composer install --prefer-dist --no-progress 46 | 47 | - name: Run test suite 48 | run: composer run-script test 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | coverage 3 | composer.lock 4 | .phpunit.* 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## v1.1.0 4 | 5 | ### Changes 6 | 7 | * Added webhook receiver integration 8 | 9 | ## v1.0.0 (Sep 20, 2022) 10 | 11 | ### Stable release 12 | 13 | * Support all Tiktok Shop API 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TiktokShop API Client in PHP 2 | 3 | [![Total Downloads](https://poser.pugx.org/ecomphp/tiktokshop-php/downloads)](https://packagist.org/packages/ecomphp/tiktokshop-php) 4 | [![Latest Stable Version](https://poser.pugx.org/ecomphp/tiktokshop-php/v/stable)](https://packagist.org/packages/ecomphp/tiktokshop-php) 5 | [![Latest Unstable Version](https://poser.pugx.org/ecomphp/tiktokshop-php/v/unstable)](https://packagist.org/packages/ecomphp/tiktokshop-php) 6 | [![Build Status](https://img.shields.io/github/actions/workflow/status/ecomphp/tiktokshop-php/ci.yml?branch=master&label=ci%20build&style=flat-square)](https://github.com/ecomphp/tiktokshop-php/actions?query=workflow%3ATest) 7 | [![License](https://poser.pugx.org/ecomphp/tiktokshop-php/license)](https://packagist.org/packages/ecomphp/tiktokshop-php) 8 | 9 | Tiktok Shop API Client is a simple SDK implementation of Tiktok Shop API. 10 | 11 | Since v2.x, library used API version 202309 and later. For older API version, please use v1.x 12 | 13 | ## Installation 14 | 15 | Install with Composer 16 | 17 | ```shell 18 | composer require ecomphp/tiktokshop-php 19 | ``` 20 | 21 | ## Configure TiktokShop PHP Client 22 | 23 | ```php 24 | use EcomPHP\TiktokShop\Client; 25 | 26 | $app_key = 'your app key'; 27 | $app_secret = 'your app secret'; 28 | 29 | $client = new Client($app_key, $app_secret); 30 | ``` 31 | 32 | ## Grant token 33 | 34 | There is a Auth class to help you getting the token from the shop using oAuth. 35 | 36 | ```php 37 | $auth = $client->auth(); 38 | ``` 39 | 40 | 1) Create the authentication request 41 | 42 | ```php 43 | $_SESSION['state'] = $state = str_random(40); // random string 44 | $auth->createAuthRequest($state); 45 | ``` 46 | 47 | > If you want the function to return the authentication url instead of auto-redirecting, you can set the argument $return (2nd argument) to true. 48 | 49 | ```php 50 | $authUrl = $auth->createAuthRequest($state, true); 51 | 52 | // redirect user to auth url 53 | header('Location: '.$authUrl); 54 | ``` 55 | 56 | 2) Get authentication code when redirected back to `Redirect callback URL` after app authorization and exchange it for access token 57 | 58 | ```php 59 | $authorization_code = $_GET['code']; 60 | $token = $auth->getToken($authorization_code); 61 | 62 | $access_token = $token['access_token']; 63 | $refresh_token = $token['refresh_token']; 64 | ``` 65 | 66 | 3) Get authorized Shop cipher 67 | 68 | ```php 69 | $access_token = $token['access_token']; 70 | $client->setAccessToken($access_token); 71 | 72 | $authorizedShopList = $client->Authorization->getAuthorizedShop(); 73 | 74 | // extract shop_id & cipher from $authorizedShopList for use later 75 | ``` 76 | 77 | ## Refresh your access token 78 | 79 | > Access token will be expired soon, so you need refresh new token by using `refresh_token` 80 | 81 | ```php 82 | $new_token = $auth->refreshNewToken($refresh_token); 83 | 84 | $new_access_token = $new_token['access_token']; 85 | $new_refresh_token = $new_token['refresh_token']; 86 | ``` 87 | ## Usage API Example 88 | 89 | > You need `access_token` and `shop_cipher` to start using TiktokShop API 90 | 91 | ```php 92 | $client = new Client($app_key, $app_secret); 93 | $client->setAccessToken($access_token); 94 | $client->setShopCipher($shop_cipher); 95 | ``` 96 | 97 | * Get product list: [api document](https://developers.tiktok-shops.com/documents/document/237487) 98 | 99 | ```php 100 | $products = $client->Product->getProductList([ 101 | 'page_size' => 50, 102 | ]); 103 | ``` 104 | 105 | * Get order list: [api document](https://developers.tiktok-shops.com/documents/document/237434) 106 | 107 | ```php 108 | $orders = $client->Order->getOrderList([ 109 | 'order_status' => 100, // Unpaid order 110 | 'page_size' => 50, 111 | ]); 112 | ``` 113 | 114 | ## Change API version 115 | 116 | > As default, API version 202309 will be used in every api call. Use example below to change it 117 | 118 | ```php 119 | $products = $client->Product->useVersion('202312')->checkListingPrerequisites(); 120 | ``` 121 | 122 | ## Webhook 123 | 124 | Use webhook to receive incoming notification from tiktok shop 125 | 126 | ```php 127 | $webhook = $client->webhook(); 128 | ``` 129 | 130 | or manually configure the webhook receiver 131 | 132 | ```php 133 | use EcomPHP\TiktokShop\Webhook; 134 | use EcomPHP\TiktokShop\Errors\TiktokShopException; 135 | 136 | $webhook = new Webhook($client); 137 | try { 138 | $webhook->verify(); 139 | $webhook->capture($_POST); 140 | } catch (TiktokShopException $e) { 141 | echo "webhook error: " . $e->getMessage() . "\n"; 142 | } 143 | ``` 144 | 145 | ```php 146 | echo "Type: " . $webhook->getType() . "\n"; 147 | echo "Timestamp: " . $webhook->getTimestamp() . "\n"; 148 | echo "Shop ID: " . $webhook->getShopId() . "\n"; 149 | echo "Data: \n"; // data is array 150 | print_r($webhook->getData()); 151 | 152 | ``` 153 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ecomphp/tiktokshop-php", 3 | "description": "Unofficial Tiktok Shop API Client in PHP", 4 | "type": "library", 5 | "license": "Apache-2.0", 6 | "authors": [ 7 | { 8 | "name": "Jin", 9 | "email": "j@sax.vn", 10 | "role": "Developer" 11 | } 12 | ], 13 | "require": { 14 | "php": "^7.2|^8.0", 15 | "ext-json": "*", 16 | "guzzlehttp/guzzle": "^7.3|^6.5" 17 | }, 18 | "require-dev": { 19 | "phpunit/phpunit": "^8|^9" 20 | }, 21 | "autoload": { 22 | "psr-4": { 23 | "EcomPHP\\TiktokShop\\": "src" 24 | } 25 | }, 26 | "autoload-dev": { 27 | "psr-4": { 28 | "EcomPHP\\TiktokShop\\Tests\\": "tests" 29 | } 30 | }, 31 | "scripts": { 32 | "test": "vendor/bin/phpunit" 33 | }, 34 | "extra": { 35 | "branch-alias": { 36 | "dev-master": "2.0.x-dev" 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | src/ 7 | 8 | 9 | 10 | 11 | ./tests/ 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Auth.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop; 12 | 13 | use GuzzleHttp\Client as GuzzleHttpClient; 14 | use GuzzleHttp\RequestOptions; 15 | use EcomPHP\TiktokShop\Errors\AuthorizationException; 16 | 17 | class Auth 18 | { 19 | protected $client; 20 | 21 | protected $httpClient; 22 | 23 | protected $authHost; 24 | 25 | public function __construct(Client $client) 26 | { 27 | $this->client = $client; 28 | $this->httpClient = new GuzzleHttpClient(); 29 | 30 | $this->authHost = 'https://auth.tiktok-shops.com'; 31 | } 32 | 33 | public function createAuthRequest($state = null, $returnAuthUrl = false) 34 | { 35 | $params = [ 36 | 'app_key' => $this->client->getAppKey(), 37 | 'state' => $state ?? rand(10000, 99999), 38 | ]; 39 | 40 | $authUrl = $this->authHost . '/oauth/authorize?' . http_build_query($params); 41 | 42 | if ($returnAuthUrl) { 43 | return $authUrl; 44 | } 45 | 46 | header('Location: '.$authUrl); 47 | exit; 48 | } 49 | 50 | public function getToken($code) 51 | { 52 | $response = $this->httpClient->get($this->authHost . '/api/v2/token/get', [ 53 | RequestOptions::QUERY => [ 54 | 'app_key' => $this->client->getAppKey(), 55 | 'app_secret' => $this->client->getAppSecret(), 56 | 'auth_code' => $code, 57 | 'grant_type' => 'authorized_code', 58 | ], 59 | ]); 60 | 61 | $json = json_decode($response->getBody(), true); 62 | if ($json['code'] !== 0) { 63 | throw new AuthorizationException($json['message'], $json['code']); 64 | } 65 | 66 | return $json['data']; 67 | } 68 | 69 | public function refreshNewToken($refresh_token) 70 | { 71 | $response = $this->httpClient->get($this->authHost . '/api/v2/token/refresh', [ 72 | RequestOptions::QUERY => [ 73 | 'app_key' => $this->client->getAppKey(), 74 | 'app_secret' => $this->client->getAppSecret(), 75 | 'refresh_token' => $refresh_token, 76 | 'grant_type' => 'refresh_token', 77 | ], 78 | ]); 79 | 80 | $json = json_decode($response->getBody(), true); 81 | if ($json['code'] !== 0) { 82 | throw new AuthorizationException($json['message'], $json['code']); 83 | } 84 | 85 | return $json['data']; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Client.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop; 12 | 13 | use EcomPHP\TiktokShop\Errors\ResponseException; 14 | use EcomPHP\TiktokShop\Resources\AffiliateCreator; 15 | use EcomPHP\TiktokShop\Resources\AffiliatePartner; 16 | use EcomPHP\TiktokShop\Resources\AffiliateSeller; 17 | use EcomPHP\TiktokShop\Resources\Analytics; 18 | use EcomPHP\TiktokShop\Resources\CustomerService; 19 | use EcomPHP\TiktokShop\Resources\FulfilledByTiktok; 20 | use GuzzleHttp\Exception\GuzzleException; 21 | use GuzzleHttp\HandlerStack; 22 | use GuzzleHttp\Client as GuzzleHttpClient; 23 | use GuzzleHttp\Middleware; 24 | use GuzzleHttp\RequestOptions; 25 | use EcomPHP\TiktokShop\Errors\TiktokShopException; 26 | use EcomPHP\TiktokShop\Resources\Event; 27 | use EcomPHP\TiktokShop\Resources\Finance; 28 | use EcomPHP\TiktokShop\Resources\Fulfillment; 29 | use EcomPHP\TiktokShop\Resources\GlobalProduct; 30 | use EcomPHP\TiktokShop\Resources\Logistic; 31 | use EcomPHP\TiktokShop\Resources\Order; 32 | use EcomPHP\TiktokShop\Resources\Product; 33 | use EcomPHP\TiktokShop\Resources\Promotion; 34 | use EcomPHP\TiktokShop\Resources\ReturnRefund; 35 | use EcomPHP\TiktokShop\Resources\Seller; 36 | use EcomPHP\TiktokShop\Resources\Authorization; 37 | use EcomPHP\TiktokShop\Resources\Supplychain; 38 | use Psr\Http\Message\RequestInterface; 39 | 40 | /** 41 | * @property-read Authorization $Authorization 42 | * @property-read Seller $Seller 43 | * @property-read Product $Product 44 | * @property-read Order $Order 45 | * @property-read Fulfillment $Fulfillment 46 | * @property-read Logistic $Logistic 47 | * @property-read Finance $Finance 48 | * @property-read GlobalProduct $GlobalProduct 49 | * @property-read Promotion $Promotion 50 | * @property-read Supplychain $Supplychain 51 | * @property-read Event $Event 52 | * @property-read ReturnRefund $ReturnRefund 53 | * @property-read CustomerService $CustomerService 54 | * @property-read AffiliateSeller $AffiliateSeller 55 | * @property-read AffiliateCreator $AffiliateCreator 56 | * @property-read AffiliatePartner $AffiliatePartner 57 | */ 58 | class Client 59 | { 60 | public CONST DEFAULT_VERSION = '202309'; 61 | protected $app_key; 62 | protected $app_secret; 63 | protected $access_token; 64 | 65 | /** 66 | * required for calling cross-border shop 67 | */ 68 | protected $shop_cipher; 69 | protected $version; 70 | 71 | /** 72 | * custom guzzle client options 73 | * 74 | * @var array 75 | * @see https://docs.guzzlephp.org/en/stable/request-options.html 76 | */ 77 | protected $options; 78 | 79 | public const resources = [ 80 | Authorization::class, 81 | Seller::class, 82 | Product::class, 83 | Order::class, 84 | Fulfillment::class, 85 | Logistic::class, 86 | Finance::class, 87 | GlobalProduct::class, 88 | Promotion::class, 89 | Supplychain::class, 90 | Event::class, 91 | ReturnRefund::class, 92 | CustomerService::class, 93 | AffiliateSeller::class, 94 | AffiliateCreator::class, 95 | AffiliatePartner::class, 96 | Analytics::class, 97 | FulfilledByTiktok::class, 98 | ]; 99 | 100 | public function __construct($app_key, $app_secret, $options = []) 101 | { 102 | $this->app_key = $app_key; 103 | $this->app_secret = $app_secret; 104 | $this->options = $options; 105 | 106 | $this->useVersion(static::DEFAULT_VERSION); 107 | } 108 | 109 | public function useSandboxMode() 110 | { 111 | trigger_deprecation('ecomphp/tiktokshop-php', '2.0.0', 'useSandboxMode() will be deprecated: Since API version 202309, Tiktokshop API sandbox is no longer worked, please use production environment.'); 112 | } 113 | 114 | /** 115 | * Change default api version for all resources called from this client 116 | */ 117 | public function useVersion($version) 118 | { 119 | $this->version = $version; 120 | } 121 | 122 | public function getAppKey() 123 | { 124 | return $this->app_key; 125 | } 126 | 127 | public function getAppSecret() 128 | { 129 | return $this->app_secret; 130 | } 131 | 132 | public function setAccessToken($access_token) 133 | { 134 | $this->access_token = $access_token; 135 | } 136 | 137 | public function setShopCipher($shop_cipher) 138 | { 139 | $this->shop_cipher = $shop_cipher; 140 | } 141 | 142 | public function auth() 143 | { 144 | return new Auth($this); 145 | } 146 | 147 | public function webhook() 148 | { 149 | $webhook = new Webhook($this); 150 | $webhook->verify(); 151 | $webhook->capture(); 152 | 153 | return $webhook; 154 | } 155 | 156 | /** 157 | * append app_key, timestamp, version, shop_id, access_token, sign to request 158 | * 159 | * @param RequestInterface $request 160 | * @return RequestInterface 161 | */ 162 | protected function modifyRequestBeforeSend(RequestInterface $request) 163 | { 164 | $uri = $request->getUri(); 165 | parse_str($uri->getQuery(), $query); 166 | 167 | $query['app_key'] = $this->getAppKey(); 168 | $query['timestamp'] = time(); 169 | 170 | if ($this->access_token && !isset($query['x-tts-access-token'])) { 171 | $request = $request->withHeader('x-tts-access-token', $this->access_token); 172 | } 173 | 174 | if ($this->shop_cipher && !isset($query['shop_cipher'])) { 175 | $query['shop_cipher'] = $this->shop_cipher; 176 | } 177 | 178 | // shop_cipher is not allowed in some api 179 | if (preg_match('/^\/product\/(\d{6})\/(compliance|global_products|files\/upload|images\/upload)/', $uri->getPath()) 180 | || ($request->getMethod() === 'POST' && preg_match('/^\/product\/(\d{6})\/(brands)/', $uri->getPath())) 181 | || preg_match('/^\/(authorization|seller)\/(\d{6})\//', $uri->getPath()) 182 | ) { 183 | unset($query['shop_cipher']); 184 | } 185 | 186 | $this->prepareSignature($request, $query); 187 | 188 | $uri = $uri->withQuery(http_build_query($query)); 189 | 190 | // set default content-type to application/json 191 | if (!$request->getHeaderLine('content-type')) { 192 | $request = $request->withHeader('content-type', 'application/json'); 193 | } 194 | 195 | return $request->withUri($uri); 196 | } 197 | 198 | protected function httpClient() 199 | { 200 | $stack = HandlerStack::create(); 201 | $stack->push(Middleware::mapRequest(function (RequestInterface $request) { 202 | return $this->modifyRequestBeforeSend($request); 203 | })); 204 | 205 | $options = array_merge([ 206 | RequestOptions::HTTP_ERRORS => false, // disable throw exception on http 4xx, manual handle it 207 | 'handler' => $stack, 208 | 'base_uri' => 'https://open-api.tiktokglobalshop.com/', 209 | ], $this->options ?? []); 210 | 211 | return new GuzzleHttpClient($options); 212 | } 213 | 214 | /** 215 | * tiktokshop api signature algorithm 216 | * @see https://partner.tiktokshop.com/doc/page/274638 217 | * 218 | * @param RequestInterface $request 219 | * @param $params 220 | * @return void 221 | */ 222 | protected function prepareSignature($request, &$params) 223 | { 224 | $paramsToBeSigned = $params; 225 | $stringToBeSigned = ''; 226 | 227 | // 1. Extract all query param EXCEPT ' sign ', ' access_token ', reorder the params based on alphabetical order. 228 | unset($paramsToBeSigned['sign'], $paramsToBeSigned['access_token'], $paramsToBeSigned['x-tts-access-token']); 229 | ksort($paramsToBeSigned); 230 | 231 | // 2. Concat all the param in the format of {key}{value} 232 | foreach ($paramsToBeSigned as $k => $v) { 233 | if (!is_array($v)) { 234 | $stringToBeSigned .= "$k$v"; 235 | } 236 | } 237 | 238 | // 3. Append the request path to the beginning 239 | $stringToBeSigned = $request->getUri()->getPath() . $stringToBeSigned; 240 | 241 | // 4. If the request header content_type is not multipart/form-data, append body to the end 242 | if ($request->getMethod() !== 'GET' && strpos($request->getHeaderLine('content-type'), 'multipart/form-data') === false) { 243 | $stringToBeSigned .= (string) $request->getBody(); 244 | } 245 | 246 | // 5. Wrap string generated in step 3 with app_secret. 247 | $stringToBeSigned = $this->getAppSecret() . $stringToBeSigned . $this->getAppSecret(); 248 | 249 | // Encode the digest byte stream in hexadecimal and use sha256 to generate sign with salt(secret). 250 | $params['sign'] = hash_hmac('sha256', $stringToBeSigned, $this->getAppSecret()); 251 | } 252 | 253 | /** 254 | * Magic call resource 255 | * 256 | * @param $resourceName 257 | * @throws TiktokShopException 258 | * @return mixed 259 | */ 260 | public function __get($resourceName) 261 | { 262 | $resourceClassName = __NAMESPACE__."\\Resources\\".$resourceName; 263 | if (!in_array($resourceClassName, self::resources)) { 264 | throw new TiktokShopException("Invalid resource ".$resourceName); 265 | } 266 | 267 | //Initiate the resource object 268 | $resource = new $resourceClassName(); 269 | if (!$resource instanceof Resource) { 270 | throw new TiktokShopException("Invalid resource class ".$resourceClassName); 271 | } 272 | 273 | $resource->useVersion($this->version); 274 | $resource->useHttpClient($this->httpClient()); 275 | 276 | return $resource; 277 | } 278 | 279 | public function call($method, $uri, $params = []) 280 | { 281 | try { 282 | $response = $this->httpClient()->request($method, $uri, $params); 283 | } catch (GuzzleException $e) { 284 | throw new ResponseException($e->getMessage(), $e->getCode(), $e); 285 | } 286 | 287 | $json = json_decode((string) $response->getBody(), true); 288 | 289 | if ($json === null) { 290 | throw new ResponseException('Unable to parse response string as JSON'); 291 | } 292 | 293 | return $json; 294 | } 295 | 296 | public function get($uri) 297 | { 298 | return $this->call('GET', $uri); 299 | } 300 | 301 | public function post($uri, $data) 302 | { 303 | return $this->call('POST', $uri, [ 304 | RequestOptions::JSON => $data, 305 | ]); 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /src/Errors/AuthorizationException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Errors; 12 | 13 | class AuthorizationException extends TiktokShopException 14 | { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/Errors/ResponseException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Errors; 12 | 13 | class ResponseException extends TiktokShopException 14 | { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/Errors/TiktokShopException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Errors; 12 | 13 | use Exception; 14 | 15 | class TiktokShopException extends Exception 16 | { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/Errors/TokenException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Errors; 12 | 13 | class TokenException extends ResponseException 14 | { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/Resource.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop; 12 | 13 | use DateTimeInterface; 14 | use EcomPHP\TiktokShop\Errors\TiktokShopException; 15 | use GuzzleHttp\Client; 16 | use GuzzleHttp\Exception\GuzzleException; 17 | use EcomPHP\TiktokShop\Client as TiktokShopClient; 18 | use EcomPHP\TiktokShop\Errors\ResponseException; 19 | use EcomPHP\TiktokShop\Errors\TokenException; 20 | 21 | abstract class Resource 22 | { 23 | /** @var Client */ 24 | protected $httpClient; 25 | 26 | protected $category = ''; 27 | 28 | protected $version = TiktokShopClient::DEFAULT_VERSION; 29 | 30 | protected $last_message = null; 31 | 32 | protected $last_request_id = null; 33 | 34 | protected $minimum_version = 202309; 35 | 36 | public function useVersion($version) 37 | { 38 | // version 202309 is minimum for this library 39 | $minimum_version = intval($this->minimum_version); 40 | if ($minimum_version < 202309) { 41 | $minimum_version = 202309; 42 | } 43 | 44 | if (intval($version) < $minimum_version) { 45 | throw new TiktokShopException('API version '.$this->minimum_version.' is the minimum requirement to access this resource'); 46 | } 47 | 48 | $this->version = $version; 49 | 50 | return $this; 51 | } 52 | 53 | public function useHttpClient(Client $client) 54 | { 55 | $this->httpClient = $client; 56 | 57 | return $this; 58 | } 59 | 60 | /** 61 | * @throws \EcomPHP\TiktokShop\Errors\TiktokShopException 62 | */ 63 | public function call($method, $action, $params = [], $version = null) 64 | { 65 | // if version not set or it's lower than default version 66 | if ($version === null || $this->version > intval($version)) { 67 | $version = $this->version; 68 | } 69 | 70 | $uri = trim($this->category.'/'.$version.'/'.$action, '/'); 71 | try { 72 | $response = $this->httpClient->request($method, $uri, $params); 73 | } catch (GuzzleException $e) { 74 | throw new ResponseException($e->getMessage(), $e->getCode(), $e); 75 | } 76 | 77 | $json = json_decode((string) $response->getBody(), true); 78 | 79 | if ($json === null) { 80 | throw new ResponseException('Unable to parse response string as JSON'); 81 | } 82 | 83 | $this->last_message = $json['message'] ?? null; 84 | $this->last_request_id = $json['request_id'] ?? null; 85 | 86 | $code = $json['code'] ?? -1; 87 | if ($code !== 0) { 88 | $this->handleErrorResponse($code, $json['message']); 89 | } 90 | 91 | return $json['data'] ?? []; 92 | } 93 | 94 | public function getLastMessage() 95 | { 96 | return $this->last_message; 97 | } 98 | 99 | public function getLastRequestId() 100 | { 101 | return $this->last_request_id; 102 | } 103 | 104 | /** 105 | * @throws ResponseException 106 | * @throws TokenException 107 | */ 108 | protected function handleErrorResponse($code, $message) 109 | { 110 | // get 3 first digit as the error code group 111 | // more detail: https://partner.tiktokshop.com/doc/page/234136 112 | $errorGroup = substr(strval($code), 0, 3); 113 | 114 | switch ($errorGroup) { 115 | case '105': 116 | case '360': 117 | throw new TokenException($message, $code); 118 | default: 119 | throw new ResponseException($message, $code); 120 | } 121 | } 122 | 123 | static public function extractParams($array, &$query, &$body) 124 | { 125 | $body = $array; 126 | unset($body['page_size'], $body['sort_order'], $body['page_token'], $body['sort_field']); 127 | 128 | $query = array_filter([ 129 | 'page_size' => $array['page_size'] ?? 20, 130 | 'sort_order' => $array['sort_order'] ?? null, 131 | 'page_token' => $array['page_token'] ?? null, 132 | 'sort_field' => $array['sort_field'] ?? null, 133 | ]); 134 | } 135 | 136 | static public function dataTypeCast($type, $data) 137 | { 138 | switch ($type) { 139 | case 'image': 140 | case 'file': 141 | if (is_resource($data)) { 142 | return $data; 143 | } 144 | 145 | return fopen($data, 'r'); 146 | case 'int': 147 | case 'integer': 148 | return intval($data); 149 | case 'array': 150 | return implode(',', is_array($data) ? $data : [$data]); 151 | case 'bool': 152 | case 'boolean': 153 | return boolval($data); 154 | case 'timestamp': 155 | $timestamp = $data; 156 | 157 | if (!$timestamp) { 158 | return time(); 159 | } 160 | 161 | if ($timestamp instanceof DateTimeInterface) { 162 | return $timestamp->getTimestamp(); 163 | } 164 | 165 | if (is_string($timestamp)) { 166 | $timestamp = strtotime($timestamp) ?: time(); 167 | } 168 | 169 | return $timestamp; 170 | case 'string': 171 | default: 172 | return strval($data); 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/Resources/AffiliateCreator.php: -------------------------------------------------------------------------------- 1 | All rights reserved. 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Resources; 12 | 13 | use EcomPHP\TiktokShop\Resource; 14 | use GuzzleHttp\RequestOptions; 15 | 16 | class AffiliateCreator extends Resource 17 | { 18 | protected $category = 'affiliate_creator'; 19 | protected $minimum_version = 202405; 20 | 21 | public function addShowcaseProducts($add_type, $product_ids = [], $product_link = '') 22 | { 23 | return $this->call('POST', 'showcases/products/add', [ 24 | 'add_type' => $add_type, 25 | 'product_ids' => $product_ids, 26 | 'product_link' => $product_link, 27 | ]); 28 | } 29 | 30 | public function getShowcaseProducts($query = []) 31 | { 32 | $query = array_merge([ 33 | 'page_size' => 10, 34 | 'origin' => 'LIVE', 35 | ], $query); 36 | 37 | return $this->call('GET', 'showcases/products', [ 38 | RequestOptions::QUERY => $query, 39 | ]); 40 | } 41 | 42 | public function getCreatorProfile() 43 | { 44 | return $this->call('GET', 'profiles'); 45 | } 46 | 47 | public function searchOpenCollaborationProduct($query = [], $body = []) 48 | { 49 | $query = array_merge([ 50 | 'page_size' => 10, 51 | ], $query); 52 | 53 | return $this->call('POST', 'open_collaborations/products/search', [ 54 | RequestOptions::QUERY => $query, 55 | RequestOptions::JSON => $body, 56 | ]); 57 | } 58 | 59 | public function searchTargetCollaborations($query, $body = []) 60 | { 61 | $query = array_merge([ 62 | 'page_size' => 10, 63 | ], $query); 64 | 65 | return $this->call('POST', 'target_collaborations/search', [ 66 | RequestOptions::QUERY => $query, 67 | RequestOptions::JSON => $body, 68 | ]); 69 | } 70 | 71 | public function searchAffiliateOrders($query = []) 72 | { 73 | $query = array_merge([ 74 | 'page_size' => 10, 75 | ], $query); 76 | 77 | return $this->call('POST', 'orders/search', [ 78 | RequestOptions::QUERY => $query, 79 | RequestOptions::JSON => [], 80 | ]); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Resources/AffiliatePartner.php: -------------------------------------------------------------------------------- 1 | All rights reserved. 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Resources; 12 | 13 | use EcomPHP\TiktokShop\Resource; 14 | use GuzzleHttp\RequestOptions; 15 | 16 | class AffiliatePartner extends Resource 17 | { 18 | protected $category = 'affiliate_partner'; 19 | protected $minimum_version = 202405; 20 | 21 | public function createAffiliatePartnerCampaign($query, $body) 22 | { 23 | return $this->call('POST', 'campaigns', [ 24 | RequestOptions::QUERY => $query, 25 | RequestOptions::JSON => $body, 26 | ]); 27 | } 28 | 29 | public function getAffiliatePartnerCampaignProductList($campaign_id, $query) 30 | { 31 | return $this->call('GET', 'campaigns/'.$campaign_id.'/products', [ 32 | RequestOptions::QUERY => $query, 33 | ]); 34 | } 35 | 36 | public function getAffiliatePartnerCampaignDetail($campaign_id, $query) 37 | { 38 | return $this->call('GET', 'campaigns/'.$campaign_id, [ 39 | RequestOptions::QUERY => $query, 40 | ]); 41 | } 42 | 43 | public function reviewAffiliatePartnerCampaignProduct($campaign_id, $product_id, $query, $body) 44 | { 45 | return $this->call('POST', 'campaigns/'.$campaign_id.'/products/'.$product_id.'/review', [ 46 | RequestOptions::QUERY => $query, 47 | RequestOptions::JSON => $body, 48 | ]); 49 | } 50 | 51 | public function editAffiliatePartnerCampaign($campaign_id, $query, $body) 52 | { 53 | return $this->call('POST', 'campaigns/'.$campaign_id.'/partial_edit', [ 54 | RequestOptions::QUERY => $query, 55 | RequestOptions::JSON => $body, 56 | ]); 57 | } 58 | 59 | public function getAffiliatePartnerCampaignList($query) 60 | { 61 | return $this->call('GET', 'campaigns', [ 62 | RequestOptions::QUERY => $query, 63 | ]); 64 | } 65 | 66 | public function publishAffiliatePartnerCampaign($campaign_id, $query) 67 | { 68 | return $this->call('POST', 'campaigns/'.$campaign_id.'/publish', [ 69 | RequestOptions::QUERY => $query, 70 | ]); 71 | } 72 | 73 | public function generateAffiliatePartnerCampaignProductLink($campaign_id, $product_id, $query, $body) 74 | { 75 | return $this->call('POST', 'campaigns/'.$campaign_id.'/products/'.$product_id.'/promotion_link/generate', [ 76 | RequestOptions::QUERY => $query, 77 | RequestOptions::JSON => $body, 78 | ]); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Resources/AffiliateSeller.php: -------------------------------------------------------------------------------- 1 | All rights reserved. 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Resources; 12 | 13 | use EcomPHP\TiktokShop\Resource; 14 | use GuzzleHttp\RequestOptions; 15 | 16 | class AffiliateSeller extends Resource 17 | { 18 | protected $category = 'affiliate_seller'; 19 | protected $minimum_version = 202405; 20 | 21 | public function editOpenCollaborationSettings($body) 22 | { 23 | return $this->call('POST', 'open_collaboration_settings', [ 24 | RequestOptions::JSON => $body, 25 | ]); 26 | } 27 | 28 | public function searchOpenCollaborationProduct($query = [], $body = []) 29 | { 30 | $query = array_merge([ 31 | 'page_size' => 10, 32 | ], $query); 33 | 34 | return $this->call('POST', 'open_collaborations/products/search', [ 35 | RequestOptions::QUERY => $query, 36 | RequestOptions::JSON => $body, 37 | ]); 38 | } 39 | 40 | public function searchSellerAffiliateOrders($query = []) 41 | { 42 | $query = array_merge([ 43 | 'page_size' => 10, 44 | ], $query); 45 | 46 | return $this->call('POST', 'orders/search', [ 47 | RequestOptions::QUERY => $query, 48 | RequestOptions::JSON => [], 49 | ]); 50 | } 51 | 52 | public function createOpenCollaboration($product_id, $commission_rate, $require_seller_approve_creator = false) 53 | { 54 | return $this->call('POST', 'open_collaborations', [ 55 | RequestOptions::JSON => [ 56 | 'product_id' => $product_id, 57 | 'commission_rate' => $commission_rate, 58 | 'require_seller_approve_creator' => $require_seller_approve_creator, 59 | ], 60 | ]); 61 | } 62 | 63 | public function createTargetCollaboration($body) 64 | { 65 | return $this->call('POST', 'target_collaborations', [ 66 | RequestOptions::JSON => $body, 67 | ]); 68 | } 69 | 70 | public function removeCreatorAffiliateFromCollaboration($open_collaboration_id, $creator_user_id, $product_id) 71 | { 72 | return $this->call('POST', 'open_collaborations/'.$open_collaboration_id.'/remove_creator', [ 73 | RequestOptions::JSON => [ 74 | 'creator_user_id' => $creator_user_id, 75 | 'product_id' => $product_id, 76 | ], 77 | ]); 78 | } 79 | 80 | public function getMarketplaceCreatorPerformance($creator_user_id) 81 | { 82 | return $this->call('GET', 'marketplace_creators/'.$creator_user_id, [], 202406); 83 | } 84 | 85 | public function searchCreatorOnMarketplace($query = [], $body = []) 86 | { 87 | $query = array_merge([ 88 | 'page_size' => 12, 89 | ], $query); 90 | 91 | return $this->call('POST', 'marketplace_creators/search', [ 92 | RequestOptions::QUERY => $query, 93 | RequestOptions::JSON => $body, 94 | ], 202406); 95 | } 96 | 97 | public function generateAffiliateProductPromotionLink($product_id) 98 | { 99 | return $this->call('POST', 'products/'.$product_id.'/promotion_link/generate'); 100 | } 101 | 102 | public function searchSampleApplicationsFulfillments($application_id, $body = []) 103 | { 104 | return $this->call('POST', 'sample_applications/'.$application_id.'/fulfillments/search', [ 105 | RequestOptions::JSON => $body, 106 | ], 202409); 107 | } 108 | 109 | public function reviewSampleApplications($application_id, $review_result, $reject_reason = '') 110 | { 111 | return $this->call('POST', 'sample_applications/'.$application_id.'/review', [ 112 | RequestOptions::JSON => [ 113 | 'review_result' => $review_result, 114 | 'reject_reason' => $reject_reason, 115 | ], 116 | ], 202409); 117 | } 118 | 119 | public function getOpenCollaborationSampleRules($product_ids) 120 | { 121 | return $this->call('GET', 'open_collaborations/sample_rules', [ 122 | RequestOptions::QUERY => [ 123 | 'product_ids' => static::dataTypeCast('array', $product_ids), 124 | ], 125 | ], 202410); 126 | } 127 | 128 | public function searchSampleApplications($query = [], $body = []) 129 | { 130 | $query = array_merge([ 131 | 'page_size' => 20, 132 | ], $query); 133 | 134 | return $this->call('POST', 'sample_applications/search', [ 135 | RequestOptions::QUERY => $query, 136 | RequestOptions::JSON => $body, 137 | ], 202409); 138 | } 139 | 140 | public function editOpenCollaborationSampleRule($body = []) 141 | { 142 | return $this->call('POST', 'open_collaborations/sample_rules', [ 143 | RequestOptions::JSON => $body, 144 | ], 202410); 145 | } 146 | 147 | public function removeTargetCollaboration($target_collaboration_id) 148 | { 149 | return $this->call('DELETE', 'target_collaborations/'.$target_collaboration_id, 150 | [], 202409); 151 | } 152 | 153 | public function queryTargetCollaborationDetail($target_collaboration_id) 154 | { 155 | return $this->call('GET', 'target_collaborations/'.$target_collaboration_id, 156 | [], 202409); 157 | } 158 | 159 | public function searchTargetCollaborations($query = [], $body = []) 160 | { 161 | $query = array_merge([ 162 | 'page_size' => 20, 163 | ], $query); 164 | 165 | return $this->call('POST', 'target_collaborations/search', [ 166 | RequestOptions::QUERY => $query, 167 | RequestOptions::JSON => $body, 168 | ], 202409); 169 | } 170 | 171 | public function updateTargetCollaboration($target_collaboration_id, $body) 172 | { 173 | return $this->call('PUT', 'target_collaborations/'.$target_collaboration_id, [ 174 | RequestOptions::JSON => $body, 175 | ], 202409); 176 | } 177 | 178 | public function searchOpenCollaboration($query = [], $body = []) 179 | { 180 | $query = array_merge([ 181 | 'page_size' => 20, 182 | ], $query); 183 | 184 | return $this->call('POST', 'open_collaborations/search', [ 185 | RequestOptions::QUERY => $query, 186 | RequestOptions::JSON => $body, 187 | ], 202409); 188 | } 189 | 190 | public function getOpenCollaborationSettings() 191 | { 192 | return $this->call('GET', 'open_collaboration_settings', [], 202409); 193 | } 194 | 195 | public function removeOpenCollaboration($product_id) 196 | { 197 | return $this->call('DELETE', 'open_collaborations/products/'.$product_id, [], 202409); 198 | } 199 | 200 | public function getOpenCollaborationCreatorContentDetail($query = []) 201 | { 202 | $query = array_merge([ 203 | 'page_size' => 20, 204 | ], $query); 205 | return $this->call('GET', 'open_collaborations/creator_content_details', [ 206 | RequestOptions::QUERY => $query, 207 | ], 202412); 208 | } 209 | 210 | public function getMessageInConversation($conversation_id, $query = []) 211 | { 212 | $query = array_merge([ 213 | 'page_size' => 20, 214 | ], $query); 215 | 216 | return $this->call('GET', 'conversation/' . $conversation_id . '/messages', [ 217 | RequestOptions::QUERY => $query, 218 | ], 202412); 219 | } 220 | 221 | public function getConversationList($query = []) 222 | { 223 | $query = array_merge([ 224 | 'page_size' => 20, 225 | ], $query); 226 | 227 | return $this->call('GET', 'conversations', [ 228 | RequestOptions::QUERY => $query, 229 | ], 202412); 230 | } 231 | 232 | public function sendImMessage($conversation_id, $msg_type, $content) 233 | { 234 | return $this->call('POST', 'conversations/' . $conversation_id . '/messages', [ 235 | RequestOptions::JSON => [ 236 | 'msg_type' => $msg_type, 237 | 'content' => $content, 238 | ] 239 | ], 202412); 240 | } 241 | 242 | public function createConversationWithCreator($creator_id, $only_need_conversation_id = true) 243 | { 244 | return $this->call('POST', 'conversations', [ 245 | RequestOptions::JSON => [ 246 | 'creator_id' => $creator_id, 247 | 'only_need_conversation_id' => $only_need_conversation_id, 248 | ], 249 | ], 202412); 250 | } 251 | 252 | public function markConversationRead($conversation_ids = []) 253 | { 254 | return $this->call('POST', 'conversations/read', [ 255 | RequestOptions::JSON => [ 256 | 'conversation_ids' => $conversation_ids, 257 | ], 258 | ], 202412); 259 | } 260 | 261 | public function getLatestUnreadMessages() 262 | { 263 | return $this->call('GET', 'conversations/messages/list/newest', [], 202412); 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /src/Resources/Analytics.php: -------------------------------------------------------------------------------- 1 | All rights reserved. 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Resources; 12 | 13 | use EcomPHP\TiktokShop\Resource; 14 | use GuzzleHttp\RequestOptions; 15 | 16 | class Analytics extends Resource 17 | { 18 | protected $category = 'analytics'; 19 | protected $minimum_version = 202405; 20 | 21 | public function getShopPerformance($params = []) 22 | { 23 | return $this->call('GET', 'shop/performance', [ 24 | RequestOptions::QUERY => $params, 25 | ]); 26 | } 27 | 28 | public function getShopProductPerformance($product_id, $params = []) 29 | { 30 | return $this->call('GET', 'shop_products/'. $product_id .'/performance', [ 31 | RequestOptions::QUERY => $params, 32 | ]); 33 | } 34 | 35 | public function getShopProductPerformanceList($params = []) 36 | { 37 | return $this->call('GET', 'shop_products/performance', [ 38 | RequestOptions::QUERY => $params, 39 | ]); 40 | } 41 | 42 | public function getShopSkuPerformance($sku_id, $params = []) 43 | { 44 | return $this->call('GET', 'shop_skus/'. $sku_id .'/performance', [ 45 | RequestOptions::QUERY => $params, 46 | ]); 47 | } 48 | 49 | public function getShopSkuPerformanceList($params = []) 50 | { 51 | return $this->call('GET', 'shop_skus/performance', [ 52 | RequestOptions::QUERY => $params, 53 | ]); 54 | } 55 | 56 | public function getShopVideoPerformanceList($params = []) 57 | { 58 | return $this->call('GET', 'shop_videos/performance', [ 59 | RequestOptions::QUERY => $params, 60 | ]); 61 | } 62 | 63 | public function getShopVideoPerformanceOverview($params = []) 64 | { 65 | return $this->call('GET', 'shop_videos/overview_performance', [ 66 | RequestOptions::QUERY => $params, 67 | ]); 68 | } 69 | 70 | public function getShopVideoPerformance($video_id, $params = []) 71 | { 72 | return $this->call('GET', 'shop_videos/'. $video_id .'/performance', [ 73 | RequestOptions::QUERY => $params, 74 | ]); 75 | } 76 | 77 | public function getShopVideoProductPerformanceList($video_id, $params = []) 78 | { 79 | return $this->call('GET', 'shop_videos/'. $video_id .'/products/performance', [ 80 | RequestOptions::QUERY => $params, 81 | ]); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Resources/Authorization.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Resources; 12 | 13 | use EcomPHP\TiktokShop\Resource; 14 | 15 | class Authorization extends Resource 16 | { 17 | protected $category = 'authorization'; 18 | 19 | public function getAuthorizedShop() 20 | { 21 | return $this->call('GET', 'shops'); 22 | } 23 | 24 | public function getAuthorizedCategoryAssets() 25 | { 26 | return $this->call('GET', 'category_assets'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Resources/CustomerService.php: -------------------------------------------------------------------------------- 1 | All rights reserved. 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Resources; 12 | 13 | use EcomPHP\TiktokShop\Resource; 14 | use GuzzleHttp\RequestOptions; 15 | 16 | class CustomerService extends Resource 17 | { 18 | protected $category = 'customer_service'; 19 | 20 | public function getConversationMessages($conversation_id, $params = []) 21 | { 22 | $params = array_merge([ 23 | 'page_size' => 20, 24 | ], $params); 25 | 26 | return $this->call('GET', 'conversations/'.$conversation_id.'/messages', [ 27 | RequestOptions::QUERY => $params, 28 | ]); 29 | } 30 | 31 | public function getConversations($params = []) 32 | { 33 | $params = array_merge([ 34 | 'page_size' => 20, 35 | ], $params); 36 | 37 | return $this->call('GET', 'conversations', [ 38 | RequestOptions::QUERY => $params, 39 | ]); 40 | } 41 | 42 | public function sendMessage($conversation_id, $type, $content) 43 | { 44 | return $this->call('POST', 'conversations/'.$conversation_id.'/messages', [ 45 | RequestOptions::JSON => [ 46 | 'type' => $type, 47 | 'content' => is_array($content) ? json_encode($content) : $content, 48 | ] 49 | ]); 50 | } 51 | 52 | public function getAgentSettings() 53 | { 54 | return $this->call('GET', 'agents/settings'); 55 | } 56 | 57 | public function updateAgentSettings($body = []) 58 | { 59 | return $this->call('PUT', 'agents/settings', [ 60 | RequestOptions::JSON => $body, 61 | ]); 62 | } 63 | 64 | public function uploadBuyerMessagesImage($image) 65 | { 66 | return $this->call('POST', 'images/upload', [ 67 | RequestOptions::MULTIPART => [ 68 | [ 69 | 'name' => 'data', 70 | 'filename' => 'image', 71 | 'contents' => static::dataTypeCast('image', $image), 72 | ] 73 | ] 74 | ]); 75 | } 76 | 77 | public function readMessage($conversation_id) 78 | { 79 | return $this->call('POST', 'conversations/'.$conversation_id.'/messages/read'); 80 | } 81 | 82 | public function createConversation($buyer_user_id) 83 | { 84 | return $this->call('POST', 'conversations', [ 85 | RequestOptions::JSON => [ 86 | 'buyer_user_id' => $buyer_user_id, 87 | ] 88 | ]); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Resources/Event.php: -------------------------------------------------------------------------------- 1 | All rights reserved. 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Resources; 12 | 13 | use EcomPHP\TiktokShop\Resource; 14 | use GuzzleHttp\RequestOptions; 15 | 16 | class Event extends Resource 17 | { 18 | protected $category = 'event'; 19 | 20 | public function getShopWebhooks() 21 | { 22 | return $this->call('GET', 'webhooks'); 23 | } 24 | 25 | public function updateShopWebhook($event_type, $webhook_url) 26 | { 27 | return $this->call('PUT', 'webhooks', [ 28 | RequestOptions::JSON => [ 29 | 'address' => $webhook_url, 30 | 'event_type' => $event_type, 31 | ] 32 | ]); 33 | } 34 | 35 | public function deleteShopWebhook($event_type) 36 | { 37 | return $this->call('DELETE', 'webhooks', [ 38 | RequestOptions::JSON => [ 39 | 'event_type' => $event_type, 40 | ] 41 | ]); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Resources/Finance.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Resources; 12 | 13 | use GuzzleHttp\RequestOptions; 14 | use EcomPHP\TiktokShop\Resource; 15 | 16 | class Finance extends Resource 17 | { 18 | protected $category = 'finance'; 19 | 20 | public function getOrderStatementTransactions($order_id) 21 | { 22 | return $this->call('GET', 'orders/'.$order_id.'/statement_transactions'); 23 | } 24 | 25 | public function getStatementTransactions($statement_id, $params = []) 26 | { 27 | $params = array_merge([ 28 | 'sort_field' => 'order_create_time', // required 29 | ], $params); 30 | 31 | return $this->call('GET', 'statements/'.$statement_id.'/statement_transactions', [ 32 | RequestOptions::QUERY => $params, 33 | ]); 34 | } 35 | 36 | public function getWithdrawals($params = []) 37 | { 38 | $params = array_merge([ 39 | 'types' => 'WITHDRAW,SETTLE,TRANSFER,REVERSE', // required, default get all 40 | ], $params); 41 | 42 | return $this->call('GET', 'withdrawals', [ 43 | RequestOptions::QUERY => $params, 44 | ]); 45 | } 46 | 47 | public function getStatements($params = []) 48 | { 49 | $params = array_merge([ 50 | 'sort_field' => 'statement_time', // required 51 | ], $params); 52 | 53 | return $this->call('GET', 'statements', [ 54 | RequestOptions::QUERY => $params, 55 | ]); 56 | } 57 | 58 | public function getPayments($params = []) 59 | { 60 | $params = array_merge([ 61 | 'sort_field' => 'create_time', // required 62 | ], $params); 63 | 64 | return $this->call('GET', 'payments', [ 65 | RequestOptions::QUERY => $params, 66 | ]); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Resources/FulfilledByTiktok.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Resources; 12 | 13 | use EcomPHP\TiktokShop\Resource; 14 | use GuzzleHttp\RequestOptions; 15 | 16 | class FulfilledByTiktok extends Resource 17 | { 18 | protected $category = 'fbt'; 19 | protected $minimum_version = 202408; 20 | 21 | public function getFbtMerchantOnboardedRegions() 22 | { 23 | return $this->call('GET', 'merchants/onboarded_regions', [], 202409); 24 | } 25 | 26 | public function getFbtWarehouseList() 27 | { 28 | return $this->call('GET', 'warehouses'); 29 | } 30 | 31 | public function getInboundOrder($ids) 32 | { 33 | return $this->call('GET', 'inbound_orders', [ 34 | RequestOptions::QUERY => [ 35 | 'ids' => static::dataTypeCast('array', $ids) 36 | ] 37 | ], 202409); 38 | } 39 | 40 | public function searchFbtInventory($query = [], $body = []) 41 | { 42 | $query = array_merge([ 43 | 'page_size' => 10, 44 | ], $query); 45 | 46 | return $this->call('POST', 'inventory/search', [ 47 | RequestOptions::QUERY => $query, 48 | RequestOptions::JSON => $body, 49 | ]); 50 | } 51 | 52 | public function searchFbtInventoryRecord($query = [], $body = []) 53 | { 54 | $query = array_merge([ 55 | 'page_size' => 10, 56 | ], $query); 57 | 58 | return $this->call('POST', 'inventory_records/search', [ 59 | RequestOptions::QUERY => $query, 60 | RequestOptions::JSON => $body, 61 | ], 202410); 62 | } 63 | 64 | public function searchGoodsInfo($query = [], $body = []) 65 | { 66 | $query = array_merge([ 67 | 'page_size' => 10, 68 | ], $query); 69 | 70 | return $this->call('POST', 'goods/search', [ 71 | RequestOptions::QUERY => $query, 72 | RequestOptions::JSON => $body, 73 | ], 202409); 74 | } 75 | } -------------------------------------------------------------------------------- /src/Resources/Fulfillment.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Resources; 12 | 13 | use GuzzleHttp\RequestOptions; 14 | use EcomPHP\TiktokShop\Resource; 15 | use SplFileInfo; 16 | 17 | class Fulfillment extends Resource 18 | { 19 | protected $category = 'fulfillment'; 20 | 21 | public function searchCombinablePackages($query = []) 22 | { 23 | $query = array_merge([ 24 | 'page_size' => 20, 25 | ], $query); 26 | 27 | return $this->call('GET', 'combinable_packages/search', [ 28 | RequestOptions::QUERY => $query 29 | ]); 30 | } 31 | 32 | public function getPackageShippingDocument($package_id, $document_type, $document_size = 0) 33 | { 34 | return $this->call('GET', 'packages/'.$package_id.'/shipping_documents', [ 35 | RequestOptions::QUERY => [ 36 | 'document_type' => $document_type, 37 | 'document_size' => $document_size, 38 | ] 39 | ]); 40 | } 41 | 42 | public function getPackageHandoverTimeSlots($package_id) 43 | { 44 | return $this->call('GET', 'packages/'.$package_id.'/handover_time_slots'); 45 | } 46 | 47 | public function getTracking($order_id) 48 | { 49 | return $this->call('GET', 'orders/'.$order_id.'/tracking'); 50 | } 51 | 52 | public function updatePackageShippingInfo($package_id, $tracking_number, $shipping_provider_id) 53 | { 54 | return $this->call('POST', 'packages/'.$package_id.'/shipping_info/update', [ 55 | RequestOptions::JSON => [ 56 | 'tracking_number' => $tracking_number, 57 | 'shipping_provider_id' => $shipping_provider_id, 58 | ] 59 | ]); 60 | } 61 | 62 | public function searchPackage($query = [], $params = []) 63 | { 64 | $query = array_merge([ 65 | 'page_size' => 20, 66 | ], $query); 67 | 68 | return $this->call('POST', 'packages/search', [ 69 | RequestOptions::QUERY => $query, 70 | RequestOptions::JSON => $params, 71 | ]); 72 | } 73 | 74 | public function shipPackage($package_id, $handover_method = 'PICKUP', $pickup_slot = [], $self_shipment = []) 75 | { 76 | return $this->call('POST', 'packages/'.$package_id.'/ship', [ 77 | RequestOptions::JSON => [ 78 | 'handover_method' => $handover_method, 79 | 'pickup_slot' => $pickup_slot, 80 | 'self_shipment' => $self_shipment, 81 | ] 82 | ]); 83 | } 84 | 85 | public function getPackageDetail($package_id) 86 | { 87 | return $this->call('GET', 'packages/'.$package_id); 88 | } 89 | 90 | public function fulfillmentUploadDeliveryImage($image) 91 | { 92 | return $this->call('POST', 'images/upload', [ 93 | RequestOptions::MULTIPART => [ 94 | [ 95 | 'name' => 'data', 96 | 'filename' => 'image', 97 | 'contents' => static::dataTypeCast('image', $image), 98 | ], 99 | ], 100 | ]); 101 | } 102 | 103 | public function fulfillmentUploadDeliveryFile($file, $file_name = 'uploaded_file.pdf') 104 | { 105 | if ($file instanceof SplFileInfo) { 106 | $file_name = $file->getFilename(); 107 | } 108 | 109 | return $this->call('POST', 'files/upload', [ 110 | RequestOptions::MULTIPART => [ 111 | [ 112 | 'name' => 'data', 113 | 'filename' => $file_name, 114 | 'contents' => static::dataTypeCast('file', $file), 115 | ], 116 | [ 117 | 'name' => 'name', 118 | 'contents' => $file_name, 119 | ] 120 | ] 121 | ]); 122 | } 123 | 124 | public function updatePackageDeliveryStatus($packages = []) 125 | { 126 | return $this->call('POST', 'packages/deliver', [ 127 | RequestOptions::JSON => [ 128 | 'packages' => $packages, 129 | ], 130 | ]); 131 | } 132 | 133 | public function batchShipPackages($packages) 134 | { 135 | return $this->call('POST', 'packages/ship', [ 136 | RequestOptions::JSON => [ 137 | 'packages' => $packages, 138 | ], 139 | ]); 140 | } 141 | 142 | public function getOrderSplitAttributes($order_ids) 143 | { 144 | return $this->call('GET', 'orders/split_attributes', [ 145 | RequestOptions::QUERY => [ 146 | 'order_ids' => static::dataTypeCast('array', $order_ids), 147 | ], 148 | ]); 149 | } 150 | 151 | public function splitOrders($order_id, $splittable_groups) 152 | { 153 | return $this->call('POST', 'orders/'.$order_id.'/split', [ 154 | RequestOptions::JSON => [ 155 | 'order_id' => $order_id, 156 | 'splittable_groups' => $splittable_groups, 157 | ], 158 | ]); 159 | } 160 | 161 | public function combinePackage($combinable_packages) 162 | { 163 | return $this->call('POST', 'packages/combine', [ 164 | RequestOptions::JSON => [ 165 | 'combinable_packages' => $combinable_packages, 166 | ], 167 | ]); 168 | } 169 | 170 | public function uncombinePackages($package_id, $order_ids = []) 171 | { 172 | return $this->call('POST', 'packages/'.$package_id.'/uncombine', [ 173 | RequestOptions::JSON => [ 174 | 'order_ids' => $order_ids, 175 | ], 176 | ]); 177 | } 178 | 179 | public function markPackageAsShipped($order_id, $tracking_number, $shipping_provider_id, $order_line_item_ids = []) 180 | { 181 | return $this->call('POST', 'orders/'.$order_id.'/packages', [ 182 | RequestOptions::JSON => [ 183 | 'tracking_number' => $tracking_number, 184 | 'shipping_provider_id' => $shipping_provider_id, 185 | 'order_line_item_ids' => $order_line_item_ids, 186 | ], 187 | ]); 188 | } 189 | 190 | public function getEligibleShippingService($order_id, $params = []) 191 | { 192 | return $this->call('POST', 'orders/'.$order_id.'/shipping_services/query', [ 193 | RequestOptions::JSON => $params, 194 | ]); 195 | } 196 | 197 | public function createPackages($order_id, $params = []) 198 | { 199 | $params['order_id'] = $order_id; 200 | 201 | return $this->call('POST', 'packages', [ 202 | RequestOptions::JSON => $params, 203 | ]); 204 | } 205 | 206 | public function updateShippingInfo($order_id, $tracking_number, $shipping_provider_id) 207 | { 208 | return $this->call('POST', 'orders/'.$order_id.'/shipping_info/update', [ 209 | RequestOptions::JSON => [ 210 | 'tracking_number' => $tracking_number, 211 | 'shipping_provider_id' => $shipping_provider_id, 212 | ], 213 | ]); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/Resources/GlobalProduct.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Resources; 12 | 13 | use GuzzleHttp\RequestOptions; 14 | use EcomPHP\TiktokShop\Resource; 15 | 16 | class GlobalProduct extends Resource 17 | { 18 | protected $category = 'product'; 19 | 20 | public function createGlobalProduct($data) 21 | { 22 | return $this->call('POST', 'global_products', [ 23 | RequestOptions::JSON => $data, 24 | ]); 25 | } 26 | 27 | public function getGlobalProduct($global_product_id) 28 | { 29 | return $this->call('GET', 'global_products/'.$global_product_id); 30 | } 31 | 32 | public function publishGlobalProduct($global_product_id, $params = []) 33 | { 34 | return $this->call('POST', 'global_products/'.$global_product_id.'/publish', [ 35 | RequestOptions::JSON => $params 36 | ]); 37 | } 38 | 39 | public function getGlobalCategories($params = []) 40 | { 41 | return $this->call('GET', 'global_categories', [ 42 | RequestOptions::QUERY => $params, 43 | ]); 44 | } 45 | 46 | public function editGlobalProduct($global_product_id, $data = []) 47 | { 48 | return $this->call('PUT', 'global_products/'.$global_product_id, [ 49 | RequestOptions::JSON => $data 50 | ]); 51 | } 52 | 53 | public function getGlobalAttributes($category_id, $params = []) 54 | { 55 | return $this->call('GET', 'categories/'.$category_id.'/global_attributes', [ 56 | RequestOptions::QUERY => $params 57 | ]); 58 | } 59 | 60 | public function getGlobalCategoryRules($category_id) 61 | { 62 | return $this->call('GET', 'categories/'.$category_id.'/global_rules'); 63 | } 64 | 65 | public function recommendGlobalCategories($params = []) 66 | { 67 | return $this->call('POST', 'global_categories/recommend', [ 68 | RequestOptions::JSON => $params 69 | ]); 70 | } 71 | 72 | public function updateGlobalInventory($global_product_id, $params = []) 73 | { 74 | return $this->call('POST', 'global_products/'.$global_product_id.'/inventory/update', [ 75 | RequestOptions::JSON => $params 76 | ]); 77 | } 78 | 79 | public function searchGlobalProducts($params = [], $page_size = 20, $page_token = '') 80 | { 81 | return $this->call('POST', 'global_products/search', [ 82 | RequestOptions::QUERY => [ 83 | 'page_size' => $page_size, 84 | 'page_token' => $page_token, 85 | ], 86 | RequestOptions::JSON => $params 87 | ]); 88 | } 89 | 90 | public function deleteGlobalProducts($global_product_ids) 91 | { 92 | return $this->call('DELETE', 'global_products', [ 93 | RequestOptions::JSON => [ 94 | 'global_product_ids' => $global_product_ids, 95 | ] 96 | ]); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Resources/Logistic.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Resources; 12 | 13 | use EcomPHP\TiktokShop\Resource; 14 | 15 | class Logistic extends Resource 16 | { 17 | protected $category = 'logistics'; 18 | 19 | public function getWarehouseDeliveryOptions($warehouse_id) 20 | { 21 | return $this->call('GET', 'warehouses/'.$warehouse_id.'/delivery_options'); 22 | } 23 | 24 | public function getShippingProvider($delivery_option_id) 25 | { 26 | return $this->call('GET','delivery_options/'.$delivery_option_id.'/shipping_providers'); 27 | } 28 | 29 | public function getWarehouseList() 30 | { 31 | return $this->call('GET', 'warehouses'); 32 | } 33 | 34 | public function getGlobalSellerWarehouse() 35 | { 36 | return $this->call('GET', 'global_warehouses'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Resources/Order.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Resources; 12 | 13 | use GuzzleHttp\RequestOptions; 14 | use EcomPHP\TiktokShop\Resource; 15 | 16 | class Order extends Resource 17 | { 18 | // order status 19 | public const STATUS_UNPAID = 'UNPAID'; 20 | public const STATUS_AWAITING_SHIPMENT = 'AWAITING_SHIPMENT'; 21 | public const STATUS_AWAITING_COLLECTION = 'AWAITING_COLLECTION'; 22 | public const STATUS_PARTIALLY_SHIPPING = 'PARTIALLY_SHIPPING'; 23 | public const STATUS_IN_TRANSIT = 'IN_TRANSIT'; 24 | public const STATUS_DELIVERED = 'DELIVERED'; 25 | public const STATUS_COMPLETED = 'COMPLETED'; 26 | public const STATUS_CANCELLED = 'CANCELLED'; 27 | 28 | protected $category = 'order'; 29 | 30 | public function getOrderDetail($ids = []) 31 | { 32 | return $this->call('GET', 'orders', [ 33 | RequestOptions::QUERY => [ 34 | 'ids' => static::dataTypeCast('array', $ids), 35 | ] 36 | ]); 37 | } 38 | 39 | public function getOrderList($query = [], $body = null) 40 | { 41 | if ($body === null) { 42 | static::extractParams($query, $query, $body); 43 | } 44 | 45 | return $this->call('POST', 'orders/search', [ 46 | RequestOptions::QUERY => $query, 47 | is_array($body) ? RequestOptions::JSON : RequestOptions::FORM_PARAMS => $body, 48 | ]); 49 | } 50 | 51 | public function addExternalOrderReferences($orders) 52 | { 53 | return $this->call('POST', 'orders/external_orders', [ 54 | RequestOptions::JSON => [ 55 | 'orders' => $orders, 56 | ] 57 | ], 202406); 58 | } 59 | 60 | public function searchOrderByExternalOrderReferences($platform, $external_order_id) 61 | { 62 | return $this->call('POST', 'orders/external_order_search', [ 63 | RequestOptions::QUERY => [ 64 | 'platform' => $platform, 65 | 'external_order_id' => $external_order_id, 66 | ] 67 | ], 202406); 68 | } 69 | 70 | public function getExternalOrderReferences($order_id, $platform) 71 | { 72 | return $this->call('GET', 'orders/' . $order_id . '/external_orders', [ 73 | RequestOptions::QUERY => [ 74 | 'platform' => $platform, 75 | ] 76 | ], 202406); 77 | } 78 | 79 | public function getPriceDetail($order_id) 80 | { 81 | return $this->call('GET', 'orders/' . $order_id . '/price_detail', [], 202407); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Resources/Product.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Resources; 12 | 13 | use GuzzleHttp\RequestOptions; 14 | use SplFileInfo; 15 | 16 | /** 17 | * separate global product api to easier managed 18 | */ 19 | class Product extends GlobalProduct 20 | { 21 | protected $category = 'product'; 22 | 23 | public function uploadProductFile($file, $file_name = 'uploaded_file.pdf') 24 | { 25 | if ($file instanceof SplFileInfo) { 26 | $file_name = $file->getFilename(); 27 | } 28 | 29 | return $this->call('POST', 'files/upload', [ 30 | RequestOptions::MULTIPART => [ 31 | [ 32 | 'name' => 'data', 33 | 'filename' => $file_name, 34 | 'contents' => static::dataTypeCast('file', $file), 35 | ], 36 | [ 37 | 'name' => 'name', 38 | 'contents' => $file_name, 39 | ] 40 | ] 41 | ]); 42 | } 43 | 44 | public function uploadProductImage($image, $use_case = 'MAIN_IMAGE') 45 | { 46 | return $this->call('POST', 'images/upload', [ 47 | RequestOptions::MULTIPART => [ 48 | [ 49 | 'name' => 'data', 50 | 'filename' => 'image', 51 | 'contents' => static::dataTypeCast('image', $image), 52 | ], 53 | [ 54 | 'name' => 'use_case', 55 | 'contents' => $use_case, 56 | ] 57 | ] 58 | ]); 59 | } 60 | 61 | public function createProduct($data) 62 | { 63 | return $this->call('POST', 'products', [ 64 | RequestOptions::JSON => $data 65 | ]); 66 | } 67 | 68 | public function deleteProducts($product_ids = []) 69 | { 70 | return $this->call('DELETE', 'products', [ 71 | RequestOptions::JSON => [ 72 | 'product_ids' => $product_ids, 73 | ] 74 | ]); 75 | } 76 | 77 | public function editProduct($product_id, $data = []) 78 | { 79 | return $this->call('PUT', 'products/'.$product_id, [ 80 | RequestOptions::JSON => $data 81 | ]); 82 | } 83 | 84 | public function updateInventory($product_id, $params = []) 85 | { 86 | return $this->call('POST', 'products/'.$product_id.'/inventory/update', [ 87 | RequestOptions::JSON => $params 88 | ]); 89 | } 90 | 91 | public function getProduct($product_id, $params = []) 92 | { 93 | return $this->call('GET', 'products/'.$product_id, [ 94 | RequestOptions::QUERY => $params, 95 | ]); 96 | } 97 | 98 | public function deactivateProducts($product_ids = []) 99 | { 100 | return $this->call('POST', 'products/deactivate', [ 101 | RequestOptions::JSON => [ 102 | 'product_ids' => $product_ids, 103 | ] 104 | ]); 105 | } 106 | 107 | public function activateProducts($product_ids = []) 108 | { 109 | return $this->call('POST', 'products/activate', [ 110 | RequestOptions::JSON => [ 111 | 'product_ids' => $product_ids, 112 | ] 113 | ]); 114 | } 115 | 116 | public function recoverProducts($product_ids = []) 117 | { 118 | return $this->call('POST', 'products/recover', [ 119 | RequestOptions::JSON => [ 120 | 'product_ids' => $product_ids, 121 | ] 122 | ]); 123 | } 124 | 125 | public function updatePrice($product_id, $params) 126 | { 127 | return $this->call('POST', 'products/'.$product_id.'/prices/update', [ 128 | RequestOptions::JSON => $params 129 | ]); 130 | } 131 | 132 | public function getCategories($params = []) 133 | { 134 | return $this->call('GET', 'categories', [ 135 | RequestOptions::QUERY => $params 136 | ]); 137 | } 138 | 139 | public function getBrands($query = []) 140 | { 141 | $query = array_merge([ 142 | 'page_size' => 20, 143 | ], $query); 144 | 145 | return $this->call('GET', 'brands', [ 146 | RequestOptions::QUERY => $query 147 | ]); 148 | } 149 | 150 | public function createCustomBrand($name) 151 | { 152 | return $this->call('POST', 'brands', [ 153 | RequestOptions::JSON => [ 154 | 'name' => $name 155 | ] 156 | ]); 157 | } 158 | 159 | public function getAttributes($category_id, $params = []) 160 | { 161 | return $this->call('GET', 'categories/'.$category_id.'/attributes', [ 162 | RequestOptions::QUERY => $params 163 | ]); 164 | } 165 | 166 | public function getCategoryRules($category_id, $params = []) 167 | { 168 | return $this->call('GET', 'categories/'.$category_id.'/rules', [ 169 | RequestOptions::QUERY => $params 170 | ]); 171 | } 172 | 173 | public function recommendCategory($product_title, $description = '', $images = [], $category_version = 'v1') 174 | { 175 | return $this->call('POST', 'categories/recommend', [ 176 | RequestOptions::JSON => [ 177 | 'product_title' => $product_title, 178 | 'description' => $description, 179 | 'images' => $images, 180 | 'category_version' => $category_version, 181 | ] 182 | ]); 183 | } 184 | 185 | public function checkListingPrerequisites() 186 | { 187 | return $this->call('GET', 'prerequisites'); 188 | } 189 | 190 | public function partialEditProduct($product_id, $params = []) 191 | { 192 | return $this->call('POST', 'products/'.$product_id.'/partial_edit', [ 193 | RequestOptions::JSON => $params 194 | ]); 195 | } 196 | 197 | public function searchProducts($query = [], $body = null) 198 | { 199 | if ($body === null) { 200 | static::extractParams($query, $query, $body); 201 | } 202 | 203 | return $this->call('POST', 'products/search', [ 204 | RequestOptions::QUERY => $query, 205 | is_array($body) ? RequestOptions::JSON : RequestOptions::FORM_PARAMS => $body, 206 | ]); 207 | } 208 | 209 | public function inventorySearch($params = []) 210 | { 211 | return $this->call('POST', 'inventory/search', [ 212 | RequestOptions::JSON => $params 213 | ]); 214 | } 215 | 216 | public function searchResponsiblePersons($query = [], $body = null) 217 | { 218 | if ($body === null) { 219 | static::extractParams($query, $query, $body); 220 | } 221 | 222 | return $this->call('POST', 'compliance/responsible_persons/search', [ 223 | RequestOptions::QUERY => $query, 224 | is_array($body) ? RequestOptions::JSON : RequestOptions::FORM_PARAMS => $body, 225 | ]); 226 | } 227 | 228 | public function searchManufacturers($query = [], $body = null) 229 | { 230 | if ($body === null) { 231 | static::extractParams($query, $query, $body); 232 | } 233 | 234 | return $this->call('POST', 'compliance/manufacturers/search', [ 235 | RequestOptions::QUERY => $query, 236 | is_array($body) ? RequestOptions::JSON : RequestOptions::FORM_PARAMS => $body, 237 | ]); 238 | } 239 | 240 | public function createResponsiblePerson($body) 241 | { 242 | return $this->call('POST', 'compliance/responsible_persons', [ 243 | RequestOptions::JSON => $body 244 | ]); 245 | } 246 | 247 | public function createManufacturer($body) 248 | { 249 | return $this->call('POST', 'compliance/manufacturers', [ 250 | RequestOptions::JSON => $body 251 | ]); 252 | } 253 | 254 | public function partialEditManufacturer($manufacturer_id, $body = []) 255 | { 256 | return $this->call('POST', 'compliance/manufacturers/'.$manufacturer_id.'/partial_edit', [ 257 | RequestOptions::JSON => $body 258 | ]); 259 | } 260 | 261 | public function partialEditResponsiblePerson($responsible_person_id, $body = []) 262 | { 263 | return $this->call('POST', 'compliance/responsible_persons/'.$responsible_person_id.'/partial_edit', [ 264 | RequestOptions::JSON => $body 265 | ]); 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /src/Resources/Promotion.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Resources; 12 | 13 | use GuzzleHttp\RequestOptions; 14 | use EcomPHP\TiktokShop\Resource; 15 | 16 | class Promotion extends Resource 17 | { 18 | protected $category = 'promotion'; 19 | 20 | public function createActivity($title, $type, $begin_time, $end_time, $product_level) 21 | { 22 | return $this->call('POST', 'activities', [ 23 | RequestOptions::JSON => [ 24 | 'title' => $title, 25 | 'activity_type' => $type, 26 | 'begin_time' => $begin_time, 27 | 'end_time' => $end_time, 28 | 'product_level' => $product_level, 29 | ] 30 | ]); 31 | } 32 | 33 | public function updateActivityProduct($activity_id, $products) 34 | { 35 | return $this->call('PUT', 'activities/'.$activity_id.'/products', [ 36 | RequestOptions::JSON => [ 37 | 'products' => $products, 38 | 'activity_id' => $activity_id, 39 | ] 40 | ]); 41 | } 42 | 43 | public function removeActivityProduct($activity_id, $product_ids = [], $sku_ids = []) 44 | { 45 | return $this->call('DELETE', 'activities/'.$activity_id.'/products', [ 46 | RequestOptions::JSON => [ 47 | 'product_ids' => $product_ids, 48 | 'sku_ids' => $sku_ids, 49 | ] 50 | ]); 51 | } 52 | 53 | public function searchActivities($params = []) 54 | { 55 | return $this->call('POST', 'activities/search', [ 56 | RequestOptions::JSON => $params, 57 | ]); 58 | } 59 | 60 | public function getActivity($activity_id) 61 | { 62 | return $this->call('GET', 'activities/'.$activity_id); 63 | } 64 | 65 | public function updateActivity($activity_id, $title, $begin_time, $end_time) 66 | { 67 | return $this->call('PUT', 'activities/'.$activity_id, [ 68 | RequestOptions::JSON => [ 69 | 'title' => $title, 70 | 'begin_time' => $begin_time, 71 | 'end_time' => $end_time, 72 | ] 73 | ]); 74 | } 75 | 76 | public function deactivateActivity($activity_id) 77 | { 78 | return $this->call('POST', 'activities/'.$activity_id.'/deactivate'); 79 | } 80 | 81 | public function searchCoupons($query = [], $body = []) 82 | { 83 | return $this->call('POST', 'coupons/search', [ 84 | RequestOptions::QUERY => $query, 85 | RequestOptions::JSON => $body, 86 | ]); 87 | } 88 | 89 | public function getCoupon($coupon_id) 90 | { 91 | return $this->call('GET', 'coupons/'.$coupon_id); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Resources/ReturnRefund.php: -------------------------------------------------------------------------------- 1 | All rights reserved. 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Resources; 12 | 13 | use GuzzleHttp\RequestOptions; 14 | use EcomPHP\TiktokShop\Resource; 15 | 16 | class ReturnRefund extends Resource 17 | { 18 | protected $category = 'return_refund'; 19 | 20 | public function searchCancellations($query = [], $body = null) 21 | { 22 | if ($body === null) { 23 | static::extractParams($query, $query, $body); 24 | } 25 | return $this->call('POST', 'cancellations/search', [ 26 | RequestOptions::QUERY => $query, 27 | RequestOptions::JSON => $body, 28 | ]); 29 | } 30 | 31 | public function approveCancellation($cancel_id) 32 | { 33 | return $this->call('POST', 'cancellations/'.$cancel_id.'/approve', [ 34 | RequestOptions::QUERY => [ 35 | 'idempotency_key' => $this->generateIdempotencyKey(), 36 | ], 37 | ]); 38 | } 39 | 40 | public function rejectCancellation($cancel_id, $params) 41 | { 42 | return $this->call('POST', 'cancellations/'.$cancel_id.'/reject', [ 43 | RequestOptions::QUERY => [ 44 | 'idempotency_key' => $this->generateIdempotencyKey(), 45 | ], 46 | RequestOptions::JSON => $params, 47 | ]); 48 | } 49 | 50 | public function searchReturns($query = [], $body = null) 51 | { 52 | if ($body === null) { 53 | static::extractParams($query, $query, $body); 54 | } 55 | return $this->call('POST', 'returns/search', [ 56 | RequestOptions::QUERY => $query, 57 | RequestOptions::JSON => $body, 58 | ]); 59 | } 60 | 61 | public function approveReturn($return_id, $params) 62 | { 63 | return $this->call('POST', 'returns/'.$return_id.'/approve', [ 64 | RequestOptions::QUERY => [ 65 | 'idempotency_key' => $this->generateIdempotencyKey(), 66 | ], 67 | RequestOptions::JSON => $params, 68 | ]); 69 | } 70 | 71 | public function rejectReturn($return_id, $params) 72 | { 73 | return $this->call('POST', 'returns/'.$return_id.'/reject', [ 74 | RequestOptions::QUERY => [ 75 | 'idempotency_key' => $this->generateIdempotencyKey(), 76 | ], 77 | RequestOptions::JSON => $params, 78 | ]); 79 | } 80 | 81 | public function getAftersaleEligibility($order_id, $query = []) 82 | { 83 | return $this->call('GET', 'orders/'.$order_id.'/aftersale_eligibility', [ 84 | RequestOptions::QUERY => $query 85 | ]); 86 | } 87 | 88 | public function getRejectReasons($query = []) 89 | { 90 | return $this->call('GET', 'reject_reasons', [ 91 | RequestOptions::QUERY => $query 92 | ]); 93 | } 94 | 95 | public function calculateRefund($params) 96 | { 97 | return $this->call('POST', 'refunds/calculate', [ 98 | RequestOptions::JSON => $params, 99 | ]); 100 | } 101 | 102 | public function getReturnRecords($return_id) 103 | { 104 | return $this->call('GET', 'returns/'.$return_id.'/records'); 105 | } 106 | 107 | public function cancelOrder($params) 108 | { 109 | return $this->call('POST', 'cancellations', [ 110 | RequestOptions::JSON => $params, 111 | ]); 112 | } 113 | 114 | public function createReturn($params) 115 | { 116 | return $this->call('POST', 'returns', [ 117 | RequestOptions::QUERY => [ 118 | 'idempotency_key' => $this->generateIdempotencyKey(), 119 | ], 120 | RequestOptions::JSON => $params, 121 | ]); 122 | } 123 | 124 | /** 125 | * The idempotency key is a unique value generated by the client which the server uses to recognize the same request. 126 | * How you create unique keys is up to you, but we suggest using UUIDs, or another random string with enough entropy to avoid collisions. 127 | * Idempotency keys can be up to 255 characters long. 128 | */ 129 | private function generateIdempotencyKey() 130 | { 131 | return uniqid('', true); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/Resources/Seller.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Resources; 12 | 13 | use EcomPHP\TiktokShop\Resource; 14 | 15 | class Seller extends Resource 16 | { 17 | protected $category = 'seller'; 18 | 19 | public function getActiveShopList() 20 | { 21 | return $this->call('GET', 'shops'); 22 | } 23 | 24 | public function getSellerPermissions() 25 | { 26 | return $this->call('GET', 'permissions'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Resources/Supplychain.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Resources; 12 | 13 | use GuzzleHttp\RequestOptions; 14 | use EcomPHP\TiktokShop\Resource; 15 | 16 | class Supplychain extends Resource 17 | { 18 | protected $category = 'supply_chain'; 19 | 20 | public function confirmPackageShipment($warehouse_provider_id, $package) 21 | { 22 | return $this->call('POST', 'packages/sync', [ 23 | RequestOptions::JSON => [ 24 | 'warehouse_provider_id' => $warehouse_provider_id, 25 | 'package' => $package, 26 | ] 27 | ]); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Webhook.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop; 12 | 13 | use EcomPHP\TiktokShop\Errors\TiktokShopException; 14 | 15 | class Webhook 16 | { 17 | public const ORDER_STATUS_UPDATE = 1; 18 | public const REVERSE_ORDER_STATUS_UPDATE = 2; 19 | public const RECIPIENT_ADDRESS_UPDATE = 3; 20 | public const PACKAGE_UPDATE = 4; 21 | public const PRODUCT_STATUS_UPDATE = 5; 22 | public const RETURN_STATUS_UPDATE = 12; 23 | public const SELLER_DEAUTHORIZATION = 6; 24 | public const UPCOMING_AUTHORIZATION_EXPIRATION = 7; 25 | 26 | /** 27 | * @var Client 28 | */ 29 | protected $client; 30 | 31 | protected $type; 32 | protected $shop_id; 33 | protected $data; 34 | protected $timestamp; 35 | 36 | public function __construct(Client $client) 37 | { 38 | $this->client = $client; 39 | } 40 | 41 | public function getType() 42 | { 43 | return $this->type; 44 | } 45 | 46 | public function getShopId() 47 | { 48 | return $this->shop_id; 49 | } 50 | 51 | public function getData() 52 | { 53 | return $this->data; 54 | } 55 | 56 | public function getTimestamp() 57 | { 58 | return $this->timestamp; 59 | } 60 | 61 | public function capture($params = null) 62 | { 63 | if ($params === null) { 64 | $rawData = file_get_contents('php://input'); 65 | $params = json_decode($rawData, true); 66 | } 67 | 68 | if (!is_array($params)) { 69 | throw new TiktokShopException("Incoming webhook request data invalid."); 70 | } 71 | 72 | $this->type = $params['type']; 73 | $this->shop_id = $params['shop_id']; 74 | $this->data = $params['data']; 75 | $this->timestamp = $params['timestamp']; 76 | } 77 | 78 | public function verify($signature = null, $rawData = null, $throw = true) 79 | { 80 | $signature = $signature ?? $_SERVER['HTTP_AUTHORIZATION'] ?? null; 81 | if ($signature === null) { 82 | if ($throw) { 83 | throw new TiktokShopException("Incoming webhook request invalid. No authorization header."); 84 | } 85 | 86 | return false; 87 | } 88 | 89 | $rawData = $rawData ?? file_get_contents('php://input'); 90 | 91 | $stringToBeSigned = $this->client->getAppKey() . $rawData; 92 | $sign = hash_hmac('sha256', $stringToBeSigned, $this->client->getAppSecret()); 93 | 94 | $verified = hash_equals($sign, $signature); 95 | 96 | if ($throw && !$verified) { 97 | throw new TiktokShopException("Incoming webhook request invalid. Signature not match."); 98 | } 99 | 100 | return $verified; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /tests/AuthTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Tests; 12 | 13 | use GuzzleHttp\Client; 14 | use GuzzleHttp\Handler\MockHandler; 15 | use GuzzleHttp\HandlerStack; 16 | use GuzzleHttp\Psr7\Response; 17 | use EcomPHP\TiktokShop\Auth; 18 | use EcomPHP\TiktokShop\Client as TiktokApiClient; 19 | use EcomPHP\TiktokShop\Errors\AuthorizationException; 20 | use PHPUnit\Framework\TestCase; 21 | use ReflectionClass; 22 | 23 | class AuthTest extends TestCase 24 | { 25 | /** 26 | * @var \EcomPHP\TiktokShop\Client 27 | */ 28 | protected $client; 29 | 30 | protected function setUp(): void 31 | { 32 | $this->client = new TiktokApiClient('app_key', 'app_secret'); 33 | } 34 | 35 | public function testRefreshNewToken() 36 | { 37 | $mockHandler = new MockHandler([ 38 | new Response(200, [], '{"code":0,"message":"success","data":[],"request_id":"sample request id"}'), 39 | new Response(200, [], '{"code":100,"message":"fail","data":[],"request_id":"sample request id"}'), 40 | ]); 41 | 42 | $httpClient = new Client([ 43 | 'handler' => HandlerStack::create($mockHandler) 44 | ]); 45 | 46 | $auth = $this->client->auth(); 47 | 48 | $reflect = new ReflectionClass($auth); 49 | $httpClientProperty = $reflect->getProperty('httpClient'); 50 | $httpClientProperty->setAccessible(true); 51 | $httpClientProperty->setValue($auth, $httpClient); 52 | 53 | $auth->refreshNewToken('refresh_token'); 54 | $request = $mockHandler->getLastRequest(); 55 | $this->assertEquals('GET', $request->getMethod()); 56 | $this->assertEquals('/api/v2/token/refresh', $request->getUri()->getPath()); 57 | 58 | $this->expectException(AuthorizationException::class); 59 | $auth->refreshNewToken('failed refresh token'); 60 | } 61 | 62 | public function testGetToken() 63 | { 64 | $mockHandler = new MockHandler([ 65 | new Response(200, [], '{"code":0,"message":"success","data":[],"request_id":"sample request id"}'), 66 | new Response(200, [], '{"code":100,"message":"fail","data":[],"request_id":"sample request id"}'), 67 | ]); 68 | 69 | $httpClient = new Client([ 70 | 'handler' => HandlerStack::create($mockHandler) 71 | ]); 72 | 73 | $auth = $this->client->auth(); 74 | 75 | $reflect = new ReflectionClass($auth); 76 | $httpClientProperty = $reflect->getProperty('httpClient'); 77 | $httpClientProperty->setAccessible(true); 78 | $httpClientProperty->setValue($auth, $httpClient); 79 | 80 | $auth->getToken('auth code'); 81 | $request = $mockHandler->getLastRequest(); 82 | $this->assertEquals('GET', $request->getMethod()); 83 | $this->assertEquals('/api/v2/token/get', $request->getUri()->getPath()); 84 | 85 | $this->expectException(AuthorizationException::class); 86 | $auth->getToken('failed auth code'); 87 | } 88 | 89 | public function testCreateAuthRequest() 90 | { 91 | $client = new TiktokApiClient('app_key', 'app_secret'); 92 | 93 | $auth = $client->auth(); 94 | 95 | $regex = '/\/oauth\/authorize\?app_key=app_key&state=state$/i'; 96 | 97 | // test returned url 98 | $authUrl = $auth->createAuthRequest('state', true); 99 | 100 | $this->assertEquals(true, !!preg_match($regex, $authUrl)); 101 | } 102 | 103 | public function test__construct() 104 | { 105 | $client = new TiktokApiClient('app_key', 'app_secret'); 106 | $auth = new Auth($client); 107 | 108 | $reflect = new ReflectionClass($auth); 109 | 110 | $clientProperty = $reflect->getProperty('client'); 111 | $clientProperty->setAccessible(true); 112 | 113 | $authHostProperty = $reflect->getProperty('authHost'); 114 | $authHostProperty->setAccessible(true); 115 | 116 | $httpClientProperty = $reflect->getProperty('httpClient'); 117 | $httpClientProperty->setAccessible(true); 118 | 119 | $this->assertEquals($client, $clientProperty->getValue($auth)); 120 | $this->assertEquals(true, !!preg_match('/https:\/\/auth.tiktok-shops.com/i', $authHostProperty->getValue($auth))); 121 | $this->assertInstanceOf(Client::class, $httpClientProperty->getValue($auth)); 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /tests/ClientTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Tests; 12 | 13 | use EcomPHP\TiktokShop\Resources\Authorization; 14 | use EcomPHP\TiktokShop\Resources\Seller; 15 | use GuzzleHttp\Psr7\Request; 16 | use EcomPHP\TiktokShop\Auth; 17 | use EcomPHP\TiktokShop\Client; 18 | use EcomPHP\TiktokShop\Errors\TiktokShopException; 19 | use EcomPHP\TiktokShop\Resource; 20 | use PHPUnit\Framework\TestCase; 21 | use Psr\Http\Message\RequestInterface; 22 | use ReflectionClass; 23 | 24 | class ClientTest extends TestCase 25 | { 26 | /** 27 | * @var Client 28 | */ 29 | protected $client; 30 | 31 | protected function setUp(): void 32 | { 33 | parent::setUp(); 34 | 35 | $this->client = new Client('app_key', 'app_secret'); 36 | } 37 | 38 | public function testSetAccessToken() 39 | { 40 | $clientReflection = new ReflectionClass($this->client); 41 | 42 | $accessTokenProperty = $clientReflection->getProperty('access_token'); 43 | $accessTokenProperty->setAccessible(true); 44 | 45 | $this->client->setAccessToken('new_access_token'); 46 | $this->assertEquals('new_access_token', $accessTokenProperty->getValue($this->client)); 47 | } 48 | 49 | public function testAuth() 50 | { 51 | $this->assertInstanceOf(Auth::class, $this->client->auth()); 52 | } 53 | 54 | public function test__get() 55 | { 56 | // test get some resource 57 | $resources = [ 58 | Authorization::class, 59 | Seller::class, 60 | ]; 61 | 62 | foreach ($resources as $resource) { 63 | $reflect = new ReflectionClass($resource); 64 | $className = $reflect->getShortName(); 65 | 66 | $this->assertInstanceOf($resource, $this->client->{$className}); 67 | } 68 | 69 | // test fail resource 70 | $this->expectException(TiktokShopException::class); 71 | $resource = $this->client->foo; 72 | $this->assertNotInstanceOf(Resource::class, $resource); 73 | } 74 | 75 | public function test__construct() 76 | { 77 | $this->assertEquals('app_key', $this->client->getAppKey()); 78 | $this->assertEquals('app_secret', $this->client->getAppSecret()); 79 | } 80 | 81 | public function testPrepareSignature() 82 | { 83 | // create proxy class with public prepareSignature function 84 | $publicClient = new class('app_key', 'app_secret') extends Client { 85 | public function prepareSignature($request, &$params) 86 | { 87 | parent::prepareSignature($request, $params); 88 | } 89 | }; 90 | 91 | $params = ['foo' => 'bar']; 92 | $publicClient->prepareSignature(new Request('GET', '/test-api'), $params); 93 | 94 | $this->assertArrayHasKey('sign', $params); 95 | $this->assertEquals('d3cb7fe11ecae942802ceeca67e7cf10120cc12f1517d45fbc1c8cfe5413c80f', $params['sign']); 96 | } 97 | 98 | public function testModifyRequestBeforeSend() 99 | { 100 | $request = new Request('GET', 'https://open-api.tiktokglobalshop.com/product/202309/products'); 101 | 102 | $this->client->setAccessToken('access_token'); 103 | $this->client->setShopCipher('shop_cipher'); 104 | 105 | $clientReflection = new ReflectionClass($this->client); 106 | $modifyRequestMethod = $clientReflection->getMethod('modifyRequestBeforeSend'); 107 | $modifyRequestMethod->setAccessible(true); 108 | 109 | $modifiedRequest = $modifyRequestMethod->invokeArgs($this->client, [$request]); 110 | $modifiedRequestUri = $modifiedRequest->getUri(); 111 | parse_str($modifiedRequestUri->getQuery(), $query); 112 | 113 | $this->assertArrayHasKey('app_key', $query); 114 | $this->assertArrayHasKey('timestamp', $query); 115 | $this->assertArrayHasKey('sign', $query); 116 | $this->assertArrayHasKey('shop_cipher', $query); 117 | 118 | $this->assertArrayHasKey('x-tts-access-token', $modifiedRequest->getHeaders()); 119 | $this->assertEquals('access_token', $modifiedRequest->getHeaderLine('x-tts-access-token')); 120 | 121 | // test for global product api 122 | $globalProductRequest = new Request('GET', 'https://open-api.tiktokglobalshop.com/product/202309/global_products/1231/inventory/update'); 123 | $modifiedGlobalProductRequest = $modifyRequestMethod->invokeArgs($this->client, [$globalProductRequest]); 124 | $modifiedGlobalProductRequestUri = $modifiedGlobalProductRequest->getUri(); 125 | parse_str($modifiedGlobalProductRequestUri->getQuery(), $globalProductQuery); 126 | 127 | $this->assertArrayNotHasKey('shop_cipher', $globalProductQuery); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /tests/ResourceTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Tests; 12 | 13 | use EcomPHP\TiktokShop\Errors\TiktokShopException; 14 | use GuzzleHttp\Client; 15 | use GuzzleHttp\Handler\MockHandler; 16 | use GuzzleHttp\HandlerStack; 17 | use GuzzleHttp\Middleware; 18 | use GuzzleHttp\Psr7\Response; 19 | use EcomPHP\TiktokShop\Errors\ResponseException; 20 | use EcomPHP\TiktokShop\Resource; 21 | use PHPUnit\Framework\TestCase; 22 | 23 | class ResourceTest extends TestCase 24 | { 25 | protected $resource; 26 | 27 | protected function setUp(): void 28 | { 29 | parent::setUp(); 30 | 31 | $this->resource = $this->getMockForAbstractClass(Resource::class); 32 | } 33 | 34 | public function testCall() 35 | { 36 | $client = new Client([ 37 | 'handler' => HandlerStack::create(new MockHandler([ 38 | new Response(200, [], '{"code": 100000, "message": "error message", "request_id": "request id"}'), 39 | ])) 40 | ]); 41 | 42 | $this->resource->useHttpClient($client); 43 | 44 | $this->expectException(ResponseException::class); 45 | $this->resource->call('GET', 'http://fake_request.com'); 46 | } 47 | 48 | public function testLastMessageAndRequestId() 49 | { 50 | $client = new Client([ 51 | 'handler' => HandlerStack::create(new MockHandler([ 52 | new Response(200, [], '{"code": 0, "message": "error message", "request_id": "request id"}'), 53 | ])) 54 | ]); 55 | 56 | $this->resource->useHttpClient($client); 57 | $this->resource->call('GET', 'http://fake_request.com'); 58 | 59 | $this->assertEquals($this->resource->getLastMessage(), 'error message'); 60 | $this->assertEquals($this->resource->getLastRequestId(), 'request id'); 61 | } 62 | 63 | public function testChangeAPIVersion() 64 | { 65 | $container = []; 66 | 67 | $mockHandler = new MockHandler(); 68 | $mockHandler->append(new Response(200, [], '{"code":0,"message":"success","data":[],"request_id":"sample request id"}')); 69 | 70 | $handler = HandlerStack::create($mockHandler); 71 | $handler->push(Middleware::history($container)); 72 | 73 | $this->resource->useHttpClient(new Client([ 74 | 'handler' => $handler 75 | ])); 76 | 77 | // test newer version 78 | $this->resource->useVersion('202409')->call('GET', 'test-api'); 79 | $request = array_pop($container)['request']; 80 | $this->assertEquals('202409/test-api', $request->getUri()->getPath()); 81 | 82 | // test older version 83 | $this->expectException(TiktokShopException::class); 84 | $this->expectExceptionMessage('API version '.\EcomPHP\TiktokShop\Client::DEFAULT_VERSION.' is the minimum requirement to access this resource'); 85 | $this->resource->useVersion('202109')->call('GET', 'test-api'); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /tests/Resources/AffiliateCreatorTest.php: -------------------------------------------------------------------------------- 1 | All rights reserved. 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Tests\Resources; 12 | 13 | use EcomPHP\TiktokShop\Tests\TestResource; 14 | 15 | /** 16 | * @property-read \EcomPHP\TiktokShop\Resources\AffiliateCreator $caller 17 | */ 18 | class AffiliateCreatorTest extends TestResource 19 | { 20 | public const TEST_API_VERSION = 202405; 21 | 22 | public function testAddShowcaseProducts() 23 | { 24 | $this->caller->addShowcaseProducts('add_type', [1, 2, 3], 'product_link'); 25 | $this->assertPreviousRequest('POST', 'affiliate_creator/'.self::TEST_API_VERSION.'/showcases/products/add'); 26 | } 27 | 28 | public function testGetShowcaseProducts() 29 | { 30 | $this->caller->getShowcaseProducts(); 31 | $this->assertPreviousRequest('GET', 'affiliate_creator/'.self::TEST_API_VERSION.'/showcases/products'); 32 | } 33 | 34 | public function testGetCreatorProfile() 35 | { 36 | $this->caller->getCreatorProfile(); 37 | $this->assertPreviousRequest('GET', 'affiliate_creator/'.self::TEST_API_VERSION.'/profiles'); 38 | } 39 | 40 | public function testSearchOpenCollaborationProduct() 41 | { 42 | $this->caller->searchOpenCollaborationProduct(); 43 | $this->assertPreviousRequest('POST', 'affiliate_creator/'.self::TEST_API_VERSION.'/open_collaborations/products/search'); 44 | } 45 | 46 | public function testSearchTargetCollaborations() 47 | { 48 | $this->caller->searchTargetCollaborations([]); 49 | $this->assertPreviousRequest('POST', 'affiliate_creator/'.self::TEST_API_VERSION.'/target_collaborations/search'); 50 | } 51 | 52 | public function testSearchAffiliateOrders() 53 | { 54 | $this->caller->searchAffiliateOrders(); 55 | $this->assertPreviousRequest('POST', 'affiliate_creator/'.self::TEST_API_VERSION.'/orders/search'); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/Resources/AffiliatePartnerTest.php: -------------------------------------------------------------------------------- 1 | caller->createAffiliatePartnerCampaign([], []); 17 | $this->assertPreviousRequest('POST', 'affiliate_partner/'.self::TEST_API_VERSION.'/campaigns'); 18 | } 19 | 20 | public function testGetAffiliatePartnerCampaignProductList() 21 | { 22 | $this->caller->getAffiliatePartnerCampaignProductList(1, []); 23 | $this->assertPreviousRequest('GET', 'affiliate_partner/'.self::TEST_API_VERSION.'/campaigns/1/products'); 24 | } 25 | 26 | public function testGetAffiliatePartnerCampaignDetail() 27 | { 28 | $this->caller->getAffiliatePartnerCampaignDetail(1, []); 29 | $this->assertPreviousRequest('GET', 'affiliate_partner/'.self::TEST_API_VERSION.'/campaigns/1'); 30 | } 31 | 32 | public function testReviewAffiliatePartnerCampaignProduct() 33 | { 34 | $this->caller->reviewAffiliatePartnerCampaignProduct(1, 2, [], []); 35 | $this->assertPreviousRequest('POST', 'affiliate_partner/'.self::TEST_API_VERSION.'/campaigns/1/products/2/review'); 36 | } 37 | 38 | public function testEditAffiliatePartnerCampaign() 39 | { 40 | $this->caller->editAffiliatePartnerCampaign(1, [], []); 41 | $this->assertPreviousRequest('POST', 'affiliate_partner/'.self::TEST_API_VERSION.'/campaigns/1/partial_edit'); 42 | } 43 | 44 | public function testGetAffiliatePartnerCampaignList() 45 | { 46 | $this->caller->getAffiliatePartnerCampaignList([]); 47 | $this->assertPreviousRequest('GET', 'affiliate_partner/'.self::TEST_API_VERSION.'/campaigns'); 48 | } 49 | 50 | public function testPublishAffiliatePartnerCampaign() 51 | { 52 | $this->caller->publishAffiliatePartnerCampaign(1, []); 53 | $this->assertPreviousRequest('POST', 'affiliate_partner/'.self::TEST_API_VERSION.'/campaigns/1/publish'); 54 | } 55 | 56 | public function testGenerateAffiliatePartnerCampaignProductLink() 57 | { 58 | $this->caller->generateAffiliatePartnerCampaignProductLink(1, 2, [], []); 59 | $this->assertPreviousRequest('POST', 'affiliate_partner/'.self::TEST_API_VERSION.'/campaigns/1/products/2/promotion_link/generate'); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/Resources/AffiliateSellerTest.php: -------------------------------------------------------------------------------- 1 | All rights reserved. 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Tests\Resources; 12 | 13 | use EcomPHP\TiktokShop\Errors\TiktokShopException; 14 | use EcomPHP\TiktokShop\Tests\TestResource; 15 | 16 | /** 17 | * @property-read \EcomPHP\TiktokShop\Resources\AffiliateSeller $caller 18 | */ 19 | class AffiliateSellerTest extends TestResource 20 | { 21 | public const TEST_API_VERSION = 202405; 22 | 23 | public function testEditOpenCollaborationSettings() 24 | { 25 | $this->caller->editOpenCollaborationSettings([]); 26 | $this->assertPreviousRequest('POST', 'affiliate_seller/'.self::TEST_API_VERSION.'/open_collaboration_settings'); 27 | } 28 | 29 | public function testSearchOpenCollaborationProduct() 30 | { 31 | $this->caller->searchOpenCollaborationProduct(); 32 | $this->assertPreviousRequest('POST', 'affiliate_seller/'.self::TEST_API_VERSION.'/open_collaborations/products/search'); 33 | } 34 | 35 | public function testSearchSellerAffiliateOrders() 36 | { 37 | $this->caller->searchSellerAffiliateOrders(); 38 | $this->assertPreviousRequest('POST', 'affiliate_seller/'.self::TEST_API_VERSION.'/orders/search'); 39 | } 40 | 41 | public function testCreateOpenCollaboration() 42 | { 43 | $this->caller->createOpenCollaboration(1, 1); 44 | $this->assertPreviousRequest('POST', 'affiliate_seller/'.self::TEST_API_VERSION.'/open_collaborations'); 45 | } 46 | 47 | public function testCreateTargetCollaboration() 48 | { 49 | $this->caller->createTargetCollaboration([]); 50 | $this->assertPreviousRequest('POST', 'affiliate_seller/'.self::TEST_API_VERSION.'/target_collaborations'); 51 | } 52 | 53 | public function testRemoveCreatorAffiliateFromCollaboration() 54 | { 55 | $this->caller->removeCreatorAffiliateFromCollaboration(1, 2, 3); 56 | $this->assertPreviousRequest('POST', 'affiliate_seller/'.self::TEST_API_VERSION.'/open_collaborations/1/remove_creator'); 57 | } 58 | 59 | public function testGetMarketplaceCreatorPerformance() 60 | { 61 | $this->caller->getMarketplaceCreatorPerformance(1); 62 | $this->assertPreviousRequest('GET', 'affiliate_seller/202406/marketplace_creators/1'); 63 | } 64 | 65 | public function testSearchCreatorOnMarketplace() 66 | { 67 | $this->caller->searchCreatorOnMarketplace(); 68 | $this->assertPreviousRequest('POST', 'affiliate_seller/202406/marketplace_creators/search'); 69 | } 70 | 71 | public function testGenerateAffiliateProductPromotionLink() 72 | { 73 | $this->caller->generateAffiliateProductPromotionLink(1); 74 | $this->assertPreviousRequest('POST', 'affiliate_seller/'.self::TEST_API_VERSION.'/products/1/promotion_link/generate'); 75 | } 76 | 77 | public function testSearchSampleApplicationsFulfillments() 78 | { 79 | $this->caller->searchSampleApplicationsFulfillments(1); 80 | $this->assertPreviousRequest('POST', 'affiliate_seller/202409/sample_applications/1/fulfillments/search'); 81 | } 82 | 83 | public function testReviewSampleApplications() 84 | { 85 | $this->caller->reviewSampleApplications(1, '', ''); 86 | $this->assertPreviousRequest('POST', 'affiliate_seller/202409/sample_applications/1/review'); 87 | } 88 | 89 | public function testGetOpenCollaborationSampleRules() 90 | { 91 | $this->caller->getOpenCollaborationSampleRules([]); 92 | $this->assertPreviousRequest('GET', 'affiliate_seller/202410/open_collaborations/sample_rules'); 93 | } 94 | 95 | public function testSearchSampleApplications() 96 | { 97 | $this->caller->searchSampleApplications(); 98 | $this->assertPreviousRequest('POST', 'affiliate_seller/202409/sample_applications/search'); 99 | } 100 | 101 | public function testEditOpenCollaborationSampleRule() 102 | { 103 | $this->caller->editOpenCollaborationSampleRule([]); 104 | $this->assertPreviousRequest('POST', 'affiliate_seller/202410/open_collaborations/sample_rules'); 105 | } 106 | 107 | public function testRemoveTargetCollaboration() 108 | { 109 | $this->caller->removeTargetCollaboration(1); 110 | $this->assertPreviousRequest('DELETE', 'affiliate_seller/202409/target_collaborations/1'); 111 | } 112 | 113 | public function testQueryTargetCollaborationDetail() 114 | { 115 | $this->caller->queryTargetCollaborationDetail(1); 116 | $this->assertPreviousRequest('GET', 'affiliate_seller/202409/target_collaborations/1'); 117 | } 118 | 119 | public function testSearchTargetCollaborations() 120 | { 121 | $this->caller->searchTargetCollaborations(); 122 | $this->assertPreviousRequest('POST', 'affiliate_seller/202409/target_collaborations/search'); 123 | } 124 | 125 | public function testUpdateTargetCollaboration() 126 | { 127 | $this->caller->updateTargetCollaboration(1, []); 128 | $this->assertPreviousRequest('PUT', 'affiliate_seller/202409/target_collaborations/1'); 129 | } 130 | 131 | public function testSearchOpenCollaboration() 132 | { 133 | $this->caller->searchOpenCollaboration(); 134 | $this->assertPreviousRequest('POST', 'affiliate_seller/202409/open_collaborations/search'); 135 | } 136 | 137 | public function testGetOpenCollaborationSettings() 138 | { 139 | $this->caller->getOpenCollaborationSettings(); 140 | $this->assertPreviousRequest('GET', 'affiliate_seller/202409/open_collaboration_settings'); 141 | } 142 | 143 | public function testRemoveOpenCollaboration() 144 | { 145 | $this->caller->removeOpenCollaboration(1); 146 | $this->assertPreviousRequest('DELETE', 'affiliate_seller/202409/open_collaborations/products/1'); 147 | } 148 | 149 | public function testGetOpenCollaborationCreatorContentDetail() 150 | { 151 | $this->caller->getOpenCollaborationCreatorContentDetail(); 152 | $this->assertPreviousRequest('GET', 'affiliate_seller/202412/open_collaborations/creator_content_details'); 153 | } 154 | 155 | public function testGetMessageInConversation() 156 | { 157 | $this->caller->getMessageInConversation(1); 158 | $this->assertPreviousRequest('GET', 'affiliate_seller/202412/conversation/1/messages'); 159 | } 160 | 161 | public function testGetConversationList() 162 | { 163 | $this->caller->getConversationList(); 164 | $this->assertPreviousRequest('GET', 'affiliate_seller/202412/conversations'); 165 | } 166 | 167 | public function testSendImMessage() 168 | { 169 | $this->caller->sendImMessage(1, '', ''); 170 | $this->assertPreviousRequest('POST', 'affiliate_seller/202412/conversations/1/messages'); 171 | } 172 | 173 | public function testCreateConversationWithCreator() 174 | { 175 | $this->caller->createConversationWithCreator(1); 176 | $this->assertPreviousRequest('POST', 'affiliate_seller/202412/conversations'); 177 | } 178 | 179 | public function testMarkConversationRead() 180 | { 181 | $this->caller->markConversationRead(); 182 | $this->assertPreviousRequest('POST', 'affiliate_seller/202412/conversations/read'); 183 | } 184 | 185 | public function testGetLatestUnreadMessages() 186 | { 187 | $this->caller->getLatestUnreadMessages(); 188 | $this->assertPreviousRequest('GET', 'affiliate_seller/202412/conversations/messages/list/newest'); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /tests/Resources/AnalyticsTest.php: -------------------------------------------------------------------------------- 1 | All rights reserved. 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Tests\Resources; 12 | 13 | use EcomPHP\TiktokShop\Resources\Analytics; 14 | use EcomPHP\TiktokShop\Tests\TestResource; 15 | 16 | /** 17 | * @property-read \EcomPHP\TiktokShop\Resources\Analytics $caller 18 | */ 19 | class AnalyticsTest extends TestResource 20 | { 21 | public const TEST_API_VERSION = 202405; 22 | 23 | public function testGetShopPerformance() 24 | { 25 | $this->caller->getShopPerformance(); 26 | $this->assertPreviousRequest('GET', 'analytics/'.self::TEST_API_VERSION.'/shop/performance'); 27 | } 28 | 29 | public function testGetShopProductPerformance() 30 | { 31 | $this->caller->getShopProductPerformance(123); 32 | $this->assertPreviousRequest('GET', 'analytics/'.self::TEST_API_VERSION.'/shop_products/123/performance'); 33 | } 34 | 35 | public function testGetShopProductPerformanceList() 36 | { 37 | $this->caller->getShopProductPerformanceList(); 38 | $this->assertPreviousRequest('GET', 'analytics/'.self::TEST_API_VERSION.'/shop_products/performance'); 39 | } 40 | 41 | public function testGetShopSkuPerformance() 42 | { 43 | $this->caller->getShopSkuPerformance(123); 44 | $this->assertPreviousRequest('GET', 'analytics/'.self::TEST_API_VERSION.'/shop_skus/123/performance'); 45 | } 46 | 47 | public function testGetShopSkuPerformanceList() 48 | { 49 | $this->caller->getShopSkuPerformanceList(); 50 | $this->assertPreviousRequest('GET', 'analytics/'.self::TEST_API_VERSION.'/shop_skus/performance'); 51 | } 52 | 53 | public function testGetShopVideoPerformance() 54 | { 55 | $this->caller->getShopVideoPerformance(123); 56 | $this->assertPreviousRequest('GET', 'analytics/'.self::TEST_API_VERSION.'/shop_videos/123/performance'); 57 | 58 | } 59 | 60 | public function testGetShopVideoPerformanceList() 61 | { 62 | $this->caller->getShopVideoPerformanceList(); 63 | $this->assertPreviousRequest('GET', 'analytics/'.self::TEST_API_VERSION.'/shop_videos/performance'); 64 | } 65 | 66 | public function testGetShopVideoPerformanceOverview() 67 | { 68 | $this->caller->getShopVideoPerformanceOverview(); 69 | $this->assertPreviousRequest('GET', 'analytics/'.self::TEST_API_VERSION.'/shop_videos/overview_performance'); 70 | } 71 | 72 | public function testGetShopVideoProductPerformanceList() 73 | { 74 | $this->caller->getShopVideoProductPerformanceList(123); 75 | $this->assertPreviousRequest('GET', 'analytics/'.self::TEST_API_VERSION.'/shop_videos/123/products/performance'); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tests/Resources/AuthorizationTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Tests\Resources; 12 | 13 | use EcomPHP\TiktokShop\Tests\TestResource; 14 | 15 | /** 16 | * @property-read \EcomPHP\TiktokShop\Resources\Authorization $caller 17 | */ 18 | class AuthorizationTest extends TestResource 19 | { 20 | 21 | public function testGetAuthorizedShop() 22 | { 23 | $this->caller->getAuthorizedShop(); 24 | $this->assertPreviousRequest('GET', 'authorization/'.TestResource::TEST_API_VERSION.'/shops'); 25 | } 26 | 27 | public function testGetAuthorizedCategoryAssets() 28 | { 29 | $this->caller->getAuthorizedCategoryAssets(); 30 | $this->assertPreviousRequest('GET', 'authorization/'.TestResource::TEST_API_VERSION.'/category_assets'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/Resources/CustomerServiceTest.php: -------------------------------------------------------------------------------- 1 | All rights reserved. 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Tests\Resources; 12 | 13 | use EcomPHP\TiktokShop\Resources\CustomerService; 14 | use EcomPHP\TiktokShop\Tests\TestResource; 15 | 16 | class CustomerServiceTest extends TestResource 17 | { 18 | public function testGetConversationMessages() 19 | { 20 | $this->caller->getConversationMessages('conversation_id'); 21 | $this->assertPreviousRequest('GET', 'customer_service/'.TestResource::TEST_API_VERSION.'/conversations/conversation_id/messages'); 22 | } 23 | 24 | public function testGetConversations() 25 | { 26 | $this->caller->getConversations(); 27 | $this->assertPreviousRequest('GET', 'customer_service/'.TestResource::TEST_API_VERSION.'/conversations'); 28 | } 29 | 30 | public function testSendMessage() 31 | { 32 | $this->caller->sendMessage('conversation_id', 'type', 'content'); 33 | $this->assertPreviousRequest('POST', 'customer_service/'.TestResource::TEST_API_VERSION.'/conversations/conversation_id/messages'); 34 | } 35 | 36 | public function testGetAgentSettings() 37 | { 38 | $this->caller->getAgentSettings(); 39 | $this->assertPreviousRequest('GET', 'customer_service/'.TestResource::TEST_API_VERSION.'/agents/settings'); 40 | } 41 | 42 | public function testUpdateAgentSettings() 43 | { 44 | $this->caller->updateAgentSettings(); 45 | $this->assertPreviousRequest('PUT', 'customer_service/'.TestResource::TEST_API_VERSION.'/agents/settings'); 46 | } 47 | 48 | public function testUploadBuyerMessagesImage() 49 | { 50 | $this->caller->uploadBuyerMessagesImage('php://memory'); 51 | $this->assertPreviousRequest('POST', 'customer_service/'.TestResource::TEST_API_VERSION.'/images/upload'); 52 | } 53 | 54 | public function testReadMessage() 55 | { 56 | $this->caller->readMessage('conversation_id'); 57 | $this->assertPreviousRequest('POST', 'customer_service/'.TestResource::TEST_API_VERSION.'/conversations/conversation_id/messages/read'); 58 | } 59 | 60 | public function testCreateConversation() 61 | { 62 | $this->caller->createConversation('buyer_id'); 63 | $this->assertPreviousRequest('POST', 'customer_service/'.TestResource::TEST_API_VERSION.'/conversations'); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/Resources/EventTest.php: -------------------------------------------------------------------------------- 1 | All rights reserved. 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Tests\Resources; 12 | 13 | use EcomPHP\TiktokShop\Resources\Event; 14 | use EcomPHP\TiktokShop\Tests\TestResource; 15 | use PHPUnit\Framework\TestCase; 16 | 17 | /** 18 | * @property-read \EcomPHP\TiktokShop\Resources\Event $caller 19 | */ 20 | class EventTest extends TestResource 21 | { 22 | 23 | public function testUpdateShopWebhook() 24 | { 25 | $this->caller->updateShopWebhook('ORDER_STATUS_CHANGE', 'https://example.com'); 26 | $this->assertPreviousRequest('PUT', 'event/'.TestResource::TEST_API_VERSION.'/webhooks'); 27 | } 28 | 29 | public function testGetShopWebhooks() 30 | { 31 | $this->caller->getShopWebhooks(); 32 | $this->assertPreviousRequest('GET', 'event/'.TestResource::TEST_API_VERSION.'/webhooks'); 33 | } 34 | 35 | public function testDeleteShopWebhook() 36 | { 37 | $this->caller->deleteShopWebhook('order_fulfillment'); 38 | $this->assertPreviousRequest('DELETE', 'event/'.TestResource::TEST_API_VERSION.'/webhooks'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/Resources/FinanceTest.php: -------------------------------------------------------------------------------- 1 | All rights reserved. 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Tests\Resources; 12 | 13 | use EcomPHP\TiktokShop\Tests\TestResource; 14 | 15 | /** 16 | * @property-read \EcomPHP\TiktokShop\Resources\Finance $caller 17 | */ 18 | class FinanceTest extends TestResource 19 | { 20 | 21 | public function testGetPayments() 22 | { 23 | $this->caller->getPayments(); 24 | $this->assertPreviousRequest('GET', 'finance/'.TestResource::TEST_API_VERSION.'/payments'); 25 | } 26 | 27 | public function testGetOrderStatementTransactions() 28 | { 29 | $order_id = 200001; 30 | $this->caller->getOrderStatementTransactions($order_id); 31 | $this->assertPreviousRequest('GET', 'finance/'.TestResource::TEST_API_VERSION.'/orders/'.$order_id.'/statement_transactions'); 32 | } 33 | 34 | public function testGetStatementTransactions() 35 | { 36 | $statement_id = 200001; 37 | $this->caller->getStatementTransactions($statement_id); 38 | $this->assertPreviousRequest('GET', 'finance/'.TestResource::TEST_API_VERSION.'/statements/'.$statement_id.'/statement_transactions'); 39 | } 40 | 41 | public function testGetStatements() 42 | { 43 | $this->caller->getStatements(); 44 | $this->assertPreviousRequest('GET', 'finance/'.TestResource::TEST_API_VERSION.'/statements'); 45 | } 46 | 47 | public function testGetWithdrawals() 48 | { 49 | $this->caller->getWithdrawals(); 50 | $this->assertPreviousRequest('GET', 'finance/'.TestResource::TEST_API_VERSION.'/withdrawals'); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/Resources/FulfilledByTiktokTest.php: -------------------------------------------------------------------------------- 1 | caller->getFbtMerchantOnboardedRegions(); 14 | $this->assertPreviousRequest('GET', 'fbt/202409/merchants/onboarded_regions'); 15 | } 16 | 17 | public function getFbtWarehouseList() 18 | { 19 | $this->caller->getFbtWarehouseList(); 20 | $this->assertPreviousRequest('GET', 'fbt/' . self::TEST_API_VERSION . '/warehouses'); 21 | } 22 | 23 | public function getInboundOrder() 24 | { 25 | $this->caller->getInboundOrder([]); 26 | $this->assertPreviousRequest('GET', 'fbt/202409/inbound_orders'); 27 | } 28 | 29 | public function testSearchFbtInventory() 30 | { 31 | $this->caller->searchFbtInventory(); 32 | $this->assertPreviousRequest('POST', 'fbt/' . self::TEST_API_VERSION . '/inventory/search'); 33 | } 34 | 35 | public function testSearchFbtInventoryRecord() 36 | { 37 | $this->caller->searchFbtInventoryRecord(); 38 | $this->assertPreviousRequest('POST', 'fbt/202410/inventory_records/search'); 39 | } 40 | 41 | public function testSearchGoodsInfo() 42 | { 43 | $this->caller->searchGoodsInfo(); 44 | $this->assertPreviousRequest('POST', 'fbt/202409/goods/search'); 45 | } 46 | } -------------------------------------------------------------------------------- /tests/Resources/FulfillmentTest.php: -------------------------------------------------------------------------------- 1 | All rights reserved. 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Tests\Resources; 12 | 13 | use EcomPHP\TiktokShop\Tests\TestResource; 14 | 15 | /** 16 | * @property-read \EcomPHP\TiktokShop\Resources\Fulfillment $caller 17 | */ 18 | class FulfillmentTest extends TestResource 19 | { 20 | 21 | public function testGetPackageShippingDocument() 22 | { 23 | $package_id = 200001; 24 | $this->caller->getPackageShippingDocument($package_id, 'SHIPPING_LABEL'); 25 | $this->assertPreviousRequest('GET', 'fulfillment/'.TestResource::TEST_API_VERSION.'/packages/'.$package_id.'/shipping_documents'); 26 | } 27 | 28 | public function testGetPackageHandoverTimeSlots() 29 | { 30 | $package_id = 200001; 31 | $this->caller->getPackageHandoverTimeSlots($package_id); 32 | $this->assertPreviousRequest('GET', 'fulfillment/'.TestResource::TEST_API_VERSION.'/packages/'.$package_id.'/handover_time_slots'); 33 | } 34 | 35 | public function testUpdatePackageDeliveryStatus() 36 | { 37 | $this->caller->updatePackageDeliveryStatus(); 38 | $this->assertPreviousRequest('POST', 'fulfillment/'.TestResource::TEST_API_VERSION.'/packages/deliver'); 39 | } 40 | 41 | public function testSearchCombinablePackages() 42 | { 43 | $this->caller->searchCombinablePackages(); 44 | $this->assertPreviousRequest('GET', 'fulfillment/'.TestResource::TEST_API_VERSION.'/combinable_packages/search'); 45 | } 46 | 47 | public function testSearchPackage() 48 | { 49 | $this->caller->searchPackage(); 50 | $this->assertPreviousRequest('POST', 'fulfillment/'.TestResource::TEST_API_VERSION.'/packages/search'); 51 | } 52 | 53 | public function testCombinePackage() 54 | { 55 | $this->caller->combinePackage([]); 56 | $this->assertPreviousRequest('POST', 'fulfillment/'.TestResource::TEST_API_VERSION.'/packages/combine'); 57 | } 58 | 59 | public function testFulfillmentUploadDeliveryFile() 60 | { 61 | $this->caller->fulfillmentUploadDeliveryFile('php://memory'); 62 | $this->assertPreviousRequest('POST', 'fulfillment/'.TestResource::TEST_API_VERSION.'/files/upload'); 63 | } 64 | 65 | public function testUpdateShippingInfo() 66 | { 67 | $order_id = 10002; 68 | $this->caller->updateShippingInfo($order_id, 'tracking number', 200001); 69 | $this->assertPreviousRequest('POST', 'fulfillment/'.TestResource::TEST_API_VERSION.'/orders/'.$order_id.'/shipping_info/update'); 70 | } 71 | 72 | public function testMarkPackageAsShipped() 73 | { 74 | $order_id = 10002; 75 | $this->caller->markPackageAsShipped($order_id, 'tracking number', 200001); 76 | $this->assertPreviousRequest('POST', 'fulfillment/'.TestResource::TEST_API_VERSION.'/orders/'.$order_id.'/packages'); 77 | } 78 | 79 | public function testGetTracking() 80 | { 81 | $order_id = 10002; 82 | $this->caller->getTracking($order_id); 83 | $this->assertPreviousRequest('GET', 'fulfillment/'.TestResource::TEST_API_VERSION.'/orders/'.$order_id.'/tracking'); 84 | } 85 | 86 | public function testGetOrderSplitAttributes() 87 | { 88 | $this->caller->getOrderSplitAttributes([]); 89 | $this->assertPreviousRequest('GET', 'fulfillment/'.TestResource::TEST_API_VERSION.'/orders/split_attributes'); 90 | } 91 | 92 | public function testUncombinePackages() 93 | { 94 | $package_id = 200001; 95 | $this->caller->uncombinePackages($package_id); 96 | $this->assertPreviousRequest('POST', 'fulfillment/'.TestResource::TEST_API_VERSION.'/packages/'.$package_id.'/uncombine'); 97 | } 98 | 99 | public function testGetPackageDetail() 100 | { 101 | $package_id = 200001; 102 | $this->caller->getPackageDetail($package_id); 103 | $this->assertPreviousRequest('GET', 'fulfillment/'.TestResource::TEST_API_VERSION.'/packages/'.$package_id); 104 | } 105 | 106 | public function testBatchShipPackages() 107 | { 108 | $this->caller->batchShipPackages([]); 109 | $this->assertPreviousRequest('POST', 'fulfillment/'.TestResource::TEST_API_VERSION.'/packages/ship'); 110 | } 111 | 112 | public function testSplitOrders() 113 | { 114 | $order_id = 10002; 115 | $this->caller->splitOrders($order_id, []); 116 | $this->assertPreviousRequest('POST', 'fulfillment/'.TestResource::TEST_API_VERSION.'/orders/'.$order_id.'/split'); 117 | } 118 | 119 | public function testCreatePackages() 120 | { 121 | $this->caller->createPackages(1000); 122 | $this->assertPreviousRequest('POST', 'fulfillment/'.TestResource::TEST_API_VERSION.'/packages'); 123 | } 124 | 125 | public function testShipPackage() 126 | { 127 | $package_id = 300001; 128 | $this->caller->shipPackage($package_id); 129 | $this->assertPreviousRequest('POST', 'fulfillment/'.TestResource::TEST_API_VERSION.'/packages/'.$package_id.'/ship'); 130 | } 131 | 132 | public function testUpdatePackageShippingInfo() 133 | { 134 | $package_id = 300001; 135 | $this->caller->updatePackageShippingInfo($package_id, 'tracking number', 200001); 136 | $this->assertPreviousRequest('POST', 'fulfillment/'.TestResource::TEST_API_VERSION.'/packages/'.$package_id.'/shipping_info/update'); 137 | } 138 | 139 | public function testFulfillmentUploadDeliveryImage() 140 | { 141 | $this->caller->fulfillmentUploadDeliveryImage('php://memory'); 142 | $this->assertPreviousRequest('POST', 'fulfillment/'.TestResource::TEST_API_VERSION.'/images/upload'); 143 | } 144 | 145 | public function testGetEligibleShippingService() 146 | { 147 | $order_id = 100005; 148 | $this->caller->getEligibleShippingService($order_id); 149 | $this->assertPreviousRequest('POST', 'fulfillment/'.TestResource::TEST_API_VERSION.'/orders/'.$order_id.'/shipping_services/query'); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /tests/Resources/GlobalProductTest.php: -------------------------------------------------------------------------------- 1 | All rights reserved. 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Tests\Resources; 12 | 13 | use EcomPHP\TiktokShop\Resources\GlobalProduct; 14 | use EcomPHP\TiktokShop\Tests\TestResource; 15 | use PHPUnit\Framework\TestCase; 16 | 17 | /** 18 | * @property-read \EcomPHP\TiktokShop\Resources\GlobalProduct $caller 19 | */ 20 | class GlobalProductTest extends TestResource 21 | { 22 | 23 | public function testGetGlobalCategories() 24 | { 25 | $this->caller->getGlobalCategories(); 26 | $this->assertPreviousRequest('GET', 'product/'.TestResource::TEST_API_VERSION.'/global_categories'); 27 | } 28 | 29 | public function testGetGlobalAttributes() 30 | { 31 | $this->caller->getGlobalAttributes(1009); 32 | $this->assertPreviousRequest('GET', 'product/'.TestResource::TEST_API_VERSION.'/categories/1009/global_attributes'); 33 | } 34 | 35 | public function testGetGlobalCategoryRules() 36 | { 37 | $this->caller->getGlobalCategoryRules(1009); 38 | $this->assertPreviousRequest('GET', 'product/'.TestResource::TEST_API_VERSION.'/categories/1009/global_rules'); 39 | } 40 | 41 | public function testUpdateGlobalInventory() 42 | { 43 | $this->caller->updateGlobalInventory(1009, []); 44 | $this->assertPreviousRequest('POST', 'product/'.TestResource::TEST_API_VERSION.'/global_products/1009/inventory/update'); 45 | } 46 | 47 | public function testEditGlobalProduct() 48 | { 49 | $this->caller->editGlobalProduct(1009, []); 50 | $this->assertPreviousRequest('PUT', 'product/'.TestResource::TEST_API_VERSION.'/global_products/1009'); 51 | } 52 | 53 | public function testSearchGlobalProducts() 54 | { 55 | $this->caller->searchGlobalProducts(); 56 | $this->assertPreviousRequest('POST', 'product/'.TestResource::TEST_API_VERSION.'/global_products/search'); 57 | } 58 | 59 | public function testGetGlobalProduct() 60 | { 61 | $this->caller->getGlobalProduct(1009); 62 | $this->assertPreviousRequest('GET', 'product/'.TestResource::TEST_API_VERSION.'/global_products/1009'); 63 | } 64 | 65 | public function testDeleteGlobalProducts() 66 | { 67 | $this->caller->deleteGlobalProducts([]); 68 | $this->assertPreviousRequest('DELETE', 'product/'.TestResource::TEST_API_VERSION.'/global_products'); 69 | } 70 | 71 | public function testCreateGlobalProduct() 72 | { 73 | $this->caller->createGlobalProduct([]); 74 | $this->assertPreviousRequest('POST', 'product/'.TestResource::TEST_API_VERSION.'/global_products'); 75 | } 76 | 77 | public function testRecommendGlobalCategories() 78 | { 79 | $this->caller->recommendGlobalCategories([]); 80 | $this->assertPreviousRequest('POST', 'product/'.TestResource::TEST_API_VERSION.'/global_categories/recommend'); 81 | } 82 | 83 | public function testPublishGlobalProduct() 84 | { 85 | $this->caller->publishGlobalProduct(1009, []); 86 | $this->assertPreviousRequest('POST', 'product/'.TestResource::TEST_API_VERSION.'/global_products/1009/publish'); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /tests/Resources/LogisticTest.php: -------------------------------------------------------------------------------- 1 | All rights reserved. 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Tests\Resources; 12 | 13 | use EcomPHP\TiktokShop\Tests\TestResource; 14 | 15 | /** 16 | * @property-read \EcomPHP\TiktokShop\Resources\Logistic $caller 17 | */ 18 | class LogisticTest extends TestResource 19 | { 20 | 21 | public function testGetGlobalSellerWarehouse() 22 | { 23 | $this->caller->getGlobalSellerWarehouse(); 24 | $this->assertPreviousRequest('GET', 'logistics/'.TestResource::TEST_API_VERSION.'/global_warehouses'); 25 | } 26 | 27 | public function testGetShippingProvider() 28 | { 29 | $delivery_option_id = 200002; 30 | $this->caller->getShippingProvider($delivery_option_id); 31 | $this->assertPreviousRequest('GET', 'logistics/'.TestResource::TEST_API_VERSION.'/delivery_options/'.$delivery_option_id.'/shipping_providers'); 32 | } 33 | 34 | public function testGetWarehouseDeliveryOptions() 35 | { 36 | $warehouse_id = 200001; 37 | $this->caller->getWarehouseDeliveryOptions($warehouse_id); 38 | $this->assertPreviousRequest('GET', 'logistics/'.TestResource::TEST_API_VERSION.'/warehouses/'.$warehouse_id.'/delivery_options'); 39 | } 40 | 41 | public function testGetWarehouseList() 42 | { 43 | $this->caller->getWarehouseList(); 44 | $this->assertPreviousRequest('GET', 'logistics/'.TestResource::TEST_API_VERSION.'/warehouses'); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/Resources/OrderTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Tests\Resources; 12 | 13 | use EcomPHP\TiktokShop\Tests\TestResource; 14 | 15 | /** 16 | * @property-read \EcomPHP\TiktokShop\Resources\Order $caller 17 | */ 18 | class OrderTest extends TestResource 19 | { 20 | public function testGetOrderDetail() 21 | { 22 | $this->caller->getOrderDetail('sample order id'); 23 | $this->assertPreviousRequest('get', 'order/'.TestResource::TEST_API_VERSION.'/orders'); 24 | } 25 | 26 | public function testGetOrderList() 27 | { 28 | $this->caller->getOrderList(); 29 | $this->assertPreviousRequest('post', 'order/'.TestResource::TEST_API_VERSION.'/orders/search'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Resources/ProductTest.php: -------------------------------------------------------------------------------- 1 | All rights reserved. 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Tests\Resources; 12 | 13 | use EcomPHP\TiktokShop\Resources\Product; 14 | use EcomPHP\TiktokShop\Tests\TestResource; 15 | use PHPUnit\Framework\TestCase; 16 | 17 | /** 18 | * @property-read \EcomPHP\TiktokShop\Resources\Product $caller 19 | */ 20 | class ProductTest extends TestResource 21 | { 22 | 23 | public function testDeactivateProducts() 24 | { 25 | $this->caller->deactivateProducts([]); 26 | $this->assertPreviousRequest('POST', 'product/'.TestResource::TEST_API_VERSION.'/products/deactivate'); 27 | } 28 | 29 | public function testGetBrands() 30 | { 31 | $this->caller->getBrands([]); 32 | $this->assertPreviousRequest('GET', 'product/'.TestResource::TEST_API_VERSION.'/brands'); 33 | } 34 | 35 | public function testCreateProduct() 36 | { 37 | $this->caller->createProduct([]); 38 | $this->assertPreviousRequest('POST', 'product/'.TestResource::TEST_API_VERSION.'/products'); 39 | } 40 | 41 | public function testSearchProducts() 42 | { 43 | $this->caller->searchProducts([]); 44 | $this->assertPreviousRequest('POST', 'product/'.TestResource::TEST_API_VERSION.'/products/search'); 45 | } 46 | 47 | public function testGetAttributes() 48 | { 49 | $this->caller->getAttributes(1009); 50 | $this->assertPreviousRequest('GET', 'product/'.TestResource::TEST_API_VERSION.'/categories/1009/attributes'); 51 | } 52 | 53 | public function testUploadProductImage() 54 | { 55 | $this->caller->uploadProductImage('php://memory'); 56 | $this->assertPreviousRequest('POST', 'product/'.TestResource::TEST_API_VERSION.'/images/upload'); 57 | } 58 | 59 | public function testGetProduct() 60 | { 61 | $this->caller->getProduct(1009); 62 | $this->assertPreviousRequest('GET', 'product/'.TestResource::TEST_API_VERSION.'/products/1009'); 63 | } 64 | 65 | public function testUploadProductFile() 66 | { 67 | $this->caller->uploadProductFile('php://memory'); 68 | $this->assertPreviousRequest('POST', 'product/'.TestResource::TEST_API_VERSION.'/files/upload'); 69 | } 70 | 71 | public function testUpdateInventory() 72 | { 73 | $this->caller->updateInventory(1009, []); 74 | $this->assertPreviousRequest('POST', 'product/'.TestResource::TEST_API_VERSION.'/products/1009/inventory/update'); 75 | } 76 | 77 | public function testInventorySearch() 78 | { 79 | $this->caller->inventorySearch([]); 80 | $this->assertPreviousRequest('POST', 'product/'.TestResource::TEST_API_VERSION.'/inventory/search'); 81 | } 82 | 83 | public function testDeleteProducts() 84 | { 85 | $this->caller->deleteProducts([]); 86 | $this->assertPreviousRequest('DELETE', 'product/'.TestResource::TEST_API_VERSION.'/products'); 87 | } 88 | 89 | public function testEditProduct() 90 | { 91 | $this->caller->editProduct(1009, []); 92 | $this->assertPreviousRequest('PUT', 'product/'.TestResource::TEST_API_VERSION.'/products/1009'); 93 | } 94 | 95 | public function testActivateProducts() 96 | { 97 | $this->caller->activateProducts([]); 98 | $this->assertPreviousRequest('POST', 'product/'.TestResource::TEST_API_VERSION.'/products/activate'); 99 | } 100 | 101 | public function testPartialEditProduct() 102 | { 103 | $this->caller->partialEditProduct(1009, []); 104 | $this->assertPreviousRequest('POST', 'product/'.TestResource::TEST_API_VERSION.'/products/1009/partial_edit'); 105 | } 106 | 107 | public function testRecommendCategory() 108 | { 109 | $this->caller->recommendCategory([]); 110 | $this->assertPreviousRequest('POST', 'product/'.TestResource::TEST_API_VERSION.'/categories/recommend'); 111 | } 112 | 113 | public function testCheckListingPrerequisites() 114 | { 115 | $this->caller->checkListingPrerequisites([]); 116 | $this->assertPreviousRequest('GET', 'product/'.TestResource::TEST_API_VERSION.'/prerequisites'); 117 | } 118 | 119 | public function testUpdatePrice() 120 | { 121 | $this->caller->updatePrice(1009, []); 122 | $this->assertPreviousRequest('POST', 'product/'.TestResource::TEST_API_VERSION.'/products/1009/prices/update'); 123 | } 124 | 125 | public function testGetCategoryRules() 126 | { 127 | $this->caller->getCategoryRules(1009); 128 | $this->assertPreviousRequest('GET', 'product/'.TestResource::TEST_API_VERSION.'/categories/1009/rules'); 129 | } 130 | 131 | public function testGetCategories() 132 | { 133 | $this->caller->getCategories([]); 134 | $this->assertPreviousRequest('GET', 'product/'.TestResource::TEST_API_VERSION.'/categories'); 135 | } 136 | 137 | public function testRecoverProducts() 138 | { 139 | $this->caller->recoverProducts([]); 140 | $this->assertPreviousRequest('POST', 'product/'.TestResource::TEST_API_VERSION.'/products/recover'); 141 | } 142 | 143 | public function testCreateCustomBrand() 144 | { 145 | $this->caller->createCustomBrand([]); 146 | $this->assertPreviousRequest('POST', 'product/'.TestResource::TEST_API_VERSION.'/brands'); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /tests/Resources/PromotionTest.php: -------------------------------------------------------------------------------- 1 | All rights reserved. 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Tests\Resources; 12 | 13 | use EcomPHP\TiktokShop\Tests\TestResource; 14 | 15 | /** 16 | * @property-read \EcomPHP\TiktokShop\Resources\Promotion $caller 17 | */ 18 | class PromotionTest extends TestResource 19 | { 20 | 21 | public function testSearchActivities() 22 | { 23 | $this->caller->searchActivities(); 24 | $this->assertPreviousRequest('POST', 'promotion/'.TestResource::TEST_API_VERSION.'/activities/search'); 25 | } 26 | 27 | public function testGetActivity() 28 | { 29 | $activity_id = 1009; 30 | $this->caller->getActivity($activity_id); 31 | $this->assertPreviousRequest('GET', 'promotion/'.TestResource::TEST_API_VERSION.'/activities/'.$activity_id); 32 | } 33 | 34 | public function testUpdateActivity() 35 | { 36 | $activity_id = 1009; 37 | $this->caller->updateActivity($activity_id, 'test title', 'begin time', 'end time'); 38 | $this->assertPreviousRequest('PUT', 'promotion/'.TestResource::TEST_API_VERSION.'/activities/'.$activity_id); 39 | } 40 | 41 | public function testDeactivateActivity() 42 | { 43 | $activity_id = 1009; 44 | $this->caller->deactivateActivity($activity_id); 45 | $this->assertPreviousRequest('POST', 'promotion/'.TestResource::TEST_API_VERSION.'/activities/'.$activity_id.'/deactivate'); 46 | } 47 | 48 | public function testUpdateActivityProduct() 49 | { 50 | $activity_id = 1009; 51 | $this->caller->updateActivityProduct($activity_id, []); 52 | $this->assertPreviousRequest('PUT', 'promotion/'.TestResource::TEST_API_VERSION.'/activities/'.$activity_id.'/products'); 53 | } 54 | 55 | public function testCreateActivity() 56 | { 57 | $this->caller->createActivity('test title', 'test type', 'begin time', 'end time', 'product level'); 58 | $this->assertPreviousRequest('POST', 'promotion/'.TestResource::TEST_API_VERSION.'/activities'); 59 | } 60 | 61 | public function testRemoveActivityProduct() 62 | { 63 | $activity_id = 1009; 64 | $this->caller->removeActivityProduct($activity_id); 65 | $this->assertPreviousRequest('DELETE', 'promotion/'.TestResource::TEST_API_VERSION.'/activities/'.$activity_id.'/products'); 66 | } 67 | 68 | public function testSearchCoupons() 69 | { 70 | $this->caller->searchCoupons(); 71 | $this->assertPreviousRequest('POST', 'promotion/'.TestResource::TEST_API_VERSION.'/coupons/search'); 72 | } 73 | 74 | public function testGetCoupon() 75 | { 76 | $coupon_id = 1009; 77 | $this->caller->getCoupon($coupon_id); 78 | $this->assertPreviousRequest('GET', 'promotion/'.TestResource::TEST_API_VERSION.'/coupons/'.$coupon_id); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /tests/Resources/ReturnRefundTest.php: -------------------------------------------------------------------------------- 1 | All rights reserved. 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Tests\Resources; 12 | 13 | use EcomPHP\TiktokShop\Tests\TestResource; 14 | 15 | /** 16 | * @property-read \EcomPHP\TiktokShop\Resources\ReturnRefund $caller 17 | */ 18 | class ReturnRefundTest extends TestResource 19 | { 20 | 21 | public function testRejectCancellation() 22 | { 23 | $this->caller->rejectCancellation(1009, []); 24 | $this->assertPreviousRequest('POST', 'return_refund/'.TestResource::TEST_API_VERSION.'/cancellations/1009/reject'); 25 | } 26 | 27 | public function testCreateReturn() 28 | { 29 | $this->caller->createReturn([]); 30 | $this->assertPreviousRequest('POST', 'return_refund/'.TestResource::TEST_API_VERSION.'/returns'); 31 | } 32 | 33 | public function testApproveCancellation() 34 | { 35 | $this->caller->approveCancellation(1009); 36 | $this->assertPreviousRequest('POST', 'return_refund/'.TestResource::TEST_API_VERSION.'/cancellations/1009/approve'); 37 | } 38 | 39 | public function testGetRejectReasons() 40 | { 41 | $this->caller->getRejectReasons(); 42 | $this->assertPreviousRequest('GET', 'return_refund/'.TestResource::TEST_API_VERSION.'/reject_reasons'); 43 | } 44 | 45 | public function testCancelOrder() 46 | { 47 | $this->caller->cancelOrder([]); 48 | $this->assertPreviousRequest('POST', 'return_refund/'.TestResource::TEST_API_VERSION.'/cancellations'); 49 | } 50 | 51 | public function testGetAftersaleEligibility() 52 | { 53 | $this->caller->getAftersaleEligibility(1009); 54 | $this->assertPreviousRequest('GET', 'return_refund/'.TestResource::TEST_API_VERSION.'/orders/1009/aftersale_eligibility'); 55 | } 56 | 57 | public function testSearchReturns() 58 | { 59 | $this->caller->searchReturns(); 60 | $this->assertPreviousRequest('POST', 'return_refund/'.TestResource::TEST_API_VERSION.'/returns/search'); 61 | } 62 | 63 | public function testApproveReturn() 64 | { 65 | $this->caller->approveReturn(1009, []); 66 | $this->assertPreviousRequest('POST', 'return_refund/'.TestResource::TEST_API_VERSION.'/returns/1009/approve'); 67 | } 68 | 69 | public function testRejectReturn() 70 | { 71 | $this->caller->rejectReturn(1009, []); 72 | $this->assertPreviousRequest('POST', 'return_refund/'.TestResource::TEST_API_VERSION.'/returns/1009/reject'); 73 | } 74 | 75 | public function testSearchCancellations() 76 | { 77 | $this->caller->searchCancellations(); 78 | $this->assertPreviousRequest('POST', 'return_refund/'.TestResource::TEST_API_VERSION.'/cancellations/search'); 79 | } 80 | 81 | public function testCalculateRefund() 82 | { 83 | $this->caller->calculateRefund([]); 84 | $this->assertPreviousRequest('POST', 'return_refund/'.TestResource::TEST_API_VERSION.'/refunds/calculate'); 85 | } 86 | 87 | public function testGetReturnRecords() 88 | { 89 | $this->caller->getReturnRecords(1009); 90 | $this->assertPreviousRequest('GET', 'return_refund/'.TestResource::TEST_API_VERSION.'/returns/1009/records'); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /tests/Resources/SellerTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Tests\Resources; 12 | 13 | use EcomPHP\TiktokShop\Tests\TestResource; 14 | 15 | /** 16 | * @property-read \EcomPHP\TiktokShop\Resources\Seller $caller 17 | */ 18 | class SellerTest extends TestResource 19 | { 20 | 21 | public function testGetActiveShopList() 22 | { 23 | $this->caller->getActiveShopList(); 24 | $this->assertPreviousRequest('GET', 'seller/'.TestResource::TEST_API_VERSION.'/shops'); 25 | } 26 | 27 | public function testGetSellerPermissions() 28 | { 29 | $this->caller->getSellerPermissions(); 30 | $this->assertPreviousRequest('GET', 'seller/'.TestResource::TEST_API_VERSION.'/permissions'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/Resources/SupplychainTest.php: -------------------------------------------------------------------------------- 1 | All rights reserved. 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Tests\Resources; 12 | 13 | use EcomPHP\TiktokShop\Tests\TestResource; 14 | 15 | /** 16 | * @property-read \EcomPHP\TiktokShop\Resources\Supplychain $caller 17 | */ 18 | class SupplychainTest extends TestResource 19 | { 20 | public function testConfirmPackageShipment() 21 | { 22 | $this->caller->confirmPackageShipment(1, []); 23 | $this->assertPreviousRequest('POST', 'supply_chain/'.TestResource::TEST_API_VERSION.'/packages/sync'); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/TestResource.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Tests; 12 | 13 | use GuzzleHttp\Client; 14 | use GuzzleHttp\Handler\MockHandler; 15 | use GuzzleHttp\HandlerStack; 16 | use GuzzleHttp\Middleware; 17 | use GuzzleHttp\Psr7\Response; 18 | use PHPUnit\Framework\TestCase; 19 | 20 | use EcomPHP\TiktokShop\Client as TiktokApiClient; 21 | 22 | abstract class TestResource extends TestCase 23 | { 24 | public const TEST_API_VERSION = '202309'; 25 | 26 | protected $caller; 27 | 28 | public static $container = []; 29 | 30 | protected function setUp(): void 31 | { 32 | parent::setUp(); 33 | 34 | $resourceName = preg_replace('/.+\\\Resources\\\\(\w+)Test$/', '$1', get_called_class()); 35 | 36 | $client = $this->tiktokShopClientForTest(); 37 | 38 | $response = new Response(200, [], '{"code":0,"message":"success","data":[],"request_id":"sample request id"}'); 39 | 40 | $mockHandler = new MockHandler(); 41 | $mockHandler->append($response); 42 | 43 | $handler = HandlerStack::create($mockHandler); 44 | $handler->push(Middleware::history(static::$container)); 45 | 46 | $httpClient = new Client([ 47 | 'handler' => $handler, 48 | ]); 49 | 50 | $this->caller = $client->{$resourceName}; 51 | $this->caller->useVersion(static::TEST_API_VERSION); 52 | $this->caller->useHttpClient($httpClient); 53 | } 54 | 55 | protected function tiktokShopClientForTest() 56 | { 57 | $client = new TiktokApiClient('app_key', 'app_secret'); 58 | $client->useVersion(static::TEST_API_VERSION); 59 | 60 | return $client; 61 | } 62 | 63 | protected function assertPreviousRequest($method, $uri) 64 | { 65 | $request = array_pop(static::$container)['request']; 66 | $this->assertEquals(strtolower($method), strtolower($request->getMethod())); 67 | $this->assertEquals($uri, trim($request->getUri()->getPath(), '/')); 68 | 69 | return $request; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tests/WebhookTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace EcomPHP\TiktokShop\Tests; 12 | 13 | use EcomPHP\TiktokShop\Client; 14 | use EcomPHP\TiktokShop\Errors\TiktokShopException; 15 | use EcomPHP\TiktokShop\Webhook; 16 | use PHPUnit\Framework\TestCase; 17 | use ReflectionClass; 18 | 19 | class WebhookTest extends TestCase 20 | { 21 | public static $webhook; 22 | public static $sample_data = '{"type":1,"shop_id":"7494726673780148744","timestamp":1664095733,"data":{"order_id":"576684019603311365","order_status":"AWAITING_SHIPMENT","update_time":1664095610}}'; 23 | 24 | public static function setUpBeforeClass(): void 25 | { 26 | parent::setUpBeforeClass(); 27 | 28 | $client = new Client('fake_app_key', 'fake_app_secret'); 29 | $webhook = new Webhook($client); 30 | 31 | // put sample data 32 | $webhook->capture(json_decode(static::$sample_data, true)); 33 | 34 | static::$webhook = $webhook; 35 | } 36 | 37 | public function testGetShopId() 38 | { 39 | $this->assertEquals('7494726673780148744', static::$webhook->getShopId()); 40 | } 41 | 42 | public function testVerify() 43 | { 44 | $stringToBeSigned = 'fake_app_key'.static::$sample_data; 45 | $signature = hash_hmac('sha256', $stringToBeSigned, 'fake_app_secret'); 46 | 47 | // correct signature 48 | $this->assertEquals(true, static::$webhook->verify($signature, static::$sample_data)); 49 | 50 | // in-correct signature without thrown exception 51 | $this->assertEquals(false, static::$webhook->verify('wrong signature', static::$sample_data, false)); 52 | 53 | // in-correct signature thrown exception 54 | $this->expectException(TiktokShopException::class); 55 | static::$webhook->verify('wrong signature', static::$sample_data); 56 | } 57 | 58 | public function testGetType() 59 | { 60 | $this->assertEquals(1, static::$webhook->getType()); 61 | } 62 | 63 | public function testGetTimestamp() 64 | { 65 | $this->assertEquals(1664095733, static::$webhook->getTimestamp()); 66 | } 67 | 68 | public function testGetData() 69 | { 70 | $sample_data = json_decode(static::$sample_data, true); 71 | $this->assertEquals($sample_data['data'], static::$webhook->getData()); 72 | } 73 | 74 | public function test__construct() 75 | { 76 | $client = new Client('fake_app_key', 'fake_app_secret'); 77 | $webhook = new Webhook($client); 78 | 79 | $reflect = new ReflectionClass($webhook); 80 | $clientProperty = $reflect->getProperty('client'); 81 | $clientProperty->setAccessible(true); 82 | 83 | $this->assertInstanceOf(Client::class, $clientProperty->getValue($webhook)); 84 | } 85 | 86 | public function testCapture() 87 | { 88 | $webhook = static::$webhook; 89 | $sample_data = json_decode(static::$sample_data, true); 90 | 91 | $webhook->capture($sample_data); 92 | $reflect = new ReflectionClass($webhook); 93 | 94 | $typeProperty = $reflect->getProperty('type'); 95 | $typeProperty->setAccessible(true); 96 | $this->assertEquals(1, $typeProperty->getValue($webhook)); 97 | 98 | $shopIdProperty = $reflect->getProperty('shop_id'); 99 | $shopIdProperty->setAccessible(true); 100 | $this->assertEquals('7494726673780148744', $shopIdProperty->getValue($webhook)); 101 | 102 | $dataProperty = $reflect->getProperty('data'); 103 | $dataProperty->setAccessible(true); 104 | $this->assertEquals($sample_data['data'], $dataProperty->getValue($webhook)); 105 | 106 | $timestampProperty = $reflect->getProperty('timestamp'); 107 | $timestampProperty->setAccessible(true); 108 | $this->assertEquals(1664095733, $timestampProperty->getValue($webhook)); 109 | } 110 | } 111 | --------------------------------------------------------------------------------