diff --git a/README.md b/README.md index 0252474..fac32b4 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ For more information, please visit [Cryptopay API docs](https://developers.crypt * [Invoices](#invoicesapi) * [Rates](#ratesapi) * [Risks](#risksapi) + * [Subscriptions](#subscriptionsapi) * [Transactions](#transactionsapi) * [Callbacks](#callbacks) @@ -452,6 +453,61 @@ $params = [ $result = $cryptopay->risks->score($params); ``` +## Subscriptions + + +### List subscriptions + + +```php +$result = $cryptopay->subscriptions->all(); +``` + +### Cancel a subscription + + +```php +$subscriptionId = '7dd7da55-2fd6-445e-8c7c-6c2c85d135d7'; + +$result = $cryptopay->subscriptions->cancel($subscriptionId); +``` + +### Create a subscription + + +```php +$startsAt = (new \DateTime())->add(\DateInterval::createFromDateString('7 days')); +$params = [ + 'name' => 'Subscription name', + 'amount' => '100.0', + 'currency' => 'EUR', + 'period' => 'month', + 'period_quantity' => 3, + 'payer_email' => 'user@example.com', + 'starts_at' => $startsAt->format(\DateTime::ATOM) +]; + +$result = $cryptopay->subscriptions->create($params); +``` + +### Retrieve a subscription + + +```php +$subscriptionId = '64249ede-8969-4d5c-a042-806f9c3e7db3'; + +$result = $cryptopay->subscriptions->retrieve($subscriptionId); +``` + +### Retrieve a subscription by custom_id + + +```php +$customId = 'PAYMENT-123'; + +$result = $cryptopay->subscriptions->retrieveByCustomId($customId); +``` + ## Transactions [Transactions API docs](https://developers.cryptopay.me/guides/transactions) diff --git a/src/Api/SubscriptionsApi.php b/src/Api/SubscriptionsApi.php new file mode 100644 index 0000000..d053f0e --- /dev/null +++ b/src/Api/SubscriptionsApi.php @@ -0,0 +1,88 @@ +request('GET', '/api/subscriptions', $params); + } + + /** + * Cancel a subscription + * + * @param string $subscriptionId + * @param null|array $params + * + * @throws \Cryptopay\Exceptions\RequestException + * @return object + */ + public function cancel(string $subscriptionId, array $params = null) + { + $path = '/api/subscriptions/{subscription_id}/cancel'; + $path = str_replace('{subscription_id}', rawurlencode($subscriptionId), $path); + + return $this->request('POST', $path, $params); + } + + /** + * Create a subscription + * + * @param null|array $params + * + * @throws \Cryptopay\Exceptions\RequestException + * @return object + */ + public function create(array $params = null) + { + return $this->request('POST', '/api/subscriptions', $params); + } + + /** + * Retrieve a subscription + * + * @param string $subscriptionId + * @param null|array $params + * + * @throws \Cryptopay\Exceptions\RequestException + * @return object + */ + public function retrieve(string $subscriptionId, array $params = null) + { + $path = '/api/subscriptions/{subscription_id}'; + $path = str_replace('{subscription_id}', rawurlencode($subscriptionId), $path); + + return $this->request('GET', $path, $params); + } + + /** + * Retrieve a subscription by custom_id + * + * @param string $customId + * @param null|array $params + * + * @throws \Cryptopay\Exceptions\RequestException + * @return object + */ + public function retrieveByCustomId(string $customId, array $params = null) + { + $path = '/api/subscriptions/custom_id/{custom_id}'; + $path = str_replace('{custom_id}', rawurlencode($customId), $path); + + return $this->request('GET', $path, $params); + } +} diff --git a/src/Cryptopay.php b/src/Cryptopay.php index ed26265..d181d51 100644 --- a/src/Cryptopay.php +++ b/src/Cryptopay.php @@ -28,6 +28,7 @@ use Cryptopay\Api\InvoicesApi; use Cryptopay\Api\RatesApi; use Cryptopay\Api\RisksApi; +use Cryptopay\Api\SubscriptionsApi; use Cryptopay\Api\TransactionsApi; class Cryptopay @@ -41,6 +42,7 @@ class Cryptopay public InvoicesApi $invoices; public RatesApi $rates; public RisksApi $risks; + public SubscriptionsApi $subscriptions; public TransactionsApi $transactions; // Deprecated services @@ -53,7 +55,7 @@ class Cryptopay private RateService $rateService; private RiskService $riskService; - private const VERSION = '2.0.0'; + private const VERSION = '2.1.0'; private const USER_AGENT = 'Cryptopay-PHP/' . Cryptopay::VERSION . ' PHP/' . \PHP_VERSION; private const USER_AGENT_DEPRECATED = Cryptopay::USER_AGENT . ' (deprecated)'; @@ -71,6 +73,7 @@ public function __construct(ConfigInterface $config) $this->invoices = new InvoicesApi($connector); $this->rates = new RatesApi($connector); $this->risks = new RisksApi($connector); + $this->subscriptions = new SubscriptionsApi($connector); $this->transactions = new TransactionsApi($connector); $this->callbackService = new CallbackService($config->getCallbackSecret()); diff --git a/tests/Api/SubscriptionsApiTest.php b/tests/Api/SubscriptionsApiTest.php new file mode 100644 index 0000000..54b7cb5 --- /dev/null +++ b/tests/Api/SubscriptionsApiTest.php @@ -0,0 +1,84 @@ +config); + + $result = $cryptopay->subscriptions->all(); + + $this->assertNotNull($result); + } + + public function testcancel() + { + VCR::insertCassette('subscriptions/cancel.yml'); + + $cryptopay = new Cryptopay($this->config); + + $subscriptionId = '7dd7da55-2fd6-445e-8c7c-6c2c85d135d7'; + + $result = $cryptopay->subscriptions->cancel($subscriptionId); + + $this->assertNotNull($result); + } + + public function testcreate() + { + VCR::insertCassette('subscriptions/create.yml'); + + $cryptopay = new Cryptopay($this->config); + + $startsAt = (new \DateTime())->add(\DateInterval::createFromDateString('7 days')); + $params = [ + 'name' => 'Subscription name', + 'amount' => '100.0', + 'currency' => 'EUR', + 'period' => 'month', + 'period_quantity' => 3, + 'payer_email' => 'user@example.com', + 'starts_at' => $startsAt->format(\DateTime::ATOM) + ]; + + $result = $cryptopay->subscriptions->create($params); + + $this->assertNotNull($result); + } + + public function testretrieve() + { + VCR::insertCassette('subscriptions/retrieve.yml'); + + $cryptopay = new Cryptopay($this->config); + + $subscriptionId = '64249ede-8969-4d5c-a042-806f9c3e7db3'; + + $result = $cryptopay->subscriptions->retrieve($subscriptionId); + + $this->assertNotNull($result); + } + + public function testretrieveByCustomId() + { + VCR::insertCassette('subscriptions/retrieveByCustomId.yml'); + + $cryptopay = new Cryptopay($this->config); + + $customId = 'PAYMENT-123'; + + $result = $cryptopay->subscriptions->retrieveByCustomId($customId); + + $this->assertNotNull($result); + } +} diff --git a/tests/ApiTest.php b/tests/ApiTest.php index 8da03b8..b26b51f 100644 --- a/tests/ApiTest.php +++ b/tests/ApiTest.php @@ -12,7 +12,7 @@ public function __construct(?string $name = null, array $data = [], $dataName = VCR::configure() ->setCassettePath('tests/cassettes') ->enableLibraryHooks(['curl']) - ->enableRequestMatchers(['method', 'url', 'query_string', 'body']) + ->enableRequestMatchers(['method', 'url', 'query_string']) ->setMode('once'); VCR::turnOn(); } diff --git a/tests/cassettes/subscriptions/all.yml b/tests/cassettes/subscriptions/all.yml new file mode 100644 index 0000000..e8df628 --- /dev/null +++ b/tests/cassettes/subscriptions/all.yml @@ -0,0 +1,73 @@ + +- + request: + method: GET + url: 'https://business-sandbox.cryptopay.me/api/subscriptions' + headers: + Host: business-sandbox.cryptopay.me + Accept-Encoding: '' + Content-Type: application/json + Date: 'Mon, 17 Jul 2023 09:23:49 +0000' + Authorization: 'HMAC OtzdZAvAkmw4vAYniZ4ljw:5nH69i72dsiGa7SgCFcrRnS3V7Y=' + User-Agent: 'Cryptopay-PHP/2.1.0 PHP/7.4.33' + Accept: '' + response: + status: + http_version: '1.1' + code: '200' + message: OK + headers: + Date: 'Mon, 17 Jul 2023 09:23:50 GMT' + Content-Type: 'application/json; charset=utf-8' + Transfer-Encoding: chunked + Connection: keep-alive + CF-Ray: 7e816dae3f560b58-AMS + CF-Cache-Status: DYNAMIC + Cache-Control: no-store + Strict-Transport-Security: 'max-age=15552000; includeSubDomains' + Vary: Accept-Encoding + content-security-policy: 'frame-ancestors ''none''' + x-content-type-options: nosniff + x-frame-options: DENY + Server: cloudflare + alt-svc: 'h3=":443"; ma=86400' + body: '{"data":[{"id":"de37fb63-986b-4f83-bf9f-612734316fdc","status":"cancelled","custom_id":null,"name":"Subscription name","amount":"100.0","currency":"EUR","period":"month","period_quantity":3,"current_period_starts_at":"2023-07-17T08:46:29+00:00","current_period_ends_at":"2023-07-24T08:46:28+00:00","current_period_paid":false,"payer_email":"user@example.com","payer_name":null,"product_name":null,"product_description":null,"success_redirect_url":null,"unsuccess_redirect_url":null,"created_at":"2023-07-17T08:46:29+00:00","cancelled_at":"2023-07-17T08:54:44+00:00"},{"id":"64249ede-8969-4d5c-a042-806f9c3e7db3","status":"active","custom_id":"PAYMENT-123","name":"Subscription name","amount":"100.0","currency":"EUR","period":"month","period_quantity":3,"current_period_starts_at":"2023-07-17T08:44:30+00:00","current_period_ends_at":"2024-01-18T10:58:28+00:00","current_period_paid":false,"payer_email":"user@example.com","payer_name":null,"product_name":null,"product_description":null,"success_redirect_url":null,"unsuccess_redirect_url":null,"created_at":"2023-07-17T08:44:30+00:00","cancelled_at":null}],"meta":{"total":2,"has_more":false}}' + curl_info: + url: 'https://business-sandbox.cryptopay.me/api/subscriptions' + content_type: 'application/json; charset=utf-8' + http_code: 200 + header_size: 478 + request_size: 260 + filetime: -1 + ssl_verify_result: 0 + redirect_count: 0 + total_time: 0.767475 + namelookup_time: 0.223341 + connect_time: 0.425383 + pretransfer_time: 0.573351 + size_upload: 0.0 + size_download: 1145.0 + speed_download: 1492.0 + speed_upload: 0.0 + download_content_length: -1.0 + upload_content_length: -1.0 + starttransfer_time: 0.766369 + redirect_time: 0.0 + redirect_url: '' + primary_ip: 172.66.40.240 + certinfo: { } + primary_port: 443 + local_ip: 192.168.208.4 + local_port: 50230 + http_version: 2 + protocol: 2 + ssl_verifyresult: 0 + scheme: HTTPS + appconnect_time_us: 573228 + connect_time_us: 425383 + namelookup_time_us: 223341 + pretransfer_time_us: 573351 + redirect_time_us: 0 + starttransfer_time_us: 766369 + total_time_us: 767475 + index: 0 diff --git a/tests/cassettes/subscriptions/cancel.yml b/tests/cassettes/subscriptions/cancel.yml new file mode 100644 index 0000000..364e2ff --- /dev/null +++ b/tests/cassettes/subscriptions/cancel.yml @@ -0,0 +1,74 @@ + +- + request: + method: POST + url: 'https://business-sandbox.cryptopay.me/api/subscriptions/7dd7da55-2fd6-445e-8c7c-6c2c85d135d7/cancel' + headers: + Host: business-sandbox.cryptopay.me + Content-Length: '0' + Accept-Encoding: '' + Content-Type: application/json + Date: 'Mon, 17 Jul 2023 09:33:06 +0000' + Authorization: 'HMAC OtzdZAvAkmw4vAYniZ4ljw:8p9LeEnzh9ndFmwEj4M8UH86k1M=' + User-Agent: 'Cryptopay-PHP/2.1.0 PHP/7.4.33' + Accept: '' + response: + status: + http_version: '1.1' + code: '200' + message: OK + headers: + Date: 'Mon, 17 Jul 2023 09:33:07 GMT' + Content-Type: 'application/json; charset=utf-8' + Transfer-Encoding: chunked + Connection: keep-alive + CF-Ray: 7e817b483813b7af-AMS + CF-Cache-Status: DYNAMIC + Cache-Control: no-store + Strict-Transport-Security: 'max-age=15552000; includeSubDomains' + Vary: Accept-Encoding + content-security-policy: 'frame-ancestors ''none''' + x-content-type-options: nosniff + x-frame-options: DENY + Server: cloudflare + alt-svc: 'h3=":443"; ma=86400' + body: '{"data":{"id":"7dd7da55-2fd6-445e-8c7c-6c2c85d135d7","status":"cancelled","custom_id":null,"name":"Subscription name","amount":"100.0","currency":"EUR","period":"month","period_quantity":3,"current_period_starts_at":"2023-07-17T09:27:43+00:00","current_period_ends_at":"2023-07-24T09:27:43+00:00","current_period_paid":false,"payer_email":"user@example.com","payer_name":null,"product_name":null,"product_description":null,"success_redirect_url":null,"unsuccess_redirect_url":null,"created_at":"2023-07-17T09:27:43+00:00","cancelled_at":"2023-07-17T09:33:07+00:00"}}' + curl_info: + url: 'https://business-sandbox.cryptopay.me/api/subscriptions/7dd7da55-2fd6-445e-8c7c-6c2c85d135d7/cancel' + content_type: 'application/json; charset=utf-8' + http_code: 200 + header_size: 478 + request_size: 324 + filetime: -1 + ssl_verify_result: 0 + redirect_count: 0 + total_time: 0.526309 + namelookup_time: 0.093494 + connect_time: 0.184553 + pretransfer_time: 0.326651 + size_upload: 0.0 + size_download: 566.0 + speed_download: 1076.0 + speed_upload: 0.0 + download_content_length: -1.0 + upload_content_length: -1.0 + starttransfer_time: 0.52627 + redirect_time: 0.0 + redirect_url: '' + primary_ip: 172.66.43.16 + certinfo: { } + primary_port: 443 + local_ip: 192.168.208.4 + local_port: 56280 + http_version: 2 + protocol: 2 + ssl_verifyresult: 0 + scheme: HTTPS + appconnect_time_us: 326603 + connect_time_us: 184553 + namelookup_time_us: 93494 + pretransfer_time_us: 326651 + redirect_time_us: 0 + starttransfer_time_us: 526270 + total_time_us: 526309 + index: 0 diff --git a/tests/cassettes/subscriptions/create.yml b/tests/cassettes/subscriptions/create.yml new file mode 100644 index 0000000..afc9b8f --- /dev/null +++ b/tests/cassettes/subscriptions/create.yml @@ -0,0 +1,74 @@ + +- + request: + method: POST + url: 'https://business-sandbox.cryptopay.me/api/subscriptions' + headers: + Host: business-sandbox.cryptopay.me + Expect: '' + Accept-Encoding: '' + Content-Type: application/json + Date: 'Mon, 17 Jul 2023 09:33:07 +0000' + Authorization: 'HMAC OtzdZAvAkmw4vAYniZ4ljw:c4GqGbyGm0GckzlkooOYDQS5Qs8=' + User-Agent: 'Cryptopay-PHP/2.1.0 PHP/7.4.33' + Accept: '' + body: '{"name":"Subscription name","amount":"100.0","currency":"EUR","period":"month","period_quantity":3,"payer_email":"user@example.com","starts_at":"2023-07-24T09:33:07+00:00"}' + response: + status: + http_version: '1.1' + code: '201' + message: Created + headers: + Date: 'Mon, 17 Jul 2023 09:33:07 GMT' + Content-Type: 'application/json; charset=utf-8' + Transfer-Encoding: chunked + Connection: keep-alive + CF-Ray: 7e817b4aaebc0ba8-AMS + CF-Cache-Status: DYNAMIC + Cache-Control: no-store + Strict-Transport-Security: 'max-age=15552000; includeSubDomains' + content-security-policy: 'frame-ancestors ''none''' + x-content-type-options: nosniff + x-frame-options: DENY + Server: cloudflare + alt-svc: 'h3=":443"; ma=86400' + body: '{"data":{"id":"cbfd6e54-ed42-4cd1-af1e-2ee8eaa9c5e3","status":"active","custom_id":null,"name":"Subscription name","amount":"100.0","currency":"EUR","period":"month","period_quantity":3,"current_period_starts_at":"2023-07-17T09:33:07+00:00","current_period_ends_at":"2023-07-24T09:33:07+00:00","current_period_paid":false,"payer_email":"user@example.com","payer_name":null,"product_name":null,"product_description":null,"success_redirect_url":null,"unsuccess_redirect_url":null,"created_at":"2023-07-17T09:33:07+00:00","cancelled_at":null}}' + curl_info: + url: 'https://business-sandbox.cryptopay.me/api/subscriptions' + content_type: 'application/json; charset=utf-8' + http_code: 201 + header_size: 460 + request_size: 454 + filetime: -1 + ssl_verify_result: 0 + redirect_count: 0 + total_time: 0.349274 + namelookup_time: 0.006026 + connect_time: 0.096794 + pretransfer_time: 0.197668 + size_upload: 172.0 + size_download: 540.0 + speed_download: 1547.0 + speed_upload: 492.0 + download_content_length: -1.0 + upload_content_length: 172.0 + starttransfer_time: 0.349243 + redirect_time: 0.0 + redirect_url: '' + primary_ip: 172.66.43.16 + certinfo: { } + primary_port: 443 + local_ip: 192.168.208.4 + local_port: 56284 + http_version: 2 + protocol: 2 + ssl_verifyresult: 0 + scheme: HTTPS + appconnect_time_us: 197565 + connect_time_us: 96794 + namelookup_time_us: 6026 + pretransfer_time_us: 197668 + redirect_time_us: 0 + starttransfer_time_us: 349243 + total_time_us: 349274 + index: 0 diff --git a/tests/cassettes/subscriptions/retrieve.yml b/tests/cassettes/subscriptions/retrieve.yml new file mode 100644 index 0000000..5a93234 --- /dev/null +++ b/tests/cassettes/subscriptions/retrieve.yml @@ -0,0 +1,72 @@ + +- + request: + method: GET + url: 'https://business-sandbox.cryptopay.me/api/subscriptions/64249ede-8969-4d5c-a042-806f9c3e7db3' + headers: + Host: business-sandbox.cryptopay.me + Accept-Encoding: '' + Content-Type: application/json + Date: 'Mon, 17 Jul 2023 09:27:43 +0000' + Authorization: 'HMAC OtzdZAvAkmw4vAYniZ4ljw:jD4uZDHi/lA6EFA5RkiZiX9wWO8=' + User-Agent: 'Cryptopay-PHP/2.1.0 PHP/8.1.21' + Accept: '' + response: + status: + code: 200 + message: OK + headers: + Date: 'Mon, 17 Jul 2023 09:27:44 GMT' + Content-Type: 'application/json; charset=utf-8' + Transfer-Encoding: chunked + Connection: keep-alive + CF-Ray: 7e8173661bf70b67-AMS + CF-Cache-Status: DYNAMIC + Cache-Control: no-store + Strict-Transport-Security: 'max-age=15552000; includeSubDomains' + Vary: Accept-Encoding + content-security-policy: "frame-ancestors 'none'" + x-content-type-options: nosniff + x-frame-options: DENY + Server: cloudflare + alt-svc: 'h3=":443"; ma=86400' + body: '{"data":{"id":"64249ede-8969-4d5c-a042-806f9c3e7db3","status":"active","custom_id":"PAYMENT-123","name":"Subscription name","amount":"100.0","currency":"EUR","period":"month","period_quantity":3,"current_period_starts_at":"2023-07-17T08:44:30+00:00","current_period_ends_at":"2024-01-18T10:58:28+00:00","current_period_paid":false,"payer_email":"user@example.com","payer_name":null,"product_name":null,"product_description":null,"success_redirect_url":null,"unsuccess_redirect_url":null,"created_at":"2023-07-17T08:44:30+00:00","cancelled_at":null}}' + curl_info: + url: 'https://business-sandbox.cryptopay.me/api/subscriptions/64249ede-8969-4d5c-a042-806f9c3e7db3' + content_type: 'application/json; charset=utf-8' + http_code: 200 + header_size: 478 + request_size: 297 + filetime: -1 + ssl_verify_result: 0 + redirect_count: 0 + total_time: 0.519626 + namelookup_time: 0.005658 + connect_time: 0.185708 + pretransfer_time: 0.32213 + size_upload: 0.0 + size_download: 549.0 + speed_download: 1056.0 + speed_upload: 0.0 + download_content_length: -1.0 + upload_content_length: 0.0 + starttransfer_time: 0.519579 + redirect_time: 0.0 + redirect_url: '' + primary_ip: 172.66.40.240 + certinfo: { } + primary_port: 443 + local_ip: 192.168.208.2 + local_port: 46486 + http_version: 2 + protocol: 2 + ssl_verifyresult: 0 + scheme: HTTPS + appconnect_time_us: 321999 + connect_time_us: 185708 + namelookup_time_us: 5658 + pretransfer_time_us: 322130 + redirect_time_us: 0 + starttransfer_time_us: 519579 + total_time_us: 519626 + index: 0 diff --git a/tests/cassettes/subscriptions/retrieveByCustomId.yml b/tests/cassettes/subscriptions/retrieveByCustomId.yml new file mode 100644 index 0000000..99346d1 --- /dev/null +++ b/tests/cassettes/subscriptions/retrieveByCustomId.yml @@ -0,0 +1,72 @@ + +- + request: + method: GET + url: 'https://business-sandbox.cryptopay.me/api/subscriptions/custom_id/PAYMENT-123' + headers: + Host: business-sandbox.cryptopay.me + Accept-Encoding: '' + Content-Type: application/json + Date: 'Mon, 17 Jul 2023 09:27:44 +0000' + Authorization: 'HMAC OtzdZAvAkmw4vAYniZ4ljw:OoJaAkj2ARIcEEL8QNrrIG0aCkU=' + User-Agent: 'Cryptopay-PHP/2.1.0 PHP/8.1.21' + Accept: '' + response: + status: + code: 200 + message: OK + headers: + Date: 'Mon, 17 Jul 2023 09:27:44 GMT' + Content-Type: 'application/json; charset=utf-8' + Transfer-Encoding: chunked + Connection: keep-alive + CF-Ray: 7e817369697dd0d1-AMS + CF-Cache-Status: DYNAMIC + Cache-Control: no-store + Strict-Transport-Security: 'max-age=15552000; includeSubDomains' + Vary: Accept-Encoding + content-security-policy: "frame-ancestors 'none'" + x-content-type-options: nosniff + x-frame-options: DENY + Server: cloudflare + alt-svc: 'h3=":443"; ma=86400' + body: '{"data":{"id":"64249ede-8969-4d5c-a042-806f9c3e7db3","status":"active","custom_id":"PAYMENT-123","name":"Subscription name","amount":"100.0","currency":"EUR","period":"month","period_quantity":3,"current_period_starts_at":"2023-07-17T08:44:30+00:00","current_period_ends_at":"2024-01-18T10:58:28+00:00","current_period_paid":false,"payer_email":"user@example.com","payer_name":null,"product_name":null,"product_description":null,"success_redirect_url":null,"unsuccess_redirect_url":null,"created_at":"2023-07-17T08:44:30+00:00","cancelled_at":null}}' + curl_info: + url: 'https://business-sandbox.cryptopay.me/api/subscriptions/custom_id/PAYMENT-123' + content_type: 'application/json; charset=utf-8' + http_code: 200 + header_size: 478 + request_size: 282 + filetime: -1 + ssl_verify_result: 0 + redirect_count: 0 + total_time: 0.588396 + namelookup_time: 0.007794 + connect_time: 0.196409 + pretransfer_time: 0.34157 + size_upload: 0.0 + size_download: 549.0 + speed_download: 933.0 + speed_upload: 0.0 + download_content_length: -1.0 + upload_content_length: 0.0 + starttransfer_time: 0.588362 + redirect_time: 0.0 + redirect_url: '' + primary_ip: 172.66.40.240 + certinfo: { } + primary_port: 443 + local_ip: 192.168.208.2 + local_port: 46502 + http_version: 2 + protocol: 2 + ssl_verifyresult: 0 + scheme: HTTPS + appconnect_time_us: 341470 + connect_time_us: 196409 + namelookup_time_us: 7794 + pretransfer_time_us: 341570 + redirect_time_us: 0 + starttransfer_time_us: 588362 + total_time_us: 588396 + index: 0