├── .github
├── FUNDING.yml
└── workflows
│ └── php.yml
├── src
├── Events
│ ├── NullDispatcher.php
│ ├── TransactionCreating.php
│ ├── TransactionCompleted.php
│ └── TransactionCreated.php
├── Exceptions
│ ├── ClientException.php
│ ├── NetworkException.php
│ ├── ServerException.php
│ ├── UnknownException.php
│ ├── TransbankException.php
│ └── HandlesException.php
├── Services
│ ├── WrapsDetail.php
│ ├── Transactions
│ │ ├── TransactionDetail.php
│ │ ├── Response.php
│ │ ├── Transaction.php
│ │ └── DynamicallyAccess.php
│ ├── HandlesCredentials.php
│ ├── DebugsTransactions.php
│ ├── FiresEvents.php
│ ├── SendsRequests.php
│ ├── Webpay.php
│ ├── WebpayMall.php
│ └── OneclickMall.php
├── ApiRequest.php
├── Credentials
│ ├── Credentials.php
│ └── Container.php
├── Transbank.php
└── Http
│ └── Connector.php
├── phpunit.xml
├── LICENSE
├── composer.json
└── README.md
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # Help me support this package
2 |
3 | ko_fi: DarkGhostHunter
4 | custom: ['https://paypal.me/darkghosthunter']
5 |
--------------------------------------------------------------------------------
/src/Events/NullDispatcher.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | tests
5 |
6 |
7 |
8 |
9 | src
10 |
11 |
12 | src/Events/NullDispatcher.php
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/Services/Transactions/TransactionDetail.php:
--------------------------------------------------------------------------------
1 | data['response_code']) && $this->data['response_code'] === 0;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Services/HandlesCredentials.php:
--------------------------------------------------------------------------------
1 | transbank->isProduction()) {
24 | return $this->container->getProductionCredentials(static::SERVICE_NAME);
25 | }
26 |
27 | // If we're running on integration, there is no harm on creating new credentials for each request.
28 | return Credentials::integrationCredentials($overrideServiceName);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Exceptions/TransbankException.php:
--------------------------------------------------------------------------------
1 | token;
32 | }
33 |
34 | /**
35 | * Returns the transaction URL where the transaction can be retrieved.
36 | *
37 | * @return string
38 | */
39 | public function getUrl(): string
40 | {
41 | return $this->url;
42 | }
43 |
44 | /**
45 | * Transforms the Response into a String for Webpay GET redirects.
46 | *
47 | * @return string
48 | */
49 | public function __toString(): string
50 | {
51 | return $this->url . '?' . http_build_query([$this->tokenName => $this->token]);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/Services/DebugsTransactions.php:
--------------------------------------------------------------------------------
1 | transbank->logger->debug($message, $context);
18 | }
19 |
20 | /**
21 | * Debugs a transaction before creating it.
22 | *
23 | * @param \DarkGhostHunter\Transbank\ApiRequest $apiRequest
24 | */
25 | protected function logCreating(ApiRequest $apiRequest): void
26 | {
27 | $this->transbank->logger->debug('Creating transaction', ['api_request' => $apiRequest]);
28 | }
29 |
30 | /**
31 | * Debugs a given operation.
32 | *
33 | * @param \DarkGhostHunter\Transbank\ApiRequest $apiRequest
34 | * @param array $rawResponse
35 | * @param string|null $token
36 | */
37 | protected function logResponse(ApiRequest $apiRequest, array $rawResponse, string $token = null): void
38 | {
39 | $context = ['api_request' => $apiRequest, 'raw_response' => $rawResponse];
40 |
41 | if ($token) {
42 | $context['token'] = $token;
43 | }
44 |
45 | $this->transbank->logger->debug('Response received', $context);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "darkghosthunter/transbank",
3 | "description": "Easy-to-use Transbank SDK for PHP.",
4 | "license": "MIT",
5 | "keywords": [
6 | "payments",
7 | "transbank",
8 | "api",
9 | "sdk",
10 | "webpay"
11 | ],
12 | "config": {
13 | "sort-packages": true
14 | },
15 | "require": {
16 | "php": "^8.0",
17 | "ext-json": "*",
18 | "psr/http-client": "1.*",
19 | "psr/log": "1.*|2.*|3.*",
20 | "psr/event-dispatcher": "1.*",
21 | "nyholm/psr7": "^1.4"
22 | },
23 | "require-dev": {
24 | "roave/security-advisories": "dev-latest",
25 | "phpunit/phpunit": "^9.5",
26 | "mockery/mockery": "^1.4",
27 | "guzzlehttp/guzzle": "^7.4"
28 | },
29 | "suggest": {
30 | "guzzlehttp/guzzle": "HTTP Client for contacting Transbank servers.",
31 | "symfony/http-client": "HTTP Client for contacting Transbank servers.",
32 | "monolog/monolog": "Allows advanced logging for this SDK operations.",
33 | "symfony/event-dispatcher": "Allows for hearing transactions created and completed.",
34 | "league/event": "Allows for hearing transactions created and completed."
35 | },
36 | "autoload": {
37 | "psr-4": {
38 | "DarkGhostHunter\\Transbank\\": "src"
39 | }
40 | },
41 | "autoload-dev": {
42 | "psr-4": {
43 | "Tests\\": "tests"
44 | }
45 | },
46 | "scripts": {
47 | "test": "vendor/bin/phpunit --coverage-clover build/logs/clover.xml",
48 | "test-coverage": "vendor/bin/phpunit --coverage-html coverage"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/.github/workflows/php.yml:
--------------------------------------------------------------------------------
1 | name: PHPUnit Tests
2 |
3 | on:
4 | push:
5 | pull_request:
6 |
7 | jobs:
8 | test:
9 |
10 | runs-on: ubuntu-latest
11 | strategy:
12 | fail-fast: true
13 | matrix:
14 | php: [8.0, 8.1]
15 | dependency-version: [prefer-stable, prefer-lowest]
16 |
17 | name: PHP ${{ matrix.php }} - ${{ matrix.dependency-version }}
18 |
19 | steps:
20 | - name: Checkout
21 | uses: actions/checkout@v2
22 |
23 | - name: Setup PHP
24 | uses: shivammathur/setup-php@v2
25 | with:
26 | php-version: ${{ matrix.php }}
27 | extensions: mbstring, intl, json
28 | coverage: xdebug
29 |
30 | - name: Cache dependencies
31 | uses: actions/cache@v2
32 | with:
33 | path: ~/.composer/cache/files
34 | key: ${{ runner.os }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
35 | restore-keys: ${{ runner.os }}-php-${{ matrix.php }}-composer-
36 |
37 | - name: Install dependencies
38 | run: composer update --${{ matrix.dependency-version }} --prefer-dist --no-progress --no-suggest
39 |
40 | - name: Run Tests
41 | run: composer run-script test
42 |
43 | - name: Upload Coverage to Coveralls
44 | env:
45 | COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
46 | COVERALLS_SERVICE_NAME: github
47 | run: |
48 | rm -rf composer.* vendor/
49 | composer require php-coveralls/php-coveralls
50 | vendor/bin/php-coveralls --coverage_clover=build/logs/clover.xml
51 |
--------------------------------------------------------------------------------
/src/Services/FiresEvents.php:
--------------------------------------------------------------------------------
1 | transbank->event->dispatch(new TransactionCreating($apiRequest));
22 | }
23 |
24 | /**
25 | * Fires a Transaction Created event.
26 | *
27 | * @param \DarkGhostHunter\Transbank\ApiRequest $apiRequest
28 | * @param \DarkGhostHunter\Transbank\Services\Transactions\Response $response
29 | */
30 | protected function fireCreated(ApiRequest $apiRequest, Response $response): void
31 | {
32 | $this->transbank->event->dispatch(new TransactionCreated($apiRequest, $response));
33 | }
34 |
35 | /**
36 | * Fires a Transaction Completed event.
37 | *
38 | * @param \DarkGhostHunter\Transbank\ApiRequest $apiRequest
39 | * @param \DarkGhostHunter\Transbank\Services\Transactions\Transaction $transaction
40 | */
41 | protected function fireCompleted(ApiRequest $apiRequest, Transaction $transaction): void
42 | {
43 | $this->transbank->event->dispatch(new TransactionCompleted($apiRequest, $transaction));
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Exceptions/HandlesException.php:
--------------------------------------------------------------------------------
1 | apiRequest;
39 | }
40 |
41 | /**
42 | * Returns the Server Request sent to Transbank, if any.
43 | *
44 | * @return \Psr\Http\Message\ServerRequestInterface|null
45 | */
46 | public function getServerRequest(): ?ServerRequestInterface
47 | {
48 | return $this->request;
49 | }
50 |
51 | /**
52 | * Returns the Response from Transbank, if any.
53 | *
54 | * @return \Psr\Http\Message\ResponseInterface|null
55 | */
56 | public function getResponse(): ?ResponseInterface
57 | {
58 | return $this->response;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Services/SendsRequests.php:
--------------------------------------------------------------------------------
1 | transbank->connector->send(
36 | $method,
37 | $this->buildEndpoint($endpoint, $replace),
38 | $apiRequest,
39 | $this->getEnvironmentCredentials($action),
40 | $options
41 | );
42 | }
43 |
44 | /**
45 | * Builds the endpoint, depending on the environment, and replaces keys from it.
46 | *
47 | * @param string $endpoint
48 | * @param array $replace
49 | *
50 | * @return string
51 | */
52 | protected function buildEndpoint(string $endpoint, array $replace = []): string
53 | {
54 | $endpoint = $this->transbank->isProduction()
55 | ? Connector::PRODUCTION_ENDPOINT . $endpoint
56 | : Connector::INTEGRATION_ENDPOINT . $endpoint;
57 |
58 | return str_replace(array_keys($replace), $replace, $endpoint);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/ApiRequest.php:
--------------------------------------------------------------------------------
1 | attributes)) {
31 | return '';
32 | }
33 |
34 | return json_encode($this->jsonSerialize(), $options);
35 | }
36 |
37 | /**
38 | * Specify data which should be serialized to JSON.
39 | *
40 | * @return mixed
41 | */
42 | public function jsonSerialize(): array
43 | {
44 | return $this->attributes;
45 | }
46 |
47 | /**
48 | * Whether an offset exists.
49 | *
50 | * @param mixed $offset
51 | * @return bool
52 | */
53 | public function offsetExists(mixed $offset): bool
54 | {
55 | return isset($this->attributes[$offset]);
56 | }
57 |
58 | /**
59 | * Offset to retrieve.
60 | *
61 | * @param mixed $offset
62 | * @return mixed
63 | */
64 | public function offsetGet(mixed $offset): mixed
65 | {
66 | return $this->attributes[$offset];
67 | }
68 |
69 | /**
70 | * Offset to set.
71 | *
72 | * @param mixed $offset
73 | * @param mixed $value
74 | * @return void
75 | */
76 | public function offsetSet(mixed $offset, mixed $value): void
77 | {
78 | $this->attributes[$offset] = $value;
79 | }
80 |
81 | /**
82 | * Offset to unset.
83 | *
84 | * @param mixed $offset
85 | * @return void
86 | */
87 | public function offsetUnset(mixed $offset): void
88 | {
89 | unset($this->attributes[$offset]);
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/Credentials/Credentials.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | public const INTEGRATION_KEYS = [
15 | 'webpay' => 597055555532,
16 | 'webpayMall' => 597055555535,
17 | 'webpayMall.capture' => 597055555531,
18 | 'oneclickMall' => 597055555541,
19 | 'oneclickMall.capture' => 597055555547,
20 | 'fullTransaction' => 597055555530,
21 | 'fullTransaction.capture' => 597055555531,
22 | 'fullTransactionMall' => 597055555551,
23 | 'fullTransactionMall.capture' => 597055555531,
24 | ];
25 |
26 | /**
27 | * Integration shared secret.
28 | *
29 | * @var string
30 | */
31 | public const INTEGRATION_SECRET = '579B532A7440BB0C9079DED94D31EA1615BACEB56610332264630D42D0A36B1C';
32 |
33 | /**
34 | * Service key, usually the Commerce Code.
35 | *
36 | * @var string|null
37 | */
38 | public ?string $key = null;
39 |
40 | /**
41 | * Service shared secret.
42 | *
43 | * @var string|null
44 | */
45 | public ?string $secret = null;
46 |
47 | /**
48 | * Create a new Credentials instance.
49 | *
50 | * @param string|null $key
51 | * @param string|null $secret
52 | */
53 | public function __construct(?string $key = null, ?string $secret = null)
54 | {
55 | $this->secret = $secret;
56 | $this->key = $key;
57 | }
58 |
59 | /**
60 | * Instance a new Credential object with the key and secret.
61 | *
62 | * @param string $key
63 | * @param string $secret
64 | * @return static
65 | */
66 | public static function make(string $key, string $secret): static
67 | {
68 | return new static($key, $secret);
69 | }
70 |
71 | /**
72 | * Returns integration key for a given service name.
73 | *
74 | * @param string $service
75 | * @return static
76 | */
77 | public static function integrationCredentials(string $service): static
78 | {
79 | if (!isset(static::INTEGRATION_KEYS[$service])) {
80 | throw new RuntimeException("The integration key for [$service] doesn't exist.");
81 | }
82 |
83 | return static::make(static::INTEGRATION_KEYS[$service], static::INTEGRATION_SECRET);
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/Credentials/Container.php:
--------------------------------------------------------------------------------
1 | $credentials
42 | * @return void
43 | */
44 | public function setFromArray(array $credentials): void
45 | {
46 | foreach ($credentials as $service => $credential) {
47 | // Check if the service name exists. If not, bail.
48 | $this->throwIfCredentialsDoesntExist($service);
49 |
50 | // We need the array declaring the key and the secret. If not, bail.
51 | if (!isset($credential['key'], $credential['secret'])) {
52 | throw new LogicException("Credentials for [$service] must have a [key] and [secret].");
53 | }
54 |
55 | $this->{$service} = Credentials::make($credential['key'], $credential['secret']);
56 | }
57 | }
58 |
59 | /**
60 | * Returns the credentials for a given service.
61 | *
62 | * @param string $service
63 | *
64 | * @return \DarkGhostHunter\Transbank\Credentials\Credentials
65 | */
66 | public function getProductionCredentials(string $service): Credentials
67 | {
68 | $this->throwIfCredentialsDoesntExist($service);
69 |
70 | return $this->{$service} ?? throw new RuntimeException("Production credentials for [$service] are not set.");
71 | }
72 |
73 | /**
74 | * Checks that credentials for a service name exists.
75 | *
76 | * @param string $service
77 | * @return void
78 | * @throws \LogicException
79 | */
80 | protected function throwIfCredentialsDoesntExist(string $service): void
81 | {
82 | if (!property_exists($this, $service)) {
83 | throw new LogicException("The Transbank service [$service] doesn't exist for these credentials.");
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/Services/Transactions/Transaction.php:
--------------------------------------------------------------------------------
1 | hello_world
21 | * $transaction->helloWorld
22 | * $transaction->getHelloWorld()
23 | * $transaction['hello_world']
24 | *
25 | * @package DarkGhostHunter\Transbank\Services\Transactions
26 | *
27 | * @property-read $details []static An array of details only for Mall transactions.
28 | */
29 | class Transaction implements ArrayAccess, JsonSerializable
30 | {
31 | use DynamicallyAccess;
32 |
33 | /**
34 | * ApiRequest constructor.
35 | *
36 | * @param string $serviceAction Name of the service and action that created this transaction using dot notation.
37 | * @param array $data Raw response array from Transbank.
38 | */
39 | public function __construct(public string $serviceAction, protected array $data)
40 | {
41 | //
42 | }
43 |
44 | /**
45 | * Creates a new Transaction with a detail array.
46 | *
47 | * @param string $serviceAction
48 | * @param array $response
49 | * @return static
50 | */
51 | public static function createWithDetails(string $serviceAction, array $response): static
52 | {
53 | // If the response contains details, add them as a class.
54 | if (isset($response['details']) && is_array($response['details'])) {
55 | foreach ($response['details'] as $index => $detail) {
56 | $response['details'][$index] = new TransactionDetail($detail);
57 | }
58 | }
59 |
60 | return new static($serviceAction, $response);
61 | }
62 |
63 | /**
64 | * Checks if the transaction was successful.
65 | *
66 | * @return bool
67 | */
68 | public function isSuccessful(): bool
69 | {
70 | // If TBK data has been received, immediately bail out.
71 | if (isset($this->data['TBK_ID_SESSION'], $this->data['TBK_ORDEN_COMPRA'])) {
72 | return false;
73 | }
74 |
75 | // If there is a native response code, return it.
76 | if (isset($this->data['response_code'])) {
77 | return $this->data['response_code'] === 0;
78 | }
79 |
80 | // If it has details,
81 | if (isset($this->data['details'])) {
82 | foreach ($this->data['details'] as $detail) {
83 | if (!$detail->isSuccessful()) {
84 | return false;
85 | }
86 | }
87 |
88 | return true;
89 | }
90 |
91 | return false;
92 | }
93 |
94 | /**
95 | * Returns the Credit Card number as an integer, or null if it doesn't exist.
96 | *
97 | * @return int|null
98 | */
99 | public function getCreditCardNumber(): ?int
100 | {
101 | if (!isset($this->data['card_detail']['card_number'])) {
102 | return null;
103 | }
104 |
105 | // If the card number is a string, then get the last 4 digits.
106 | if (is_string($this->data['card_detail']['card_number'])) {
107 | return (int)substr($this->data['card_detail']['card_number'], -4);
108 | }
109 |
110 | return $this->data['card_detail']['card_number'];
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/Services/Transactions/DynamicallyAccess.php:
--------------------------------------------------------------------------------
1 | data[$name]) || isset($this->data[$name = self::toSnakeCase($name)])) {
36 | return $this->data[$name];
37 | }
38 | }
39 |
40 | // Since there is no key matching the name, bail.
41 | throw new BadMethodCallException("Method $method does not exist");
42 | }
43 |
44 | /**
45 | * This function happily returns the key of a response using snake case.
46 | *
47 | * @param string $method
48 | *
49 | * @return string
50 | */
51 | protected static function toSnakeCase(string $method): string
52 | {
53 | return strtolower(ltrim(preg_replace('/[A-Z]([A-Z](?![a-z]))*/', '_$0', $method), '_'));
54 | }
55 |
56 | /**
57 | * Dynamically return a key from their properties.
58 | *
59 | * @param string $name
60 | *
61 | * @return mixed
62 | */
63 | public function __get(string $name)
64 | {
65 | if (isset($this->data[$name])) {
66 | return $this->data[$name];
67 | }
68 |
69 | // Lets try to use camelCase.
70 | if (ctype_lower($name[0]) && isset($this->data[$snake = self::toSnakeCase($name)])) {
71 | return $this->data[$snake];
72 | }
73 |
74 | // The property doesn't exist, so bail out.
75 | trigger_error("Undefined property: " . __CLASS__ . '::$' . $name, E_USER_ERROR);
76 | }
77 |
78 | /**
79 | * Disable setting a property.
80 | *
81 | * @param string $name
82 | * @param mixed $value
83 | */
84 | public function __set(string $name, mixed $value): void
85 | {
86 | // Immutable
87 | }
88 |
89 | /**
90 | * Checks if a property exists.
91 | *
92 | * @param string $name
93 | *
94 | * @return bool
95 | */
96 | public function __isset(string $name): bool
97 | {
98 | return $this->offsetExists($name) || isset($this->data[self::toSnakeCase($name)]);
99 | }
100 |
101 | /**
102 | * Whether an offset exists.
103 | *
104 | * @param mixed $offset
105 | * @return bool
106 | */
107 | public function offsetExists(mixed $offset): bool
108 | {
109 | return isset($this->data[$offset]);
110 | }
111 |
112 | /**
113 | * Offset to retrieve.
114 | *
115 | * @param mixed $offset
116 | * @return mixed
117 | */
118 | public function offsetGet(mixed $offset)
119 | {
120 | return $this->data[$offset];
121 | }
122 |
123 | /**
124 | * Offset to set.
125 | *
126 | * @param mixed $offset
127 | * @param mixed $value
128 | * @return void
129 | */
130 | public function offsetSet(mixed $offset, mixed $value): void
131 | {
132 | // Immutable.
133 | }
134 |
135 | /**
136 | * Offset to unset.
137 | *
138 | * @param mixed $offset
139 | * @return void
140 | */
141 | public function offsetUnset(mixed $offset): void
142 | {
143 | // Immutable
144 | }
145 |
146 | /**
147 | * Specify data which should be serialized to JSON
148 | *
149 | * @return array
150 | */
151 | public function jsonSerialize(): array
152 | {
153 | return $this->data;
154 | }
155 |
156 | /**
157 | * Transforms this transaction to a JSON string.
158 | *
159 | * @param int $options
160 | * @return string
161 | */
162 | public function toJson(int $options = 0): string
163 | {
164 | return json_encode($this->jsonSerialize(), $options);
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | [](https://packagist.org/packages/darkghosthunter/transbank) [](https://packagist.org/packages/darkghosthunter/transbank)  [](https://github.com/DarkGhostHunter/transbank/actions) [](https://coveralls.io/github/DarkGhostHunter/Transbank?branch=master)
4 |
5 | # Transbank
6 |
7 | Easy-to-use Transbank SDK for PHP.
8 |
9 | Supports Webpay, Webpay Mall and Webpay Oneclick Mall.
10 |
11 | ## Requisites:
12 |
13 | * PHP 8.0 or later.
14 | * `ext-json`
15 | * [HTTP Client](#http-client)
16 | * [Logger](#logger-optional) (optional)
17 | * [Event dispatcher](#event-dispatcher-optional) (optional)
18 |
19 | # Installation
20 |
21 | Require it with [Composer](https://getcomposer.org/):
22 |
23 | composer require darkghosthunter/webpay
24 |
25 | ## Usage
26 |
27 | This SDK mimics all the Webpay, Webpay Mall and Oneclick Mall methods from the official Transbank SDK for PHP.
28 |
29 | You can check the documentation of these services in Transbank Developer's site.
30 |
31 | - [Webpay](https://www.transbankdevelopers.cl/documentacion/webpay-plus#webpay-plus)
32 | - [Webpay Mall](https://www.transbankdevelopers.cl/documentacion/webpay-plus#webpay-plus-mall)
33 | - [Oneclick Mall](https://www.transbankdevelopers.cl/documentacion/oneclick)
34 |
35 | ## Quickstart
36 |
37 | Instance the `Transbank` object. You can do it manually, or with `make()` which will use Guzzle o Symfony HTTP Clients if any of these is already installed.
38 |
39 | ```php
40 | use DarkGhostHunter\Transbank\Transbank;
41 |
42 | $transbank = Transbank::make();
43 | ```
44 |
45 | If your project doesn't manage single instances, you can use `getInstance()` to receive a static Transbank instance.
46 |
47 | ```php
48 | use DarkGhostHunter\Transbank\Transbank;
49 |
50 | $transbank = Transbank::getInstance();
51 | ```
52 |
53 | ### Environments and credentials
54 |
55 | By default, this SDK starts up in **integration** environment, where all transactions made are fake by using Transbank's own _integration_ server.
56 |
57 | To operate in production mode, where all transaction will be real, you will need to use `toProduction()` along an `array` with the name of the service and their production credentials issued by Transbank to you: `webpay`, `webpayMall` or `oneclickMall`.
58 |
59 | ```php
60 | $transbank->toProduction([
61 | 'patpass' => ['555876543210','7a7b7d6cce5e...']
62 | ]);
63 | ```
64 |
65 | > For Mall operations, the "child" commerce code is only needed when doing the transactions.
66 |
67 | ### Using a Service
68 |
69 | To use a Transbank service, just call the method on the `Transbank` instance: `webpay`, `webpayMall` and `oneclickMall`.
70 |
71 | ```php
72 | use DarkGhostHunter\Transbank\Transbank;
73 |
74 | $transaction = Transbank::singleton()
75 | ->webpay()
76 | ->create('order#123', 9990, 'https://app.com/compra');
77 | ```
78 |
79 | ### HTTP Client
80 |
81 | This package is compatible with any PSR-18 compliant HTTP Client. IF you don't have one, you can install [Guzzle](https://docs.guzzlephp.org/) or [Symfony](https://symfony.com/doc/current/http_client.html)
82 |
83 | composer require guzzlehttp/guzzle:>=7.0
84 |
85 | or
86 |
87 | composer require symfony/http-client:>=5.2
88 |
89 | Some PHP platforms already ship with their own HTTP Client, like [Amp](https://amphp.org/http-client/), [ReactPHP](https://reactphp.org/http/), or [Swoole](https://www.swoole.co.uk/docs/modules/swoole-coroutine-http-client).
90 |
91 | ### Logger (optional)
92 |
93 | You can use this package with any PSR-3 compliant Log system, allowing to debug transactions. You can use [Monolog](https://github.com/Seldaek/monolog) if you don't have one.
94 |
95 | composer require monolog/monolog
96 |
97 | All operations are sent to the logger using `debug`.
98 |
99 | ### Event dispatcher (optional)
100 |
101 | You can use this package with any PSR-14 compliant Event Dispatcher, so you will be able to hear transactions started and completed. You can use [Symfony](https://github.com/symfony/event-dispatcher) or [League](https://event.thephpleague.com/) if you don't have one.
102 |
103 | composer require symfony/event-dispatcher
104 |
105 | or
106 |
107 | composer require league/event
108 |
109 | This package sends the following events:
110 |
111 | * `TransactionCreating` before a transaction is created in Transbank.
112 | * `TransactionCreated` after a transaction is created in Transbank, but pending payment.
113 | * `TransactionCompleted` after a transaction or refund is completed in Transbank, regardless of the success.
114 |
115 | ### Excepciones
116 |
117 | All exceptions implement `TransbankException`, so you can easily catch and check what happened.
118 |
119 | > Transactions properly rejected by banks or credit card issuers do not throw exceptions.
120 |
121 | There are 4 types of exceptions:
122 |
123 | * `ClientException`: Any error byproduct of bad transactions, bad configuration, abort, abandonment, timeout or invalid values.
124 | * `NetworkException`: Any communication error from Transbank Server, like network timeouts or wrong endpoints.
125 | * `ServerException`: Any internal Transbank error.
126 | * `UnknownException`: Any other error.
127 |
128 | > Exceptions are **not** logged.
129 |
130 | ## [Run examples locally](examples/README.md)
131 |
132 | # Licence
133 |
134 | The MIT License (MIT). Please see [License File](LICENSE) for more information.
135 |
136 | `Redcompra`, `Webpay`, `Oneclick`, `Onepay`, `Patpass` and `Transbank` are trademarks of [Transbank S.A.](https://www.transbank.cl/). This package and its author are not associated with Transbank S.A.
137 |
--------------------------------------------------------------------------------
/src/Transbank.php:
--------------------------------------------------------------------------------
1 | new \GuzzleHttp\Client(),
69 | class_exists(\Symfony\Component\HttpClient\Psr18Client::class) => new \Symfony\Component\HttpClient\Psr18Client(),
70 | default => throw new RuntimeException(
71 | 'The "guzzlehttp/guzzle" or "symfony/http-client" libraries are not present. Install one or use your own PSR-18 HTTP Client.'
72 | ),
73 | };
74 |
75 | return new static(
76 | new Container(),
77 | new NullLogger(),
78 | new NullDispatcher(),
79 | new Connector($client, $factory = new Psr17Factory(), $factory)
80 | );
81 | }
82 |
83 | /**
84 | * Returns the Transbank single instance.
85 | *
86 | * @return static
87 | */
88 | public static function getInstance(): self
89 | {
90 | return static::$instance ??= static::make();
91 | }
92 |
93 | /**
94 | * Sets the Transbank instance.
95 | *
96 | * @param static|null $instance
97 | * @return void
98 | */
99 | public static function setInstance(self $instance = null): void
100 | {
101 | static::$instance = $instance;
102 | }
103 |
104 | /**
105 | * Sets all the Transbank services to run in production servers.
106 | *
107 | * Supported services:
108 | * - webpay
109 | * - webpayMall
110 | * - oneclickMall
111 | * - fullTransaction
112 | * - fullTransactionMall
113 | *
114 | * @param array> $credentials
115 | * @return $this
116 | */
117 | public function toProduction(array $credentials): static
118 | {
119 | if (empty($credentials)) {
120 | throw new LogicException('Cannot set empty credentials for production environment.');
121 | }
122 |
123 | $this->credentials->setFromArray($credentials);
124 |
125 | $this->production = true;
126 |
127 | $this->logger->debug(
128 | 'Transbank has been set to production environment.',
129 | ['credentials' => array_keys($credentials)]
130 | );
131 |
132 | return $this;
133 | }
134 |
135 | /**
136 | * Returns the SDK to integration environment.
137 | *
138 | * @return $this
139 | */
140 | public function toIntegration(): static
141 | {
142 | $this->production = false;
143 |
144 | $this->logger->debug('Transbank has been set to integration environment.');
145 |
146 | return $this;
147 | }
148 |
149 | /**
150 | * Check if the current Transbank SDK are running in integration environment.
151 | *
152 | * @return bool
153 | */
154 | public function isIntegration(): bool
155 | {
156 | return !$this->isProduction();
157 | }
158 |
159 | /**
160 | * Check if the current Transbank SDK are running in production environment.
161 | *
162 | * @return bool
163 | */
164 | public function isProduction(): bool
165 | {
166 | return $this->production;
167 | }
168 |
169 | /**
170 | * Returns the Webpay service.
171 | *
172 | * @return \DarkGhostHunter\Transbank\Services\Webpay
173 | */
174 | public function webpay(): Services\Webpay
175 | {
176 | return $this->webpay ??= new Services\Webpay($this, $this->credentials);
177 | }
178 |
179 | /**
180 | * Returns the Webpay Mall service.
181 | *
182 | * @return \DarkGhostHunter\Transbank\Services\WebpayMall
183 | */
184 | public function webpayMall(): Services\WebpayMall
185 | {
186 | return $this->webpayMall ??= new Services\WebpayMall($this, $this->credentials);
187 | }
188 |
189 | /**
190 | * Returns the Oneclick Mall service.
191 | *
192 | * @return \DarkGhostHunter\Transbank\Services\OneclickMall
193 | */
194 | public function oneclickMall(): Services\OneclickMall
195 | {
196 | return $this->oneclickMall ??= new Services\OneclickMall($this, $this->credentials);
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/src/Services/Webpay.php:
--------------------------------------------------------------------------------
1 | $buyOrder,
71 | 'amount' => $amount,
72 | 'session_id' => $sessionId,
73 | 'return_url' => $returnUrl,
74 | ]
75 | );
76 |
77 | $this->logCreating($apiRequest);
78 | $this->fireCreating($apiRequest);
79 |
80 | $response = $this->send(static::SERVICE_NAME, $apiRequest, 'post', self::ENDPOINT_CREATE, [], $options);
81 | $transbankResponse = new Transactions\Response($response['token'], $response['url']);
82 |
83 | $this->fireCreated($apiRequest, $transbankResponse);
84 | $this->logResponse($apiRequest, $response);
85 |
86 | return $transbankResponse;
87 | }
88 |
89 | /**
90 | * Commits a transaction immediately
91 | *
92 | * @param string $token
93 | * @param array $options
94 | * @return \DarkGhostHunter\Transbank\Services\Transactions\Transaction
95 | *
96 | * @throws \JsonException|\DarkGhostHunter\Transbank\Exceptions\TransbankException
97 | */
98 | public function commit(string $token, array $options = []): Transactions\Transaction
99 | {
100 | $apiRequest = new ApiRequest(static::ACTION_COMMIT);
101 |
102 | $this->log('Committing transaction', ['token' => $token, 'api_request' => $apiRequest]);
103 |
104 | $response = $this->send(
105 | static::SERVICE_NAME,
106 | $apiRequest,
107 | 'put',
108 | static::ENDPOINT_COMMIT,
109 | ['{token}' => $token],
110 | $options
111 | );
112 |
113 | $transaction = new Transactions\Transaction(static::ACTION_COMMIT, $response);
114 |
115 | $this->logResponse($apiRequest, $response, $token);
116 | $this->fireCompleted($apiRequest, $transaction);
117 |
118 | return $transaction;
119 | }
120 |
121 | /**
122 | * Returns the status of a non-expired transaction by its token.
123 | *
124 | * @param string $token
125 | * @param array $options
126 | * @return \DarkGhostHunter\Transbank\Services\Transactions\Transaction
127 | *
128 | * @throws \JsonException|\DarkGhostHunter\Transbank\Exceptions\TransbankException
129 | */
130 | public function status(string $token, array $options = []): Transactions\Transaction
131 | {
132 | $apiRequest = new ApiRequest(self::ACTION_STATUS);
133 |
134 | $this->log('Retrieving transaction status', ['token' => $token, 'api_request' => $apiRequest]);
135 |
136 | $response = $this->send(
137 | self::SERVICE_NAME,
138 | $apiRequest,
139 | 'get',
140 | self::ENDPOINT_STATUS,
141 | ['{token}' => $token],
142 | $options
143 | );
144 |
145 | $this->logResponse($apiRequest, $response, $token);
146 |
147 | return new Transactions\Transaction(static::ACTION_STATUS, $response);
148 | }
149 |
150 | /**
151 | * Refunds partially or totally a given credit-card charge amount.
152 | *
153 | * @param string $token
154 | * @param int|float $amount
155 | * @param array $options
156 | * @return \DarkGhostHunter\Transbank\Services\Transactions\Transaction
157 | *
158 | * @throws \JsonException|\DarkGhostHunter\Transbank\Exceptions\TransbankException
159 | */
160 | public function refund(string $token, int|float $amount, array $options = []): Transactions\Transaction
161 | {
162 | $apiRequest = new ApiRequest(static::ACTION_REFUND, ['amount' => $amount]);
163 |
164 | $this->log('Refunding transaction', ['token' => $token, 'api_request' => $apiRequest]);
165 |
166 | $this->fireCreating($apiRequest);
167 |
168 | $response = $this->send(
169 | static::SERVICE_NAME,
170 | $apiRequest,
171 | 'put',
172 | self::ENDPOINT_REFUND,
173 | ['{token}' => $token],
174 | $options
175 | );
176 |
177 | $transaction = new Transactions\Transaction(static::ACTION_REFUND, $response);
178 |
179 | $this->logResponse($apiRequest, $response, $token);
180 |
181 | $this->fireCompleted($apiRequest, $transaction);
182 |
183 | return $transaction;
184 | }
185 |
186 | /**
187 | * Creates a Capture ApiRequest on Transbank servers, returns a response.
188 | *
189 | * This transaction type only works for credit cards, and "holds" the amount up to 7 days.
190 | *
191 | * @param string $token
192 | * @param string $buyOrder
193 | * @param int $authorizationCode
194 | * @param int|float $captureAmount
195 | * @param array $options
196 | * @return \DarkGhostHunter\Transbank\Services\Transactions\Transaction
197 | *
198 | * @throws \JsonException|\DarkGhostHunter\Transbank\Exceptions\TransbankException
199 | */
200 | public function capture(
201 | string $token,
202 | string $buyOrder,
203 | int $authorizationCode,
204 | int|float $captureAmount,
205 | array $options = []
206 | ): Transactions\Transaction {
207 | $apiRequest = new ApiRequest(
208 | static::ACTION_CAPTURE,
209 | [
210 | 'buy_order' => $buyOrder,
211 | 'authorization_code' => $authorizationCode,
212 | 'capture_amount' => $captureAmount,
213 | ]
214 | );
215 |
216 | $this->log('Capturing transaction', ['token' => $token, 'api_request' => $apiRequest]);
217 |
218 | $response = $this->send(
219 | static::SERVICE_NAME,
220 | $apiRequest,
221 | 'put',
222 | self::ENDPOINT_CAPTURE,
223 | ['{token}' => $token],
224 | $options
225 | );
226 |
227 | $transaction = new Transactions\Transaction(static::ACTION_CAPTURE, $response);
228 |
229 | $this->logResponse($apiRequest, $response, $token);
230 | $this->fireCompleted($apiRequest, $transaction);
231 |
232 | return $transaction;
233 | }
234 | }
235 |
--------------------------------------------------------------------------------
/src/Services/WebpayMall.php:
--------------------------------------------------------------------------------
1 | $buyOrder,
64 | 'session_id' => $sessionId,
65 | 'return_url' => $returnUrl,
66 | 'details' => static::wrapDetails($details),
67 | ]
68 | );
69 |
70 | $this->logCreating($apiRequest);
71 | $this->fireCreating($apiRequest);
72 |
73 | $response = $this->send(self::SERVICE_NAME, $apiRequest, 'post', static::ENDPOINT_CREATE, [], $options);
74 |
75 | $transbankResponse = new Transactions\Response($response['token'], $response['url']);
76 |
77 | $this->logResponse($apiRequest, $response);
78 | $this->fireCreated($apiRequest, $transbankResponse);
79 |
80 | return $transbankResponse;
81 | }
82 |
83 | /**
84 | * Commits a Mall transaction from Transbank servers.
85 | *
86 | * @param string $token
87 | * @param array $options
88 | * @return \DarkGhostHunter\Transbank\Services\Transactions\Transaction
89 | *
90 | * @throws \JsonException|\DarkGhostHunter\Transbank\Exceptions\TransbankException
91 | */
92 | public function commit(string $token, array $options = []): Transactions\Transaction
93 | {
94 | $apiRequest = new ApiRequest(static::ACTION_COMMIT);
95 |
96 | $this->log('Committing transaction', ['token' => $token, 'api_request' => $apiRequest]);
97 |
98 | $response = $this->send(
99 | static::SERVICE_NAME,
100 | $apiRequest,
101 | 'put',
102 | static::ENDPOINT_COMMIT,
103 | ['{token}' => $token],
104 | $options
105 | );
106 |
107 | $transaction = Transactions\Transaction::createWithDetails(static::ACTION_COMMIT, $response);
108 |
109 | $this->logResponse($apiRequest, $response, $token);
110 | $this->fireCompleted($apiRequest, $transaction);
111 |
112 | return $transaction;
113 | }
114 |
115 |
116 | /**
117 | * Returns the transaction status by its token.
118 | *
119 | * @param string $token
120 | * @param array $options
121 | * @return \DarkGhostHunter\Transbank\Services\Transactions\Transaction
122 | *
123 | * @throws \JsonException|\DarkGhostHunter\Transbank\Exceptions\TransbankException
124 | */
125 | public function status(string $token, array $options = []): Transactions\Transaction
126 | {
127 | $apiRequest = new ApiRequest(static::ACTION_STATUS);
128 |
129 | $this->log('Retrieving transaction status', ['token' => $token, 'api_request' => $apiRequest]);
130 |
131 | $response = $this->send(
132 | static::SERVICE_NAME,
133 | $apiRequest,
134 | 'get',
135 | Webpay::ENDPOINT_STATUS,
136 | ['{token}' => $token],
137 | $options
138 | );
139 |
140 | $this->logResponse($apiRequest, $response, $token);
141 |
142 | return Transactions\Transaction::createWithDetails(static::ACTION_STATUS, $response);
143 | }
144 |
145 | /**
146 | * Refunds partially or totally a Mall transaction in Transbank.
147 | *
148 | * @param string|int $commerceCode
149 | * @param string $token
150 | * @param string $buyOrder
151 | * @param int|float $amount
152 | * @param array $options
153 | * @return \DarkGhostHunter\Transbank\Services\Transactions\Transaction
154 | *
155 | * @throws \JsonException|\DarkGhostHunter\Transbank\Exceptions\TransbankException
156 | */
157 | public function refund(
158 | string|int $commerceCode,
159 | string $token,
160 | string $buyOrder,
161 | int|float $amount,
162 | array $options = []
163 | ): Transactions\Transaction {
164 | $apiRequest = new ApiRequest(
165 | static::ACTION_REFUND,
166 | [
167 | 'commerce_code' => $commerceCode,
168 | 'buy_order' => $buyOrder,
169 | 'amount' => $amount,
170 | ]
171 | );
172 |
173 | $this->log('Refunding transaction', ['token' => $token, 'api_request' => $apiRequest]);
174 |
175 | $this->fireCreating($apiRequest);
176 |
177 | $response = $this->send(
178 | static::SERVICE_NAME,
179 | $apiRequest,
180 | 'post',
181 | static::ENDPOINT_REFUND,
182 | ['{token}' => $token],
183 | $options
184 | );
185 |
186 | $transaction = new Transactions\Transaction(static::ACTION_REFUND, $response);
187 |
188 | $this->logResponse($apiRequest, $response, $token);
189 | $this->fireCompleted($apiRequest, $transaction);
190 |
191 | return $transaction;
192 | }
193 |
194 | /**
195 | * Captures an amount of a given transaction by its token.
196 | *
197 | * @param string|int $commerceCode
198 | * @param string $token
199 | * @param string $buyOrder
200 | * @param int|string $authorizationCode
201 | * @param int|float $captureAmount
202 | * @param array $options
203 | * @return \DarkGhostHunter\Transbank\Services\Transactions\Transaction
204 | *
205 | * @throws \JsonException|\DarkGhostHunter\Transbank\Exceptions\TransbankException
206 | */
207 | public function capture(
208 | string|int $commerceCode,
209 | string $token,
210 | string $buyOrder,
211 | string|int $authorizationCode,
212 | int|float $captureAmount,
213 | array $options = []
214 | ): Transactions\Transaction {
215 | $apiRequest = new ApiRequest(
216 | static::ACTION_CAPTURE,
217 | [
218 | 'commerce_code' => $commerceCode,
219 | 'buy_order' => $buyOrder,
220 | 'authorization_code' => $authorizationCode,
221 | 'capture_amount' => $captureAmount,
222 | ]
223 | );
224 |
225 | $this->log('Capturing transaction', ['token' => $token, 'api_request' => $apiRequest]);
226 |
227 | // If we are on integration, we need to override the credentials.
228 | $serviceName = $this->transbank->isIntegration() ? static::ACTION_CAPTURE : static::SERVICE_NAME;
229 |
230 | $response = $this->send(
231 | $serviceName,
232 | $apiRequest,
233 | 'put',
234 | Webpay::ENDPOINT_CAPTURE,
235 | ['{token}' => $token],
236 | $options
237 | );
238 |
239 | $transaction = new Transactions\Transaction(static::ACTION_CAPTURE, $response);
240 |
241 | $this->logResponse($apiRequest, $response, $token);
242 | $this->fireCompleted($apiRequest, $transaction);
243 |
244 | return $transaction;
245 | }
246 | }
247 |
--------------------------------------------------------------------------------
/src/Http/Connector.php:
--------------------------------------------------------------------------------
1 | requestFactory->createServerRequest($method, $this->setApiVersion($endpoint));
98 |
99 | return $this->sendRequest($request, $apiRequest, $credentials, $options);
100 | }
101 |
102 | /**
103 | * Replace the API Version from the endpoint.
104 | *
105 | * @param string $endpoint
106 | *
107 | * @return string
108 | */
109 | protected function setApiVersion(string $endpoint): string
110 | {
111 | return str_replace('{api_version}', static::API_VERSION, $endpoint);
112 | }
113 |
114 | /**
115 | * Prepares the transaction and sends it.
116 | *
117 | * @param \Psr\Http\Message\ServerRequestInterface $request
118 | * @param \DarkGhostHunter\Transbank\ApiRequest $apiRequest
119 | * @param \DarkGhostHunter\Transbank\Credentials\Credentials $credentials
120 | * @param array $options
121 | *
122 | * @return array
123 | * @throws \JsonException|\DarkGhostHunter\Transbank\Exceptions\TransbankException
124 | */
125 | protected function sendRequest(
126 | Request $request,
127 | ApiRequest $apiRequest,
128 | Credentials $credentials,
129 | array $options = []
130 | ): array {
131 | $request = $this->prepareRequest($request, $apiRequest, $credentials, $options);
132 |
133 | try {
134 | $response = $this->client->sendRequest($request);
135 | } catch (NetworkExceptionInterface $exception) {
136 | throw new NetworkException(
137 | 'Could not establish connection with Transbank.',
138 | $apiRequest,
139 | $request,
140 | null,
141 | $exception
142 | );
143 | } catch (Throwable $exception) {
144 | throw new UnknownException(
145 | 'An error occurred when trying to communicate with Transbank.',
146 | $apiRequest,
147 | $request,
148 | null,
149 | $exception
150 | );
151 | }
152 |
153 | // If we received a response, check if the response is NOT an error.
154 | $this->throwExceptionOnResponseError($apiRequest, $request, $response);
155 |
156 | return $this->decodeJsonFromContents($apiRequest, $request, $response);
157 | }
158 |
159 | /**
160 | * Prepares the HTTP Request to send to the Transbank servers.
161 | *
162 | * @param \Psr\Http\Message\ServerRequestInterface $request
163 | * @param \DarkGhostHunter\Transbank\ApiRequest $apiRequest
164 | * @param \DarkGhostHunter\Transbank\Credentials\Credentials $credentials
165 | * @param array $options
166 | *
167 | * @return \Psr\Http\Message\ServerRequestInterface
168 | */
169 | protected function prepareRequest(
170 | Request $request,
171 | ApiRequest $apiRequest,
172 | Credentials $credentials,
173 | array $options
174 | ): Request {
175 | // Let the developer override the credentials headers by adding them before anything else.
176 | $request = $request->withHeader(static::HEADER_KEY, $credentials->key);
177 | $request = $request->withHeader(static::HEADER_SECRET, $credentials->secret);
178 |
179 | // Pass any "option" headers to the request.
180 | if ($options && isset($options['headers'])) {
181 | foreach ($options['headers'] as $header => $value) {
182 | $request = $request->withHeader($header, $value);
183 | }
184 | }
185 |
186 | $request = $request->withHeader('Content-Type', 'application/json');
187 | $request = $request->withHeader('User-Agent', 'php:darkghosthunter/transbank/' . Transbank::VERSION);
188 |
189 | return $request->withBody($this->streamFactory->createStream($apiRequest->toJson()));
190 | }
191 |
192 | /**
193 | * Checks if the Response is an error or not.
194 | *
195 | * @param \Psr\Http\Message\ResponseInterface $response
196 | * @param \Psr\Http\Message\ServerRequestInterface $request
197 | * @param \DarkGhostHunter\Transbank\ApiRequest $apiRequest
198 | * @return void
199 | */
200 | protected function throwExceptionOnResponseError(ApiRequest $apiRequest, Request $request, Response $response): void
201 | {
202 | // Bail out if the response is present but is not JSON.
203 | if ($response->getBody()->getSize() && !in_array('application/json', $response->getHeader('Content-Type'))) {
204 | throw new ServerException('Non-JSON response received.', $apiRequest, $request, $response);
205 | }
206 |
207 | $status = $response->getStatusCode();
208 |
209 | if ($status > 299) {
210 | if ($status < 400) {
211 | throw new ServerException('A redirection was returned.', $apiRequest, $request, $response);
212 | }
213 |
214 | if ($status < 500) {
215 | throw new ClientException($this->getErrorMessage($response), $apiRequest, $request, $response);
216 | }
217 |
218 | throw new ServerException($this->getErrorMessage($response), $apiRequest, $request, $response);
219 | }
220 | }
221 |
222 | /**
223 | * Returns the error message from the Transbank response.
224 | *
225 | * @param \Psr\Http\Message\ResponseInterface $response
226 | * @return string
227 | */
228 | protected function getErrorMessage(Response $response): string
229 | {
230 | $contents = json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR) ?? [];
231 |
232 | return $contents['error_message'] ?? $response->getBody()->getContents();
233 | }
234 |
235 | /**
236 | * Parses the JSON from the response, or bails if is malformed.
237 | *
238 | * @param \DarkGhostHunter\Transbank\ApiRequest $apiRequest
239 | * @param \Psr\Http\Message\ServerRequestInterface $request
240 | * @param \Psr\Http\Message\ResponseInterface $response
241 | *
242 | * @return array
243 | */
244 | protected function decodeJsonFromContents(
245 | ApiRequest $apiRequest,
246 | ServerRequestInterface $request,
247 | ResponseInterface $response
248 | ): array {
249 | if (empty($contents = $response->getBody()->getContents())) {
250 | return [];
251 | }
252 |
253 | try {
254 | return json_decode($contents, true, 512, JSON_THROW_ON_ERROR);
255 | } catch (JsonException $exception) {
256 | throw new ServerException('The response JSON is malformed.', $apiRequest, $request, $response, $exception);
257 | }
258 | }
259 | }
260 |
--------------------------------------------------------------------------------
/src/Services/OneclickMall.php:
--------------------------------------------------------------------------------
1 | $username,
76 | 'email' => $email,
77 | 'response_url' => $responseUrl,
78 | ]
79 | );
80 |
81 | $this->log('Creating subscription', ['api_request' => $apiRequest,]);
82 | $this->fireCreating($apiRequest);
83 |
84 | $response = $this->send(self::SERVICE_NAME, $apiRequest, 'post', static::ENDPOINT_START, [], $options);
85 | $transaction = new Transactions\Response($response['token'], $response['url_webpay']);
86 |
87 | $this->fireCreated($apiRequest, $transaction);
88 | $this->logResponse($apiRequest, $response);
89 |
90 | return new Transactions\Response($response['token'], $response['url_webpay'], 'TBK_TOKEN');
91 | }
92 |
93 | /**
94 | * Finishes a subscription process in Transbank.
95 | *
96 | * @param string $token
97 | * @param array $options
98 | * @return \DarkGhostHunter\Transbank\Services\Transactions\Transaction
99 | *
100 | * @throws \JsonException|\DarkGhostHunter\Transbank\Exceptions\TransbankException
101 | */
102 | public function finish(string $token, array $options = []): Transactions\Transaction
103 | {
104 | $apiRequest = new ApiRequest(static::ACTION_FINISH);
105 |
106 | $this->log('Finishing subscription', ['token' => $token, 'api_request' => $apiRequest]);
107 |
108 | $response = $this->send(
109 | static::SERVICE_NAME,
110 | $apiRequest,
111 | 'put',
112 | static::ENDPOINT_FINISH,
113 | ['{token}' => $token],
114 | $options
115 | );
116 |
117 | $transaction = new Transactions\Transaction(static::ACTION_FINISH, $response);
118 |
119 | $this->logResponse($apiRequest, $response, $token);
120 |
121 | $this->fireCompleted($apiRequest, $transaction);
122 |
123 | return $transaction;
124 | }
125 |
126 | /**
127 | * Deletes a subscription.
128 | *
129 | * If the subscription doesn't exist, an exception will be returned.
130 | *
131 | * @param string $tbkUser
132 | * @param string $username
133 | * @param array $options
134 | * @return void
135 | *
136 | * @throws \JsonException|\DarkGhostHunter\Transbank\Exceptions\TransbankException
137 | */
138 | public function delete(string $tbkUser, string $username, array $options = []): void
139 | {
140 | $apiRequest = new ApiRequest(static::ACTION_DELETE, ['tbk_user' => $tbkUser, 'username' => $username]);
141 |
142 | $this->log('Deleting subscription', ['api_request' => $apiRequest]);
143 |
144 | $response = $this->send(static::SERVICE_NAME, $apiRequest, 'delete', static::ENDPOINT_DELETE, [], $options);
145 |
146 | $this->logResponse($apiRequest, $response);
147 | }
148 |
149 | /**
150 | * Authorizes a given set of transactions.
151 | *
152 | * @param string $tbkUser
153 | * @param string $username
154 | * @param string $buyOrder
155 | * @param array $details
156 | * @param array $options
157 | * @return \DarkGhostHunter\Transbank\Services\Transactions\Transaction
158 | *
159 | * @throws \JsonException|\DarkGhostHunter\Transbank\Exceptions\TransbankException
160 | */
161 | public function authorize(
162 | string $tbkUser,
163 | string $username,
164 | string $buyOrder,
165 | array $details,
166 | array $options = []
167 | ): Transactions\Transaction {
168 | $apiRequest = new ApiRequest(
169 | static::ACTION_AUTHORIZE,
170 | [
171 | 'tbk_user' => $tbkUser,
172 | 'username' => $username,
173 | 'buy_order' => $buyOrder,
174 | 'details' => static::wrapDetails($details),
175 | ]
176 | );
177 |
178 | $this->log('Authorizing transaction', ['api_request' => $apiRequest]);
179 |
180 | $this->fireCreating($apiRequest);
181 |
182 | $response = $this->send(static::SERVICE_NAME, $apiRequest, 'post', static::ENDPOINT_AUTHORIZE, [], $options);
183 | $transaction = Transactions\Transaction::createWithDetails(static::ACTION_AUTHORIZE, $response);
184 |
185 | $this->logResponse($apiRequest, $response);
186 | $this->fireCompleted($apiRequest, $transaction);
187 |
188 | return $transaction;
189 | }
190 |
191 | /**
192 | * Retrieves a transaction from Transbank.
193 | *
194 | * @param string $buyOrder
195 | * @param array $options
196 | * @return \DarkGhostHunter\Transbank\Services\Transactions\Transaction
197 | *
198 | * @throws \JsonException|\DarkGhostHunter\Transbank\Exceptions\TransbankException
199 | */
200 | public function status(string $buyOrder, array $options = []): Transactions\Transaction
201 | {
202 | $apiRequest = new ApiRequest(static::ACTION_STATUS);
203 |
204 | $this->log('Retrieving transaction status', ['buy_order' => $buyOrder, 'api_request' => $apiRequest]);
205 |
206 | $response = $this->send(
207 | static::SERVICE_NAME,
208 | $apiRequest,
209 | 'get',
210 | static::ENDPOINT_STATUS,
211 | ['{buyOrder}' => $buyOrder],
212 | $options
213 | );
214 |
215 | $this->logResponse($apiRequest, $response);
216 |
217 | return Transactions\Transaction::createWithDetails(static::ACTION_AUTHORIZE, $response);
218 | }
219 |
220 | /**
221 | * Refunds a child transaction.
222 | *
223 | * @param string $buyOrder
224 | * @param string $childCommerceCode
225 | * @param string $childBuyOrder
226 | * @param int|float $amount
227 | * @param array $options
228 | * @return \DarkGhostHunter\Transbank\Services\Transactions\Transaction
229 | *
230 | * @throws \JsonException|\DarkGhostHunter\Transbank\Exceptions\TransbankException
231 | */
232 | public function refund(
233 | string $buyOrder,
234 | string $childCommerceCode,
235 | string $childBuyOrder,
236 | int|float $amount,
237 | array $options = []
238 | ): Transactions\Transaction {
239 | $apiRequest = new ApiRequest(
240 | static::ACTION_REFUND,
241 | [
242 | 'commerce_code' => $childCommerceCode,
243 | 'detail_buy_order' => $childBuyOrder,
244 | 'amount' => $amount,
245 | ]
246 | );
247 |
248 | $this->log('Refunding transaction', ['buy_order' => $buyOrder, 'api_request' => $apiRequest,]);
249 |
250 | $this->fireCreating($apiRequest);
251 |
252 | $response = $this->send(
253 | static::SERVICE_NAME,
254 | $apiRequest,
255 | 'post',
256 | static::ENDPOINT_REFUND,
257 | ['{buyOrder}' => $buyOrder],
258 | $options
259 | );
260 |
261 | $transaction = new Transactions\Transaction(static::ACTION_REFUND, $response);
262 |
263 | $this->log('Response received', ['buy_order' => $buyOrder, 'api_request' => $apiRequest, 'response' => $response]);
264 | $this->fireCompleted($apiRequest, $transaction);
265 |
266 | return $transaction;
267 | }
268 |
269 | /**
270 | * Captures a transaction from Transbank.
271 | *
272 | * @param string $commerceCode
273 | * @param string $buyOrder
274 | * @param string|int $authorizationCode
275 | * @param int|float $captureAmount
276 | * @param array $options
277 | * @return \DarkGhostHunter\Transbank\Services\Transactions\Transaction
278 | *
279 | * @throws \JsonException|\DarkGhostHunter\Transbank\Exceptions\TransbankException
280 | */
281 | public function capture(
282 | string $commerceCode,
283 | string $buyOrder,
284 | string|int $authorizationCode,
285 | int|float $captureAmount,
286 | array $options = []
287 | ): Transactions\Transaction {
288 | $apiRequest = new ApiRequest(
289 | static::ACTION_CAPTURE,
290 | [
291 | 'commerce_code' => $commerceCode,
292 | 'buy_order' => $buyOrder,
293 | 'authorization_code' => $authorizationCode,
294 | 'capture_amount' => $captureAmount,
295 | ]
296 | );
297 |
298 | $this->log('Capturing transaction', ['api_request' => $apiRequest]);
299 |
300 | // If we are on integration, we need to override the credentials.
301 | $serviceName = $this->transbank->isIntegration() ? static::ACTION_CAPTURE : static::SERVICE_NAME;
302 |
303 | $response = $this->send($serviceName, $apiRequest, 'put', static::ENDPOINT_CAPTURE, [], $options);
304 | $transaction = new Transactions\Transaction(static::ACTION_CAPTURE, $response);
305 |
306 | $this->logResponse($apiRequest, $response);
307 | $this->fireCompleted($apiRequest, $transaction);
308 |
309 | return $transaction;
310 | }
311 | }
312 |
--------------------------------------------------------------------------------