├── .editorconfig ├── .github └── workflows │ └── tests.yml ├── .gitignore ├── .phpunit.result.cache ├── .styleci.yml ├── LICENSE.md ├── README.md ├── composer.json ├── config └── laravel-sens.php ├── phpunit.xml.dist ├── src ├── AlimTalk │ ├── AlimTalk.php │ ├── AlimTalkChannel.php │ ├── AlimTalkMessage.php │ └── AlimTalkRequest.php ├── Contracts │ ├── Sens.php │ └── SensMessage.php ├── Exceptions │ └── SensException.php ├── Sens.php ├── SensServiceProvider.php └── Sms │ ├── Sms.php │ ├── SmsChannel.php │ └── SmsMessage.php └── tests └── SensSmsChannelTest.php /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | tests: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | strategy: 13 | fail-fast: true 14 | matrix: 15 | php: [ 7.2, 7.3, 7.3, 7.4, 8.0 ] 16 | stability: [prefer-stable] 17 | 18 | name: P${{ matrix.php }} 19 | 20 | steps: 21 | - name: Checkout code 22 | uses: actions/checkout@v2 23 | 24 | - name: Setup PHP 25 | uses: shivammathur/setup-php@v2 26 | with: 27 | php-version: ${{ matrix.php }} 28 | tools: composer:v2 29 | coverage: none 30 | 31 | - name: Validate composer.json and composer.lock 32 | run: composer validate 33 | 34 | - name: Install dependencies 35 | run: composer update --prefer-dist --no-interaction --no-progress 36 | 37 | - name: Execute tests 38 | run: vendor/bin/phpunit --verbose 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /vendor 3 | composer.lock 4 | .DS_Store -------------------------------------------------------------------------------- /.phpunit.result.cache: -------------------------------------------------------------------------------- 1 | C:37:"PHPUnit\Runner\DefaultTestResultCache":177:{a:2:{s:7:"defects";a:1:{s:7:"Warning";i:6;}s:5:"times";a:2:{s:7:"Warning";d:0.013;s:77:"Seungmun\Sens\Tests\SensSmsChannelTest::testIamSorryBecauseTestsAreSoNuisance";d:0.082;}}} -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: psr12 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2022 **Seungmun Jeong** 4 | 5 | > Permission is hereby granted, free of charge, to any person obtaining a copy 6 | > of this software and associated documentation files (the "Software"), to deal 7 | > in the Software without restriction, including without limitation the rights 8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | > copies of the Software, and to permit persons to whom the Software is 10 | > furnished to do so, subject to the following conditions: 11 | > 12 | > The above copyright notice and this permission notice shall be included in all 13 | > copies or substantial portions of the Software. 14 | > 15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | > SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NCLOUD SENS notifications channel for Laravel 2 | 3 | [![Latest Stable Version](https://poser.pugx.org/seungmun/laravel-sens/v)](//packagist.org/packages/seungmun/laravel-sens) 4 | [![Total Downloads](https://poser.pugx.org/seungmun/laravel-sens/downloads)](//packagist.org/packages/seungmun/laravel-sens) 5 | [![License](https://poser.pugx.org/seungmun/laravel-sens/license)](//packagist.org/packages/seungmun/laravel-sens) 6 | 7 | Build Status 8 | 9 | 10 | This package makes it easy to send notification using [ncloud sens](//ncloud.com/product/applicationService/sens) with Laravel. 11 | 12 | And We are working on an unofficial sdk development public project so that ncloud sens can be used in php more flexibly. 13 | 14 | You can check the project here. (https://github.com/seungmun/sens-php) 15 | 16 | ## Official Community 17 | 18 | - [라라벨코리아](https://laravel.kr/) 19 | - [라라벨코리아 오픈채팅](https://open.kakao.com/o/g3dWlf0) 20 | 21 | ## Prerequisites 22 | 23 | Before you get started, you need the following: 24 | 25 | - PHP >= 7.2 (9.x also compatible) 26 | - Laravel (9.x / 8.x / 7.x / 6.x) 27 | 28 | ## Installation 29 | 30 | You can install the package via composer: 31 | 32 | ``` bash 33 | composer require seungmun/laravel-sens 34 | ``` 35 | 36 | The package will automatically register itself. 37 | 38 | You can publish the config with: 39 | ```bash 40 | php artisan vendor:publish --provider="Seungmun\Sens\SensServiceProvider" --tag="config" 41 | ``` 42 | 43 | Also, you can use it without publish the config file can be used simply by adding environment variables with: 44 | 45 | ```bash 46 | SENS_ACCESS_KEY=your-sens-access-key 47 | SENS_SECRET_KEY=your-sens-secret-key 48 | SENS_SERVICE_ID=your-sens-service-id 49 | SENS_ALIMTALK_SERVICE_ID=your-alimtalk-service-id 50 | SENS_PlUS_FRIEND_ID=your-plus-friend-id 51 | ``` 52 | 53 | If you want to put the `sms_from` value in your .env, 54 | 55 | config/services.php 56 | 57 | ```php 58 | /* 59 | |-------------------------------------------------------------------------- 60 | | SMS "From" Number 61 | |-------------------------------------------------------------------------- 62 | | 63 | | This configuration option defines the phone number that will be used as 64 | | the "from" number for all outgoing text messages. You should provide 65 | | the number you have already reserved within your Naver Cloud Platform 66 | | /sens/sms-calling-number of dashboard. 67 | | 68 | */ 69 | 'sens' => [ 70 | 'services' => [ 71 | 'sms' => [ 72 | 'sender' => env('SENS_SMS_FROM'), 73 | ], 74 | ], 75 | ], 76 | ``` 77 | 78 | .env: 79 | 80 | ```env 81 | SENS_SMS_FROM=1234567890 82 | ``` 83 | 84 | ## Usage 85 | 86 | This package can be used using with the Laravel default notification feature. 87 | 88 | ##### 1) Request to send a SMS 89 | 90 | ```bash 91 | php artisan make:notification SendPurchaseReceipt 92 | ``` 93 | 94 | ```php 95 | to($notifiable->phone) 129 | ->from('055-000-0000') 130 | ->content('Welcome: https://open.kakao.com/o/g3dWlf0') 131 | ->contentType('AD')// You can ignore it (default: COMM) 132 | ->type('SMS'); // You can ignore it (default: SMS) 133 | } 134 | } 135 | ``` 136 | 137 | ```php 138 | use App\User; 139 | use App\Notifications\SendPurchaseReceipt; 140 | 141 | User::find(1)->notify(new SendPurchaseReceipt); 142 | ``` 143 | 144 | ##### 2) Request to send MMS 145 | 146 | ```bash 147 | php artisan make:notification SendPurchaseInvoice 148 | ``` 149 | 150 | ```php 151 | image = $image; 176 | } 177 | 178 | /** 179 | * Get the notification's delivery channels. 180 | * 181 | * @param mixed $notifiable 182 | * @return array 183 | */ 184 | public function via($notifiable) 185 | { 186 | return [SmsChannel::class]; 187 | } 188 | 189 | /** 190 | * Get the sens sms representation of the notification. 191 | * 192 | * @param mixed $notifiable 193 | * @return SmsMessage 194 | * @throws FileNotFoundException 195 | */ 196 | public function toSms($notifiable) 197 | { 198 | return (new SmsMessage) 199 | ->type('MMS') 200 | ->to($notifiable->phone) 201 | ->from('055-000-0000') 202 | ->content('This is your invoice.\nCheck out the attached image.') 203 | /* file's path string or UploadedFile object of Illuminate are allowed */ 204 | ->file('filename.jpg', $this->image); 205 | } 206 | } 207 | ``` 208 | 209 | ```php 210 | notify(new SendPurchaseReceipt(request()->file('image'))); 218 | ``` 219 | 220 | 221 | Now `User id: 1` which has own phone attribute would receive a sms or mms message soon. 222 | 223 | ##### 3) Request send AlimTalk 224 | 225 | ```php 226 | templateCode('TEMPLATE001') // required 260 | ->to($notifiable->phone) // required 261 | ->content('Evans, Your order is shipped.') //required 262 | ->countryCode('82') // optional 263 | ->addButton(['type' => 'DS', 'name' => 'Tracking of Shipment']) // optional 264 | ->setReserved('2020-05-31 14:20', 'Asia/Seoul'); // optional 265 | } 266 | } 267 | ``` 268 | 269 | ## Features 270 | 271 | - SMS(LMS) and MMS 272 | - Kakao Alimtalk 273 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "seungmun/laravel-sens", 3 | "description": "NCloud SENS notification channels for Lovely Laravel.", 4 | "keywords": [ 5 | "laravel", 6 | "laravel-sms", 7 | "ncloud", 8 | "sens" 9 | ], 10 | "license": "MIT", 11 | "homepage": "https://open.kakao.com/o/g3dWlf0", 12 | "support": { 13 | "issues": "https://github.com/seungmun/laravel-sens/issues", 14 | "source": "https://github.com/seungmun/laravel-sens" 15 | }, 16 | "authors": [ 17 | { 18 | "name": "Seungmun Jeong", 19 | "email": "seungmunj@gmail.com" 20 | } 21 | ], 22 | "require": { 23 | "php": "^7.2|^8.0", 24 | "ext-json": "*", 25 | "guzzlehttp/guzzle": "^6.0|^7.0|^7.2", 26 | "illuminate/support": "^6.0|^7.0|^8.0|^9.0", 27 | "illuminate/notifications": "~5.8.0|^6.0|^7.0|^8.0|^9.0" 28 | }, 29 | "require-dev": { 30 | "mockery/mockery": "^1.0", 31 | "phpunit/phpunit": "^7.0|^8.0|^9.0" 32 | }, 33 | "autoload": { 34 | "psr-4": { 35 | "Seungmun\\Sens\\": "src/" 36 | } 37 | }, 38 | "autoload-dev": { 39 | "psr-4": { 40 | "Seungmun\\Sens\\Tests\\": "tests/" 41 | } 42 | }, 43 | "extra": { 44 | "laravel": { 45 | "providers": [ 46 | "Seungmun\\Sens\\SensServiceProvider" 47 | ] 48 | } 49 | }, 50 | "config": { 51 | "sort-packages": true 52 | }, 53 | "minimum-stability": "dev", 54 | "prefer-stable": true 55 | } 56 | -------------------------------------------------------------------------------- /config/laravel-sens.php: -------------------------------------------------------------------------------- 1 | env('SENS_SERVICE_ID', ''), 14 | 15 | /* 16 | |-------------------------------------------------------------------------- 17 | | NCLOUD SENS Service ID for AlimTalk 18 | |-------------------------------------------------------------------------- 19 | | 20 | | Service ID used to authenticate the SENS AlimTalk api request. 21 | | SMS service ID is not same with this AlimTalk service ID. 22 | */ 23 | 'alimtalk_service_id' => env('SENS_ALIMTALK_SERVICE_ID', ''), 24 | 'plus_friend_id' => env('SENS_PlUS_FRIEND_ID', '@id'), 25 | 26 | /* 27 | |-------------------------------------------------------------------------- 28 | | NCLOUD SENS Access Key 29 | |-------------------------------------------------------------------------- 30 | | 31 | | Access key used to authenticate the SENS api request. 32 | | 33 | */ 34 | 'access_key' => env('SENS_ACCESS_KEY', ''), 35 | 36 | /* 37 | |-------------------------------------------------------------------------- 38 | | NCLOUD SENS Secret Key 39 | |-------------------------------------------------------------------------- 40 | | 41 | | Secret key used to authenticate the SENS api request. 42 | | 43 | */ 44 | 'secret_key' => env('SENS_SECRET_KEY', ''), 45 | 46 | ]; 47 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | ./tests 18 | 19 | 20 | 21 | 22 | ./src 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/AlimTalk/AlimTalk.php: -------------------------------------------------------------------------------- 1 | setServiceId($config['alimtalk_service_id']); 19 | } 20 | 21 | /** 22 | * @param array $params 23 | * @return void 24 | * 25 | * @throws \Seungmun\Sens\Exceptions\SensException 26 | */ 27 | public function send(array $params) 28 | { 29 | if (! $this->assertValidTokens()) { 30 | throw SensException::InvalidNCPTokens('NCP tokens are invalid.'); 31 | } 32 | 33 | $uri = '{method} https://sens.apigw.ntruss.com/alimtalk/v2/services/{service}/messages'; 34 | 35 | $endpoint = $this->resolveEndpoint($uri, [ 36 | 'method' => 'POST', 37 | 'service' => $this->getServiceId(), 38 | ]); 39 | 40 | try { 41 | $this->httpClient()->post($endpoint['url'], [ 42 | 'headers' => $this->prepareRequestHeaders( 43 | $endpoint['method'], 44 | $endpoint['path'] 45 | ), 46 | 'body' => json_encode($params), 47 | ]); 48 | } catch (Exception $e) { 49 | throw new SensException($e); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/AlimTalk/AlimTalkChannel.php: -------------------------------------------------------------------------------- 1 | alimtalk = $sens; 24 | } 25 | 26 | /** 27 | * Send the specified SENS notification. 28 | * 29 | * @param mixed $notifiable 30 | * @param \Illuminate\Notifications\Notification $notification 31 | * @return void 32 | * @throws \Seungmun\Sens\Exceptions\SensException 33 | */ 34 | public function send($notifiable, Notification $notification) 35 | { 36 | /** @var \Seungmun\Sens\Sms\SmsMessage $message */ 37 | $message = $notification->{'toAlimTalk'}($notifiable); 38 | 39 | $this->alimtalk->send($message->toArray()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/AlimTalk/AlimTalkMessage.php: -------------------------------------------------------------------------------- 1 | plusFriendId = $friendId ? $friendId : config('laravel-sens.plus_friend_id'); 46 | } 47 | 48 | /** 49 | * @param string $countryCode 50 | * @return $this 51 | */ 52 | public function countryCode(string $countryCode) 53 | { 54 | $this->countryCode = $countryCode; 55 | 56 | return $this; 57 | } 58 | 59 | /** 60 | * @param string $to 61 | * @return $this 62 | */ 63 | public function to(string $to) 64 | { 65 | $this->to = $to; 66 | 67 | return $this; 68 | } 69 | 70 | /** 71 | * @param string $content 72 | * @return $this 73 | */ 74 | public function content(string $content) 75 | { 76 | $this->content = $content; 77 | 78 | return $this; 79 | } 80 | 81 | /** 82 | * @param array $button 83 | * @return $this 84 | */ 85 | public function addButton(array $button) 86 | { 87 | $this->buttons[] = $button; 88 | 89 | return $this; 90 | } 91 | 92 | /** 93 | * @param string $reserveTime 94 | * @param string $reserveTimeZone 95 | * @return \Seungmun\Sens\AlimTalk\AlimTalkMessage 96 | */ 97 | public function setReserved($reserveTime, $reserveTimeZone = 'Asia/Seoul') 98 | { 99 | $this->reserveTime = $reserveTime; 100 | $this->reserveTimeZone = $reserveTimeZone; 101 | 102 | return $this; 103 | } 104 | 105 | /** 106 | * @param string $code 107 | * @return $this 108 | */ 109 | public function setSchedule(string $code) 110 | { 111 | if ($this->scheduleCode) { 112 | $this->scheduleCode = $code; 113 | } 114 | 115 | return $this; 116 | } 117 | 118 | /** 119 | * @param string $id 120 | * @return $this 121 | */ 122 | public function plusFriendId(string $id) 123 | { 124 | $this->plusFriendId = $id; 125 | 126 | return $this; 127 | } 128 | 129 | /** 130 | * @param string $code 131 | * @return $this 132 | */ 133 | public function templateCode(string $code) 134 | { 135 | $this->templateCode = $code; 136 | 137 | return $this; 138 | } 139 | 140 | /** 141 | * @return array 142 | */ 143 | public function toArray() 144 | { 145 | $buffer = [ 146 | 'plusFriendId' => $this->plusFriendId, 147 | 'templateCode' => $this->templateCode, 148 | 'scheduleCode' => $this->scheduleCode, 149 | ]; 150 | 151 | if ($this->reserveTime) { 152 | $buffer['reserveTime'] = $this->reserveTime; 153 | } 154 | 155 | if ($this->reserveTimeZone) { 156 | $buffer['reserveTimeZone'] = $this->reserveTimeZone; 157 | } 158 | 159 | $message = [ 160 | 'countryCode' => $this->countryCode, 161 | 'to' => $this->to, 162 | 'content' => $this->content, 163 | ]; 164 | 165 | if (count($this->buttons)) { 166 | $message['buttons'] = $this->buttons; 167 | } 168 | 169 | $buffer['messages'][] = $message; 170 | 171 | return $buffer; 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/AlimTalk/AlimTalkRequest.php: -------------------------------------------------------------------------------- 1 | mappingParams($params); 34 | } 35 | 36 | /** 37 | * @param array $params 38 | */ 39 | protected function mappingParams(array $params) 40 | { 41 | $attributes = ['plusFriendId', 'templateCode', 'messages', 'reserveTime', 'reserveTimeZone', 'scheduleCode']; 42 | 43 | foreach ($attributes as $attribute) { 44 | $val = Arr::get($params, $attribute); 45 | 46 | if ($val) { 47 | $this->{$attribute} = $val; 48 | } 49 | } 50 | } 51 | 52 | /** 53 | * @return \Illuminate\Contracts\Validation\Validator 54 | */ 55 | public function validator() 56 | { 57 | return Validator::make($this->toArray(), [ 58 | 'plusFriendId' => 'required', 59 | 'templateCode' => 'required', 60 | 'messages' => 'required|array', 61 | 'reserveTime' => 'nullable', 62 | 'reserveTimeZone' => 'nullable', 63 | 'scheduleCode' => 'nullable', 64 | ]); 65 | } 66 | 67 | /** 68 | * @param \Seungmun\Sens\AlimTalk\AlimTalkMessage $message 69 | * @return $this 70 | */ 71 | public function addMessage(AlimTalkMessage $message) 72 | { 73 | $this->messages[] = $message; 74 | 75 | return $this; 76 | } 77 | 78 | /** 79 | * @return array 80 | */ 81 | public function toArray() 82 | { 83 | $buffer = [ 84 | 'plusFriendId' => $this->plusFriendId, 85 | 'templateCode' => $this->templateCode, 86 | 'messages' => array_map( 87 | function (AlimTalkMessage $message) { 88 | return $message->toArray(); 89 | }, 90 | $this->messages 91 | ), 92 | ]; 93 | 94 | if ($this->reserveTime) { 95 | $buffer['reserveTime'] = $this->reserveTime; 96 | } 97 | 98 | if ($this->reserveTimeZone) { 99 | $buffer['reserveTimeZone'] = $this->reserveTimeZone; 100 | } 101 | 102 | if ($this->scheduleCode) { 103 | $buffer['scheduleCode'] = $this->scheduleCode; 104 | } 105 | 106 | return $buffer; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Contracts/Sens.php: -------------------------------------------------------------------------------- 1 | httpClient(); 36 | 37 | $this->setServiceId($config['service_id']) 38 | ->setAccessKey($config['access_key']) 39 | ->setSecretKey($config['secret_key']); 40 | 41 | $this->config = $config; 42 | } 43 | 44 | /** 45 | * Create a new HTTP Request Client. 46 | * 47 | * @return \GuzzleHttp\Client 48 | */ 49 | protected function httpClient() 50 | { 51 | return $this->http ?: $this->http = new Client(); 52 | } 53 | 54 | /** 55 | * Determine if tokens are exists normally. 56 | * 57 | * @return bool 58 | */ 59 | public function assertValidTokens() 60 | { 61 | return ! empty($this->getServiceId()) && 62 | ! empty($this->getAccessKey()) && 63 | ! empty($this->getSecretKey()); 64 | } 65 | 66 | /** 67 | * Get SENS service identifier. 68 | * 69 | * @return string 70 | */ 71 | public function getServiceId() 72 | { 73 | return $this->serviceId; 74 | } 75 | 76 | /** 77 | * Set SENS service identifier. 78 | * 79 | * @param string $serviceId 80 | * @return \Seungmun\Sens\Sens 81 | */ 82 | public function setServiceId(string $serviceId) 83 | { 84 | $this->serviceId = $serviceId; 85 | 86 | return $this; 87 | } 88 | 89 | /** 90 | * Get SENS access key. 91 | * 92 | * @return string 93 | */ 94 | public function getAccessKey() 95 | { 96 | return $this->accessKey; 97 | } 98 | 99 | /** 100 | * Set SENS access key. 101 | * 102 | * @param string $accessKey 103 | * @return \Seungmun\Sens\Sens 104 | */ 105 | public function setAccessKey(string $accessKey) 106 | { 107 | $this->accessKey = $accessKey; 108 | 109 | return $this; 110 | } 111 | 112 | /** 113 | * Get SENS secret key. 114 | * 115 | * @return string 116 | */ 117 | public function getSecretKey() 118 | { 119 | return $this->secretKey; 120 | } 121 | 122 | /** 123 | * Set SENS secret key. 124 | * 125 | * @param string $secretKey 126 | * @return \Seungmun\Sens\Sens 127 | */ 128 | public function setSecretKey(string $secretKey) 129 | { 130 | $this->secretKey = $secretKey; 131 | 132 | return $this; 133 | } 134 | 135 | /** 136 | * Resolve the given uri to http request url. 137 | * 138 | * @param string $uri 139 | * @param array $params 140 | * @return array 141 | */ 142 | public function resolveEndpoint($uri, $params) 143 | { 144 | foreach ($params as $key => $value) { 145 | $uri = str_replace('{' . $key . '}', $value, $uri); 146 | } 147 | 148 | $tokens = explode(' ', $uri); 149 | 150 | return [ 151 | 'method' => $tokens[0], 152 | 'url' => $tokens[1], 153 | 'path' => parse_url($tokens[1], PHP_URL_PATH), 154 | 'host' => parse_url($tokens[1], PHP_URL_HOST), 155 | ]; 156 | } 157 | 158 | /** 159 | * Prepare HTTP headers for request NCLOUD API v2 authentication. 160 | * 161 | * @param string $method 162 | * @param string $uri 163 | * @return array 164 | */ 165 | public function prepareRequestHeaders($method, $uri) 166 | { 167 | $timestamp = $this->timestamp(); 168 | 169 | $this->addHeader('Content-Type', 'application/json; charset=utf-8'); 170 | $this->addHeader(self::X_NCP_APIGW_TIMESTAMP, $timestamp); 171 | $this->addHeader(self::X_NCP_IAM_ACCESS_KEY, $this->getAccessKey()); 172 | $this->addHeader(self::X_NCP_APIGW_SIGNATURE_V2, $this->makeSignature($method, $uri, $timestamp)); 173 | 174 | return $this->headers(); 175 | } 176 | 177 | /** 178 | * Get current timestamp to compare api server. 179 | * 180 | * @return string 181 | */ 182 | protected function timestamp() 183 | { 184 | return strval((int)round(microtime(true) * 1000)); 185 | } 186 | 187 | /** 188 | * Add a new HTTP header attribute. 189 | * 190 | * @param string $key 191 | * @param string $value 192 | * @return $this 193 | */ 194 | public function addHeader(string $key, string $value) 195 | { 196 | $this->headers[$key] = $value; 197 | 198 | return $this; 199 | } 200 | 201 | /** 202 | * generate x-ncp-apigw-signature-v2 token for authentication. 203 | * 204 | * @param string $method 205 | * @param string $uri 206 | * @param string $timestamp 207 | * @return string 208 | */ 209 | public function makeSignature($method, $uri, $timestamp) 210 | { 211 | $buffer = []; 212 | 213 | // Important - do not change these all lines down here ever! 214 | array_push($buffer, strtoupper($method) . " " . $uri); 215 | array_push($buffer, $timestamp); 216 | array_push($buffer, $this->getAccessKey()); 217 | 218 | $secretKey = utf8_encode($this->getSecretKey()); 219 | $message = utf8_encode(implode("\n", $buffer)); 220 | $hash = hex2bin(hash_hmac('sha256', $message, $secretKey)); 221 | 222 | return base64_encode($hash); 223 | } 224 | 225 | /** 226 | * HTTP Header Attributes 227 | * 228 | * @return array 229 | */ 230 | public function headers() 231 | { 232 | return $this->headers; 233 | } 234 | 235 | /** 236 | * Remove the given HTTP header. 237 | * 238 | * @param string $key 239 | * @return \Seungmun\Sens\Sens 240 | */ 241 | public function removeHeader(string $key) 242 | { 243 | unset($this->headers[$key]); 244 | 245 | return $this; 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/SensServiceProvider.php: -------------------------------------------------------------------------------- 1 | mergeConfigFrom( 21 | __DIR__ . '/../config/laravel-sens.php', 22 | 'laravel-sens' 23 | ); 24 | 25 | // Register SENS SMS service. 26 | $this->app->when(SmsChannel::class) 27 | ->needs(Sms::class) 28 | ->give(function ($app) { 29 | return new Sms($app['config']->get('laravel-sens')); 30 | }); 31 | 32 | // Register SENS AlimTalk service. 33 | $this->app->when(AlimTalkChannel::class) 34 | ->needs(AlimTalk::class) 35 | ->give(function ($app) { 36 | return new AlimTalk($app['config']->get('laravel-sens')); 37 | }); 38 | } 39 | 40 | /** 41 | * Bootstrap any application services. 42 | * 43 | * @return void 44 | */ 45 | public function boot() 46 | { 47 | $this->publishes([ 48 | __DIR__ . '/../config/laravel-sens.php' => config_path('laravel-sens.php') 49 | ], 'config'); 50 | } 51 | 52 | /** 53 | * Get the services provided by the provider. 54 | * 55 | * @return array 56 | */ 57 | public function provides() 58 | { 59 | return []; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Sms/Sms.php: -------------------------------------------------------------------------------- 1 | assertValidTokens()) { 21 | throw SensException::InvalidNCPTokens('NCP tokens are invalid.'); 22 | } 23 | 24 | try { 25 | $uri = '{method} https://sens.apigw.ntruss.com/sms/v2/services/{service}/messages'; 26 | 27 | $endpoint = $this->resolveEndpoint($uri, [ 28 | 'method' => 'POST', 29 | 'service' => $this->getServiceId(), 30 | ]); 31 | 32 | $this->httpClient()->post($endpoint['url'], [ 33 | 'headers' => $this->prepareRequestHeaders( 34 | $endpoint['method'], 35 | $endpoint['path'] 36 | ), 37 | 'body' => json_encode($params), 38 | ]); 39 | } catch (Exception $e) { 40 | throw new SensException($e); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Sms/SmsChannel.php: -------------------------------------------------------------------------------- 1 | sms = $sens; 24 | } 25 | 26 | /** 27 | * Send the specified SENS notification. 28 | * 29 | * @param mixed $notifiable 30 | * @param \Illuminate\Notifications\Notification $notification 31 | * @return void 32 | * @throws \Seungmun\Sens\Exceptions\SensException 33 | */ 34 | public function send($notifiable, Notification $notification) 35 | { 36 | /** @var \Seungmun\Sens\Sms\SmsMessage $message */ 37 | $message = $notification->{'toSms'}($notifiable); 38 | 39 | $this->sms->send($message->toArray()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Sms/SmsMessage.php: -------------------------------------------------------------------------------- 1 | from = config('services.sens.services.sms.sender'); 42 | } 43 | 44 | /** 45 | * Set SMS Type (ex: SMS, LMS) 46 | * 47 | * @param string $type 48 | * @return $this 49 | */ 50 | public function type(string $type) 51 | { 52 | $this->type = strtoupper($type); 53 | 54 | return $this; 55 | } 56 | 57 | /** 58 | * Set SMS Content Type (ex: COMM / AD) 59 | * 60 | * @param string $contentType 61 | * @return $this 62 | */ 63 | public function contentType(string $contentType) 64 | { 65 | $this->contentType = strtoupper($contentType); 66 | 67 | return $this; 68 | } 69 | 70 | /** 71 | * Set Country Code. 72 | * 73 | * @param int $countryCode 74 | * @return $this 75 | */ 76 | public function countryCode(int $countryCode) 77 | { 78 | $this->countryCode = $countryCode; 79 | 80 | return $this; 81 | } 82 | 83 | /** 84 | * Set Sender's tel number. 85 | * 86 | * @param string $from 87 | * @return $this 88 | */ 89 | public function from(string $from) 90 | { 91 | $this->from = str_replace('-', '', $from); 92 | 93 | return $this; 94 | } 95 | 96 | /** 97 | * Set title only for LMS. 98 | * 99 | * @param string $subject 100 | * @return $this 101 | */ 102 | public function subject(string $subject) 103 | { 104 | $this->subject = $subject; 105 | 106 | return $this; 107 | } 108 | 109 | /** 110 | * Set SMS Contents. (SMS: 80byte, LMS: 2000byte) 111 | * 112 | * @param string $content 113 | * @return $this 114 | */ 115 | public function content(string $content) 116 | { 117 | $this->content = $content; 118 | 119 | return $this; 120 | } 121 | 122 | /** 123 | * Set Recipient's number. 124 | * 125 | * @param string $to 126 | * @return $this 127 | */ 128 | public function to(string $to) 129 | { 130 | array_push($this->messages, [ 131 | 'to' => str_replace('-', '', $to), 132 | ]); 133 | 134 | return $this; 135 | } 136 | 137 | /** 138 | * Add a new file into files for MMS message. 139 | * 140 | * @param string $name 141 | * @param mixed $file 142 | * @return $this 143 | * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException 144 | */ 145 | public function file(string $name, $file) 146 | { 147 | $body = null; 148 | 149 | if ($file instanceof \Illuminate\Http\UploadedFile) { 150 | /** @var \Illuminate\Http\UploadedFile $file */ 151 | $body = base64_encode($file->get()); 152 | } elseif (is_string($file)) { 153 | $body = base64_encode(file_get_contents($file)); 154 | } else { 155 | throw new FileNotFoundException(); 156 | } 157 | 158 | array_push($this->files, [ 159 | 'name' => $name, 160 | 'body' => $body, 161 | ]); 162 | 163 | return $this; 164 | } 165 | 166 | /** 167 | * Serialize to Array. 168 | * 169 | * @return array 170 | */ 171 | public function toArray() 172 | { 173 | $resource = [ 174 | 'type' => $this->type, 175 | 'contentType' => $this->contentType, 176 | 'countryCode' => strval($this->countryCode), 177 | 'from' => $this->from, 178 | 'subject' => $this->subject, 179 | 'content' => $this->content, 180 | 'messages' => $this->messages, 181 | ]; 182 | 183 | if (! empty($this->files)) { 184 | $resource['files'] = $this->files; 185 | } 186 | 187 | return $resource; 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /tests/SensSmsChannelTest.php: -------------------------------------------------------------------------------- 1 | guzzleHttp = m::mock(Client::class); 24 | } 25 | 26 | /** 27 | * @return void 28 | */ 29 | public function testIamSorryBecauseTestsAreSoNuisance() 30 | { 31 | $this->assertTrue(true); 32 | } 33 | 34 | /** 35 | * @return void 36 | */ 37 | protected function tearDown(): void 38 | { 39 | m::close(); 40 | } 41 | } 42 | --------------------------------------------------------------------------------