Initial commit
This commit is contained in:
		
						commit
						7f4b49d98d
					
				
							
								
								
									
										15
									
								
								.editorconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								.editorconfig
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| ; This file is for unifying the coding style for different editors and IDEs. | ||||
| ; More information at http://editorconfig.org | ||||
| 
 | ||||
| root = true | ||||
| 
 | ||||
| [*] | ||||
| charset = utf-8 | ||||
| indent_size = 4 | ||||
| indent_style = space | ||||
| end_of_line = lf | ||||
| insert_final_newline = true | ||||
| trim_trailing_whitespace = true | ||||
| 
 | ||||
| [*.md] | ||||
| trim_trailing_whitespace = false | ||||
							
								
								
									
										11
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | ||||
| # Path-based git attributes | ||||
| # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html | ||||
| 
 | ||||
| # Ignore all test and documentation with "export-ignore". | ||||
| /.gitattributes     export-ignore | ||||
| /.gitignore         export-ignore | ||||
| /.travis.yml        export-ignore | ||||
| /phpunit.xml.dist   export-ignore | ||||
| /.scrutinizer.yml   export-ignore | ||||
| /tests              export-ignore | ||||
| /.editorconfig      export-ignore | ||||
							
								
								
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| build | ||||
| composer.lock | ||||
| vendor | ||||
| coverage.clover | ||||
| .phpunit.result.cache | ||||
							
								
								
									
										18
									
								
								.scrutinizer.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								.scrutinizer.yml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| filter: | ||||
|     excluded_paths: [tests/*] | ||||
| 
 | ||||
| checks: | ||||
|     php: | ||||
|         remove_extra_empty_lines: true | ||||
|         remove_php_closing_tag: true | ||||
|         remove_trailing_whitespace: true | ||||
|         fix_use_statements: | ||||
|             remove_unused: true | ||||
|             preserve_multiple: false | ||||
|             preserve_blanklines: true | ||||
|             order_alphabetically: true | ||||
|         fix_php_opening_tag: true | ||||
|         fix_linefeed: true | ||||
|         fix_line_ending: true | ||||
|         fix_identation_4spaces: true | ||||
|         fix_doc_comments: true | ||||
							
								
								
									
										4
									
								
								.styleci.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.styleci.yml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| preset: laravel | ||||
| 
 | ||||
| disabled: | ||||
|   - single_class_element_per_statement | ||||
							
								
								
									
										35
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | ||||
| language: php | ||||
| 
 | ||||
| cache: | ||||
|     directories: | ||||
|         - $HOME/.composer/cache | ||||
| 
 | ||||
| matrix: | ||||
|     fast_finish: true | ||||
|     include: | ||||
|         - php: 7.2 | ||||
|           env: LARAVEL='5.8.*' TESTBENCH='3.8.*' COMPOSER_FLAGS='--prefer-lowest' | ||||
|         - php: 7.2 | ||||
|           env: LARAVEL='5.8.*' TESTBENCH='3.8.*' COMPOSER_FLAGS='--prefer-stable' | ||||
|         - php: 7.3 | ||||
|           env: LARAVEL='5.8.*' TESTBENCH='3.8.*' COMPOSER_FLAGS='--prefer-lowest' | ||||
|         - php: 7.3 | ||||
|           env: LARAVEL='5.8.*' TESTBENCH='3.8.*' COMPOSER_FLAGS='--prefer-stable' | ||||
|         - php: 7.2 | ||||
|           env: LARAVEL='6.*' TESTBENCH='4.*' COMPOSER_FLAGS='--prefer-lowest' | ||||
|         - php: 7.2 | ||||
|           env: LARAVEL='6.*' TESTBENCH='4.*' COMPOSER_FLAGS='--prefer-stable' | ||||
|         - php: 7.3 | ||||
|           env: LARAVEL='6.*' TESTBENCH='4.*' COMPOSER_FLAGS='--prefer-lowest' | ||||
|         - php: 7.3 | ||||
|           env: LARAVEL='6.*' TESTBENCH='4.*' COMPOSER_FLAGS='--prefer-stable' | ||||
| 
 | ||||
| before_install: | ||||
|     - travis_retry composer self-update | ||||
|     - travis_retry composer require --no-update --no-interaction "illuminate/support:${LARAVEL}" "orchestra/testbench:${TESTBENCH}" | ||||
| 
 | ||||
| install: | ||||
|     - travis_retry composer update ${COMPOSER_FLAGS} --prefer-dist --no-interaction --no-suggest | ||||
| 
 | ||||
| script: | ||||
|     - vendor/bin/phpunit | ||||
							
								
								
									
										0
									
								
								CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										0
									
								
								CONTRIBUTING.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								CONTRIBUTING.md
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										21
									
								
								LICENSE.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								LICENSE.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| The MIT License (MIT) | ||||
| 
 | ||||
| Copyright (c) App-vise V.O.F. <info@app-vise.nl> | ||||
| 
 | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
| 
 | ||||
| The above copyright notice and this permission notice shall be included in | ||||
| all copies or substantial portions of the Software. | ||||
| 
 | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
| THE SOFTWARE. | ||||
							
								
								
									
										47
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | ||||
| # Handle Appstore server-to-server notifications for auto-renewable subscriptions | ||||
| 
 | ||||
| [](https://packagist.org/packages/app-vise/laravel-appstore-notifications) | ||||
| [](https://travis-ci.org/app-vise/laravel-appstore-notifications) | ||||
| [](https://styleci.io/repos/105920179) | ||||
| [](https://scrutinizer-ci.com/g/app-vise/laravel-appstore-notifications) | ||||
| [](https://packagist.org/packages/spatie/laravel-appstore-notifications) | ||||
| 
 | ||||
| ## Installation | ||||
| You can install this package via composer | ||||
| 
 | ||||
| ```bash | ||||
| composer require app-vise/laravel-appstore-server-notifications  | ||||
|  ``` | ||||
| 
 | ||||
| The service provider will register itself. | ||||
| You have to publish the config file with: | ||||
| 
 | ||||
| ```bash | ||||
| php artisan vendor:publish --provider="Appvise\AppStoreNotifications\NotificationsServiceProvider" --tag="config"  | ||||
|  ``` | ||||
| 
 | ||||
| ## Usage | ||||
| 
 | ||||
| ## Changelog | ||||
| 
 | ||||
| Please see CHANGELOG for more information about what has changed recently. | ||||
| 
 | ||||
| ## Testing | ||||
| 
 | ||||
| ```bash | ||||
| composer test | ||||
| ``` | ||||
| 
 | ||||
| ## Security | ||||
| 
 | ||||
| If you discover any security related issues, please email daan@app-vise.nl instead of using the issue tracker. | ||||
| 
 | ||||
| ## Credits | ||||
| 
 | ||||
| - [Daan Geurts](https://github.com/DaanGeurts) | ||||
| - [All Contributors](../../contributors) | ||||
| 
 | ||||
| A big thanks to [Spatie's](https://spatie.be) laravel-stripe-webhooks which was a huge inspiration and starting point for this package | ||||
| ## License | ||||
| 
 | ||||
| The MIT License (MIT). Please see [License File](LICENSE.md) for more information. | ||||
							
								
								
									
										0
									
								
								UPGRADING.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								UPGRADING.md
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										53
									
								
								composer.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								composer.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,53 @@ | ||||
| { | ||||
|     "name": "app-vise/laravel-appstore-server-notifications", | ||||
|     "description": "Handling Appstore server to server notifications", | ||||
|     "keywords": [ | ||||
|         "app-vise", | ||||
|         "appvise", | ||||
|         "laravel-appstore-server-to-server-notifications", | ||||
|         "laravel-appstore-server-notifications", | ||||
|         "laravel in app subscriptions", | ||||
|         "laravel-appstore-server-notifications" | ||||
|     ], | ||||
|     "license": "MIT", | ||||
|     "authors": [ | ||||
|         { | ||||
|             "name": "Daan Geurts", | ||||
|             "email": "daan@app-vise.nl", | ||||
|             "homepage": "https://www.app-vise.nl", | ||||
|             "role": "Developer" | ||||
|         } | ||||
|     ], | ||||
|     "require": { | ||||
|         "php": "^7.2", | ||||
|         "illuminate/support": "~5.7.0|^6.0", | ||||
|         "bensampo/laravel-enum": "^1.0" | ||||
|     }, | ||||
|     "require-dev": { | ||||
|         "orchestra/testbench": "~3.8.0|^4.0", | ||||
|         "phpunit/phpunit": "^8.2" | ||||
|     }, | ||||
|     "autoload": { | ||||
|         "psr-4": { | ||||
|             "Appvise\\AppStoreNotifications\\": "src" | ||||
|         } | ||||
|     }, | ||||
|     "autoload-dev": { | ||||
|         "psr-4": { | ||||
|             "Appvise\\AppStoreNotifications\\Tests\\": "tests" | ||||
|         } | ||||
|     }, | ||||
|     "scripts": { | ||||
|         "test": "vendor/bin/phpunit --verbose" | ||||
|     }, | ||||
|     "config": { | ||||
|         "sort-packages": true | ||||
|     }, | ||||
|     "extra": { | ||||
|         "laravel": { | ||||
|             "providers": [ | ||||
|                 "Appvise\\AppStoreNotifications\\NotificationsServiceProvider" | ||||
|             ] | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										24
									
								
								config/appstore-server-notifications.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								config/appstore-server-notifications.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | ||||
| <?php | ||||
| return [ | ||||
|     /* | ||||
|      * Apple will send the shared secret with the request that should match | ||||
|      * the one you use when validating receipts. | ||||
|      * https://developer.apple.com/documentation/storekit/in-app_purchase/enabling_server-to-server_notifications?language=objc#overview
 | ||||
|      */ | ||||
|     'shared_secret' => env('APPLE_SHARED_SECRET'), | ||||
|     /* | ||||
|      * All the events that should be handeled by your application. | ||||
|      * Typically you should uncomment all jobs | ||||
|      * | ||||
|      * You can find a list of all notification types here: | ||||
|      * https://developer.apple.com/documentation/storekit/in-app_purchase/enabling_server-to-server_notifications?language=objc#3162176
 | ||||
|      */ | ||||
|     'jobs' => [ | ||||
|         // 'initial_buy' => \App\Jobs\AppstoreNotifications\HandleInitialBuy::class,
 | ||||
|         // 'cancel' => \App\Jobs\AppstoreNotifications\HandleCancellation::class,
 | ||||
|         // 'renewal' => \App\Jobs\AppstoreNotifications\HandleRenewal::class,
 | ||||
|         // 'interactive_renewal' => \App\Jobs\AppstoreNotifications\HandleInteractiveRenewal::class,
 | ||||
|         // 'did_change_renewal_pref' => \App\Jobs\AppstoreNotifications\HandleDidChangeRenewalPreferences::class,
 | ||||
|         // 'did_change_renewal_status' => \App\Jobs\AppstoreNotifications\HandleDidChangeRenewalStatus::class,
 | ||||
|     ], | ||||
| ]; | ||||
| @ -0,0 +1,25 @@ | ||||
| <?php | ||||
| 
 | ||||
| use Illuminate\Database\Schema\Blueprint; | ||||
| use Illuminate\Database\Migrations\Migration; | ||||
| 
 | ||||
| class CreateAppleNotificationsTable extends Migration | ||||
| { | ||||
|     public function up() | ||||
|     { | ||||
|         Schema::create('apple_notifications', function (Blueprint $table) { | ||||
|             $table->bigIncrements('id'); | ||||
| 
 | ||||
|             $table->string('type'); | ||||
|             $table->text('payload')->nullable(); | ||||
|             $table->text('exception')->nullable(); | ||||
| 
 | ||||
|             $table->timestamps(); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     public function down() | ||||
|     { | ||||
|         Schema::dropIfExists('apple_notifications'); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										29
									
								
								phpunit.xml.dist
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								phpunit.xml.dist
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <phpunit bootstrap="vendor/autoload.php" | ||||
|          backupGlobals="false" | ||||
|          backupStaticAttributes="false" | ||||
|          colors="true" | ||||
|          verbose="true" | ||||
|          convertErrorsToExceptions="true" | ||||
|          convertNoticesToExceptions="true" | ||||
|          convertWarningsToExceptions="true" | ||||
|          processIsolation="true" | ||||
|          stopOnFailure="false"> | ||||
|     <testsuites> | ||||
|         <testsuite name="App-vise Test Suite"> | ||||
|             <directory>tests</directory> | ||||
|         </testsuite> | ||||
|     </testsuites> | ||||
|     <filter> | ||||
|         <whitelist> | ||||
|             <directory suffix=".php">src/</directory> | ||||
|         </whitelist> | ||||
|     </filter> | ||||
|     <logging> | ||||
|         <log type="tap" target="build/report.tap"/> | ||||
|         <log type="junit" target="build/report.junit.xml"/> | ||||
|         <log type="coverage-html" target="build/coverage"/> | ||||
|         <log type="coverage-text" target="build/coverage.txt"/> | ||||
|         <log type="coverage-clover" target="build/logs/clover.xml"/> | ||||
|     </logging> | ||||
| </phpunit> | ||||
							
								
								
									
										30
									
								
								src/NotificationsServiceProvider.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/NotificationsServiceProvider.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| <?php | ||||
| namespace Appvise\AppStoreNotifications; | ||||
| 
 | ||||
| use Illuminate\Support\ServiceProvider; | ||||
| 
 | ||||
| class NotificationsServiceProvider extends ServiceProvider | ||||
| { | ||||
|     public function boot() | ||||
|     { | ||||
|         if ($this->app->runningInConsole()) { | ||||
|             $this->publishes([ | ||||
|                 __DIR__.'/../config/appstore-server-notifications.php' => config_path('appstore-server-notifications.php'), | ||||
|             ], 'config'); | ||||
|         } | ||||
| 
 | ||||
|         if (! class_exists('CreateAppleNotificationsTable')) { | ||||
|             $timestamp = date('Y_m_d_His', time()); | ||||
|             $this->publishes([ | ||||
|                 __DIR__.'/../database/migrations/create_apple_notifications_table.php.stub' => database_path("migrations/{$timestamp}_create_apple_notifications_table.php"), | ||||
|             ], 'migrations'); | ||||
|         } | ||||
| 
 | ||||
|         $this->loadRoutesFrom(__DIR__.'/routes.php'); | ||||
|     } | ||||
| 
 | ||||
|     public function register() | ||||
|     { | ||||
|         $this->mergeConfigFrom(__DIR__.'/../config/appstore-server-notifications.php', 'appstore-server-notifications'); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										43
									
								
								src/WebhooksController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/WebhooksController.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Appvise\AppStoreNotifications; | ||||
| 
 | ||||
| use Appvise\AppStoreNotifications\Exceptions\WebhookFailed; | ||||
| use Appvise\AppStoreNotifications\Model\AppleNotification; | ||||
| use Appvise\AppStoreNotifications\Model\NotificationPayload; | ||||
| use Appvise\AppStoreNotifications\Model\NotificationType; | ||||
| use Illuminate\Http\Request; | ||||
| 
 | ||||
| class WebhooksController | ||||
| { | ||||
|     public function __invoke(Request $request) | ||||
|     { | ||||
|         $jobConfigKey = NotificationType::{$request->input('notification_type')}(); | ||||
|         $payload = NotificationPayload::createFromRequest($request); | ||||
| 
 | ||||
|         $this->determineValidRequest($payload); | ||||
| 
 | ||||
|         AppleNotification::storeNotification($jobConfigKey, $payload); | ||||
| 
 | ||||
|         $jobClass = config("appstore-server-notifications.jobs.{$jobConfigKey}", null); | ||||
| 
 | ||||
|         if (is_null($jobClass)) { | ||||
|             throw WebhookFailed::jobClassDoesNotExist($jobConfigKey); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         $job = new $jobClass($payload); | ||||
|         dispatch($job); | ||||
| 
 | ||||
|         return response()->json(); | ||||
|     } | ||||
| 
 | ||||
|     private function determineValidRequest(NotificationPayload $notificationPayload): bool | ||||
|     { | ||||
|         if ($notificationPayload->getPassword() !== config('appstore-server-notifications.shared_secret')) { | ||||
|             throw WebhookFailed::nonValidRequest(); | ||||
|         } | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										23
									
								
								src/exceptions/WebhookFailed.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/exceptions/WebhookFailed.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Appvise\AppStoreNotifications\Exceptions; | ||||
| 
 | ||||
| use Exception; | ||||
| 
 | ||||
| class WebhookFailed extends Exception | ||||
| { | ||||
|     public static function nonValidRequest() | ||||
|     { | ||||
|         return new static("Your shared secret does not match password in Apple's request", 400); | ||||
|     } | ||||
| 
 | ||||
|     public static function jobClassDoesNotExist(string $jobClass) | ||||
|     { | ||||
|         return new static("Could not process webhook because the configured job `$jobClass` does not exist.", 501); | ||||
|     } | ||||
| 
 | ||||
|     public function render($request) | ||||
|     { | ||||
|         return response(['error' => $this->getMessage()], 400); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										19
									
								
								src/model/AppleNotification.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/model/AppleNotification.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Appvise\AppStoreNotifications\Model; | ||||
| 
 | ||||
| use Illuminate\Database\Eloquent\Model; | ||||
| 
 | ||||
| class AppleNotification extends Model | ||||
| { | ||||
|     public $guarded = []; | ||||
| 
 | ||||
|     public static function storeNotification(String $notificationType, NotificationPayload $notificationPayload): AppleNotification | ||||
|     { | ||||
|         return self::create([ | ||||
|             'type' => $notificationType, | ||||
|             'payload' => serialize($notificationPayload), | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										193
									
								
								src/model/NotificationPayload.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								src/model/NotificationPayload.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,193 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Appvise\AppStoreNotifications\Model; | ||||
| 
 | ||||
| use Illuminate\Http\Request; | ||||
| 
 | ||||
| class NotificationPayload | ||||
| { | ||||
|     private $environment; | ||||
|     private $notificationType; | ||||
|     private $password; | ||||
|     private $cancellationDate; | ||||
|     private $cancellationDatePst; | ||||
|     private $cancellationDateMs; | ||||
|     private $webOrderLineItemId; | ||||
|     private $latestReceipt; | ||||
|     private $latestReceiptInfo; | ||||
|     private $latestExpiredReceipt; | ||||
|     private $latestExpiredReceiptInfo; | ||||
|     private $autoRenewStatus; | ||||
|     private $autoRenewProductId; | ||||
|     private $autoRenewStatusChangeDate; | ||||
|     private $autoRenewStatusChangeDatePst; | ||||
|     private $autoRenewStatusChangeDateMs; | ||||
|     private $pendingRenewalInfo; | ||||
| 
 | ||||
|     public function __construct() | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     static function createFromRequest(Request $request) | ||||
|     { | ||||
|         $instance = new self(); | ||||
|         $instance->environment = $request->input('environment'); | ||||
|         $instance->password = $request->input('password'); | ||||
|         $instance->notificationType = $request->input('notification_type'); | ||||
|         $instance->cancellationDate = $request->input('cancellation_date'); | ||||
|         $instance->cancellationDatePst = $request->input('cancellation_date_pst'); | ||||
|         $instance->cancellationDateMs = $request->input('cancellation_date_ms'); | ||||
|         $instance->webOrderLineItemId = $request->input('web_order_line_item_id'); | ||||
|         $instance->latestReceipt = $request->input('latest_receipt'); | ||||
|         $instance->latestReceiptInfo = Receipt::createFromArray($request->input('latest_receipt_info')); | ||||
|         $instance->latestExpiredReceipt = $request->input('latest_expired_receipt'); | ||||
|         $instance->latestExpiredReceiptInfo = Receipt::createFromArray($request->input('latest_expired_receipt_info')); | ||||
|         $instance->autoRenewStatus = $request->input('auto_renew_status'); | ||||
|         $instance->autoRenewProductId = $request->input('auto_renew_product_id'); | ||||
|         $instance->autoRenewStatusChangeDate = $request->input('auto_renew_status_change_date'); | ||||
|         $instance->autoRenewStatusChangeDatePst = $request->input('auto_renew_status_change_date_pst'); | ||||
|         $instance->autoRenewStatusChangeDateMs = $request->input('auto_renew_status_change_date_ms'); | ||||
|         foreach ($request->input('pending_renewal_info') as $pendingRenewalInfo) { | ||||
|             $instance->pendingRenewalInfo[] = RenewalInfo::createFromRequest($pendingRenewalInfo); | ||||
|         } | ||||
| 
 | ||||
|         return $instance; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of environment | ||||
|      */ | ||||
|     public function getEnvironment() | ||||
|     { | ||||
|         return $this->environment; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of notificationType | ||||
|      */ | ||||
|     public function getNotificationType() | ||||
|     { | ||||
|         return $this->notificationType; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of pendingRenewalInfo | ||||
|      */ | ||||
|     public function getPendingRenewalInfo() | ||||
|     { | ||||
|         return $this->pendingRenewalInfo; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of autoRenewStatusChangeDateMs | ||||
|      */ | ||||
|     public function getAutoRenewStatusChangeDateMs() | ||||
|     { | ||||
|         return $this->autoRenewStatusChangeDateMs; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of autoRenewStatusChangeDatePst | ||||
|      */ | ||||
|     public function getAutoRenewStatusChangeDatePst() | ||||
|     { | ||||
|         return $this->autoRenewStatusChangeDatePst; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of autoRenewStatusChangeDate | ||||
|      */ | ||||
|     public function getAutoRenewStatusChangeDate() | ||||
|     { | ||||
|         return $this->autoRenewStatusChangeDate; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of autoRenewProductId | ||||
|      */ | ||||
|     public function getAutoRenewProductId() | ||||
|     { | ||||
|         return $this->autoRenewProductId; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of autoRenewStatus | ||||
|      */ | ||||
|     public function getAutoRenewStatus() | ||||
|     { | ||||
|         return $this->autoRenewStatus; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of latestExpiredReceiptInfo | ||||
|      */ | ||||
|     public function getLatestExpiredReceiptInfo() | ||||
|     { | ||||
|         return $this->latestExpiredReceiptInfo; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of latestExpiredReceipt | ||||
|      */ | ||||
|     public function getLatestExpiredReceipt() | ||||
|     { | ||||
|         return $this->latestExpiredReceipt; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of latestReceiptInfo | ||||
|      */ | ||||
|     public function getLatestReceiptInfo() | ||||
|     { | ||||
|         return $this->latestReceiptInfo; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of latestReceipt | ||||
|      */ | ||||
|     public function getLatestReceipt() | ||||
|     { | ||||
|         return $this->latestReceipt; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of webOrderLineItemId | ||||
|      */ | ||||
|     public function getWebOrderLineItemId() | ||||
|     { | ||||
|         return $this->webOrderLineItemId; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of cancellationDateMs | ||||
|      */ | ||||
|     public function getCancellationDateMs() | ||||
|     { | ||||
|         return $this->cancellationDateMs; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of cancellationDatePst | ||||
|      */ | ||||
|     public function getCancellationDatePst() | ||||
|     { | ||||
|         return $this->cancellationDatePst; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of cancellationDate | ||||
|      */ | ||||
|     public function getCancellationDate() | ||||
|     { | ||||
|         return $this->cancellationDate; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of password | ||||
|      */ | ||||
|     public function getPassword() | ||||
|     { | ||||
|         return $this->password; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										18
									
								
								src/model/NotificationType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/model/NotificationType.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Appvise\AppStoreNotifications\Model; | ||||
| 
 | ||||
| use BenSampo\Enum\Enum; | ||||
| 
 | ||||
| class NotificationType extends Enum | ||||
| { | ||||
|     const INITIAL_BUY = 'initial_buy'; | ||||
|     const CANCEL = 'cancel'; | ||||
|     const RENEWAL = 'renewal'; | ||||
|     const INTERACTIVE_RENEWAL = 'interactive_renewal'; | ||||
|     const DID_CHANGE_RENEWAL_PREF = 'did_change_renewal_pref'; | ||||
|     const DID_CHANGE_RENEWAL_STATUS = 'did_change_renewal_status'; | ||||
|     const DID_FAIL_TO_RENEW = 'did_fail_to_renew'; | ||||
|     const DID_RECOVER = 'did_recover'; // replaces RENEWAL
 | ||||
|     const PRICE_INCREASE_CONSENT = 'price_increase_consent'; | ||||
| } | ||||
							
								
								
									
										301
									
								
								src/model/Receipt.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										301
									
								
								src/model/Receipt.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,301 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Appvise\AppStoreNotifications\Model; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| class Receipt | ||||
| { | ||||
|     private $originalTransactionId; | ||||
|     private $webOrderLineItemId; | ||||
|     private $productId; | ||||
|     private $purchaseDateMs; | ||||
|     private $purchaseDate; | ||||
|     private $purchaseDatePst; | ||||
|     private $originalPurchaseDate; | ||||
|     private $originalPurchaseDateMs; | ||||
|     private $originalPurchaseDatePst; | ||||
|     private $cancellationReason; | ||||
|     private $cancellationDate; | ||||
|     private $cancellationDateMs; | ||||
|     private $cancellationDatePst; | ||||
|     private $expiresDate; | ||||
|     private $expiresDateMs; | ||||
|     private $expiresDateFormatted; | ||||
|     private $expiresDateFormattedPst; | ||||
|     private $quantity; | ||||
|     private $uniqueIdentifier; | ||||
|     private $uniqueVendorIdentifier; | ||||
|     private $isInIntroOfferPeriod; | ||||
|     private $isTrialPeriod; | ||||
|     private $itemId; | ||||
|     private $appItemId; | ||||
|     private $versionExternalIdentifier; | ||||
|     private $transactionId; | ||||
|     private $bvrs; | ||||
|     private $bid; | ||||
| 
 | ||||
|     public function __construct() | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     static function createFromArray(array $receiptInfo) | ||||
|     { | ||||
|         $instance = new self(); | ||||
|         $instance->originalTransactionId = $receiptInfo['original_transaction_id'] ?? null; | ||||
|         $instance->webOrderLineItemId = $receiptInfo['web_order_line_item_id'] ?? null; | ||||
|         $instance->productId = $receiptInfo['product_id'] ?? null; | ||||
|         $instance->purchaseDateMs = $receiptInfo['purchase_date_ms'] ?? null; | ||||
|         $instance->purchaseDate = $receiptInfo['purchase_date'] ?? null; | ||||
|         $instance->purchaseDatePst = $receiptInfo['purchase_date_pst'] ?? null; | ||||
|         $instance->originalPurchaseDate = $receiptInfo['original_purchase_date'] ?? null; | ||||
|         $instance->originalPurchaseDateMs = $receiptInfo['original_purchase_date_ms'] ?? null; | ||||
|         $instance->originalPurchaseDatePst = $receiptInfo['original_purchase_date_pst'] ?? null; | ||||
|         $instance->cancellationReason = $receiptInfo['cancellation_reason'] ?? null; | ||||
|         $instance->cancellationDate = $receiptInfo['cancellation_date'] ?? null; | ||||
|         $instance->cancellationDateMs = $receiptInfo['cancellation_date_ms'] ?? null; | ||||
|         $instance->cancellationDatePst = $receiptInfo['cancellation_date_pst'] ?? null; | ||||
|         $instance->expiresDate = $receiptInfo['expires_date'] ?? null; | ||||
|         $instance->expiresDateMs = $receiptInfo['expires_date_ms'] ?? null; | ||||
|         $instance->expiresDateFormatted = $receiptInfo['expires_date_formatted'] ?? null; | ||||
|         $instance->expiresDateFormattedPst = $receiptInfo['expires_date_formatted_pst'] ?? null; | ||||
|         $instance->quantity = $receiptInfo['quantity'] ?? null; | ||||
|         $instance->uniqueIdentifier = $receiptInfo['unique_identifier'] ?? null; | ||||
|         $instance->uniqueVendorIdentifier = $receiptInfo['unique_vendor_identifier'] ?? null; | ||||
|         $instance->isInIntroOfferPeriod = $receiptInfo['is_in_intro_offer_period'] ?? null; | ||||
|         $instance->isTrialPeriod = $receiptInfo['is_trial_period'] ?? null; | ||||
|         $instance->itemId = $receiptInfo['item_id'] ?? null; | ||||
|         $instance->appItemId = $receiptInfo['app_item_id'] ?? null; | ||||
|         $instance->versionExternalIdentifier = $receiptInfo['version_external_identifier'] ?? null; | ||||
|         $instance->transactionId = $receiptInfo['transaction_id'] ?? null; | ||||
|         $instance->bvrs = $receiptInfo['bvrs'] ?? null; | ||||
|         $instance->bid = $receiptInfo['bid'] ?? null; | ||||
|         return $instance; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of bid | ||||
|      */ | ||||
|     public function getBid() | ||||
|     { | ||||
|         return $this->bid; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of bvrs | ||||
|      */ | ||||
|     public function getBvrs() | ||||
|     { | ||||
|         return $this->bvrs; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of transactionId | ||||
|      */ | ||||
|     public function getTransactionId() | ||||
|     { | ||||
|         return $this->transactionId; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of versionExternalIdentifier | ||||
|      */ | ||||
|     public function getVersionExternalIdentifier() | ||||
|     { | ||||
|         return $this->versionExternalIdentifier; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of appItemId | ||||
|      */ | ||||
|     public function getAppItemId() | ||||
|     { | ||||
|         return $this->appItemId; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of itemId | ||||
|      */ | ||||
|     public function getItemId() | ||||
|     { | ||||
|         return $this->itemId; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of isTrialPeriod | ||||
|      */ | ||||
|     public function getIsTrialPeriod() | ||||
|     { | ||||
|         return $this->isTrialPeriod; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of isInIntroOfferPeriod | ||||
|      */ | ||||
|     public function getIsInIntroOfferPeriod() | ||||
|     { | ||||
|         return $this->isInIntroOfferPeriod; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of uniqueVendorIdentifier | ||||
|      */ | ||||
|     public function getUniqueVendorIdentifier() | ||||
|     { | ||||
|         return $this->uniqueVendorIdentifier; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of uniqueIdentifier | ||||
|      */ | ||||
|     public function getUniqueIdentifier() | ||||
|     { | ||||
|         return $this->uniqueIdentifier; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of quantity | ||||
|      */ | ||||
|     public function getQuantity() | ||||
|     { | ||||
|         return $this->quantity; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of expiresDateFormattedPst | ||||
|      */ | ||||
|     public function getExpiresDateFormattedPst() | ||||
|     { | ||||
|         return $this->expiresDateFormattedPst; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of expiresDateFormatted | ||||
|      */ | ||||
|     public function getExpiresDateFormatted() | ||||
|     { | ||||
|         return $this->expiresDateFormatted; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of expiresDateMs | ||||
|      */ | ||||
|     public function getExpiresDateMs() | ||||
|     { | ||||
|         return $this->expiresDateMs; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of expiresDate | ||||
|      */ | ||||
|     public function getExpiresDate() | ||||
|     { | ||||
|         return $this->expiresDate; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of cancellationDatePst | ||||
|      */ | ||||
|     public function getCancellationDatePst() | ||||
|     { | ||||
|         return $this->cancellationDatePst; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of cancellationDateMs | ||||
|      */ | ||||
|     public function getCancellationDateMs() | ||||
|     { | ||||
|         return $this->cancellationDateMs; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of cancellationDate | ||||
|      */ | ||||
|     public function getCancellationDate() | ||||
|     { | ||||
|         return $this->cancellationDate; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of cancellationReason | ||||
|      */ | ||||
|     public function getCancellationReason() | ||||
|     { | ||||
|         return $this->cancellationReason; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of originalPurchaseDatePst | ||||
|      */ | ||||
|     public function getOriginalPurchaseDatePst() | ||||
|     { | ||||
|         return $this->originalPurchaseDatePst; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of originalPurchaseDateMs | ||||
|      */ | ||||
|     public function getOriginalPurchaseDateMs() | ||||
|     { | ||||
|         return $this->originalPurchaseDateMs; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of originalPurchaseDate | ||||
|      */ | ||||
|     public function getOriginalPurchaseDate() | ||||
|     { | ||||
|         return $this->originalPurchaseDate; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of purchaseDatePst | ||||
|      */ | ||||
|     public function getPurchaseDatePst() | ||||
|     { | ||||
|         return $this->purchaseDatePst; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of purchaseDate | ||||
|      */ | ||||
|     public function getPurchaseDate() | ||||
|     { | ||||
|         return $this->purchaseDate; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of purchaseDateMs | ||||
|      */ | ||||
|     public function getPurchaseDateMs() | ||||
|     { | ||||
|         return $this->purchaseDateMs; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of productId | ||||
|      */ | ||||
|     public function getProductId() | ||||
|     { | ||||
|         return $this->productId; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of webOrderLineItemId | ||||
|      */ | ||||
|     public function getWebOrderLineItemId() | ||||
|     { | ||||
|         return $this->webOrderLineItemId; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of originalTransactionId | ||||
|      */ | ||||
|     public function getOriginalTransactionId() | ||||
|     { | ||||
|         return $this->originalTransactionId; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										116
									
								
								src/model/RenewalInfo.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								src/model/RenewalInfo.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,116 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Appvise\AppStoreNotifications\Model; | ||||
| 
 | ||||
| 
 | ||||
| class RenewalInfo | ||||
| { | ||||
|     private $autoRenewProductId; | ||||
|     private $autoRenewStatus; | ||||
|     private $expirationIntent; | ||||
|     private $originalTransactionId; | ||||
|     private $isInBillingRetryPeriod; | ||||
|     private $productId; | ||||
|     private $priceConsentStatus; | ||||
|     private $gracePeriodExpiresDate; | ||||
|     private $gracePeriodExpiresDateMs; | ||||
|     private $gracePeriodExpiresDatePst; | ||||
| 
 | ||||
| 
 | ||||
|     public function __construct() | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     public static function createFromRequest(array $pendingRenewalInfo) { | ||||
|         $instance = new self(); | ||||
|         $instance->autoRenewProductId = $pendingRenewalInfo['auto_renew_product_id'] ?? null; | ||||
|         $instance->autoRenewStatus = $pendingRenewalInfo['auto_renew_status'] ?? null; | ||||
|         $instance->expirationIntent = $pendingRenewalInfo['expiration_intent'] ?? null; | ||||
|         $instance->originalTransactionId = $pendingRenewalInfo['original_transaction_id'] ?? null; | ||||
|         $instance->isInBillingRetryPeriod = $pendingRenewalInfo['is_in_billing_retry_period'] ?? null; | ||||
|         $instance->productId = $pendingRenewalInfo['product_id'] ?? null; | ||||
|         $instance->priceConsentStatus = $pendingRenewalInfo['price_consent_status'] ?? null; | ||||
|         $instance->gracePeriodExpiresDate = $pendingRenewalInfo['grace_period_expires_date'] ?? null; | ||||
|         $instance->gracePeriodExpiresDatePst = $pendingRenewalInfo['grace_period_expires_date_pst'] ?? null; | ||||
|         return $instance; | ||||
|     } | ||||
|     /** | ||||
|      * Get the value of autoRenewProductId | ||||
|      */ | ||||
|     public function getAutoRenewProductId() | ||||
|     { | ||||
|         return $this->autoRenewProductId; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of autoRenewStatus | ||||
|      */ | ||||
|     public function getAutoRenewStatus() | ||||
|     { | ||||
|         return $this->autoRenewStatus; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of expirationIntent | ||||
|      */ | ||||
|     public function getExpirationIntent() | ||||
|     { | ||||
|         return $this->expirationIntent; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of originalTransactionId | ||||
|      */ | ||||
|     public function getOriginalTransactionId() | ||||
|     { | ||||
|         return $this->originalTransactionId; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of isInBillingRetryPeriod | ||||
|      */ | ||||
|     public function getIsInBillingRetryPeriod() | ||||
|     { | ||||
|         return $this->isInBillingRetryPeriod; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of productId | ||||
|      */ | ||||
|     public function getProductId() | ||||
|     { | ||||
|         return $this->productId; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of priceConsentStatus | ||||
|      */ | ||||
|     public function getPriceConsentStatus() | ||||
|     { | ||||
|         return $this->priceConsentStatus; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of gracePeriodExpiresDate | ||||
|      */ | ||||
|     public function getGracePeriodExpiresDate() | ||||
|     { | ||||
|         return $this->gracePeriodExpiresDate; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of gracePeriodExpiresDateMs | ||||
|      */ | ||||
|     public function getGracePeriodExpiresDateMs() | ||||
|     { | ||||
|         return $this->gracePeriodExpiresDateMs; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the value of gracePeriodExpiresDatePst | ||||
|      */ | ||||
|     public function getGracePeriodExpiresDatePst() | ||||
|     { | ||||
|         return $this->gracePeriodExpiresDatePst; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										3
									
								
								src/routes.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/routes.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| <?php | ||||
| 
 | ||||
| Route::post('/apple/server/notifications', "\Appvise\AppStoreNotifications\WebhooksController"); | ||||
							
								
								
									
										27
									
								
								tests/DummyJob.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								tests/DummyJob.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Appvise\AppStoreNotifications\Tests; | ||||
| 
 | ||||
| use Appvise\AppStoreNotifications\Model\NotificationPayload; | ||||
| use Illuminate\Bus\Queueable; | ||||
| use Illuminate\Contracts\Queue\ShouldQueue; | ||||
| use Illuminate\Foundation\Bus\Dispatchable; | ||||
| use Illuminate\Queue\InteractsWithQueue; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| 
 | ||||
| 
 | ||||
| class DummyJob implements ShouldQueue | ||||
| { | ||||
|     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; | ||||
| 
 | ||||
|     public $payload; | ||||
| 
 | ||||
|     public function __construct(NotificationPayload $payload) | ||||
|     { | ||||
|         $this->payload = $payload; | ||||
|     } | ||||
| 
 | ||||
|     function handle() | ||||
|     { | ||||
|     } | ||||
| } | ||||
							
								
								
									
										79
									
								
								tests/IntegrationTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								tests/IntegrationTest.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,79 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Appvise\AppStoreNotifications\Tests; | ||||
| 
 | ||||
| use Appvise\AppStoreNotifications\Model\AppleNotification; | ||||
| use Illuminate\Support\Facades\Queue; | ||||
| use Illuminate\Support\Facades\Route; | ||||
| 
 | ||||
| class IntegrationTest extends TestCase | ||||
| { | ||||
|     public function setUp(): void | ||||
|     { | ||||
|         parent::setUp(); | ||||
| 
 | ||||
|         Queue::fake(); | ||||
| 
 | ||||
|         Route::post('/apple/server/notifications', "\Appvise\AppStoreNotifications\WebhooksController"); | ||||
| 
 | ||||
|         config( | ||||
|             [ | ||||
|                 'appstore-server-notifications.jobs' => [ | ||||
|                     'initial_buy' => DummyJob::class | ||||
|                 ], | ||||
|                 'appstore-server-notifications.shared_secret' => 'VALID_APPLE_PASSWORD', | ||||
|             ] | ||||
|         ); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /** @test */ | ||||
|     public function it_can_handle_a_valid_request() | ||||
|     { | ||||
|         $payload = include_once __DIR__ . '/__fixtures__/request.php'; | ||||
| 
 | ||||
|         $payload['password'] = "VALID_APPLE_PASSWORD"; | ||||
| 
 | ||||
|         $this | ||||
|             ->postJson('/apple/server/notifications', $payload) | ||||
|             ->assertSuccessful(); | ||||
| 
 | ||||
|         $this->assertCount(1, AppleNotification::get()); | ||||
| 
 | ||||
|         $notification = AppleNotification::first(); | ||||
| 
 | ||||
|         $this->assertEquals('initial_buy', $notification->type); | ||||
|         $this->assertInstanceOf(AppleNotification::class, $notification); | ||||
| 
 | ||||
|         Queue::assertPushed(DummyJob::class); | ||||
|     } | ||||
| 
 | ||||
|     /** @test */ | ||||
|     public function a_request_with_an_invalid_password_wont_be_logged() | ||||
|     { | ||||
|         $payload = include_once __DIR__ . '/__fixtures__/request.php'; | ||||
|         $payload['password'] = "NON_VALID_APPLE_PASSWORD"; | ||||
| 
 | ||||
|         $this | ||||
|             ->postJson('/apple/server/notifications', $payload) | ||||
|             ->assertStatus(400); | ||||
| 
 | ||||
|         $this->assertCount(0, AppleNotification::get()); | ||||
|         $this->assertNull(AppleNotification::first()); | ||||
| 
 | ||||
|         Queue::assertNotPushed(DummyJob::class); | ||||
|     } | ||||
| 
 | ||||
|     /** @test */ | ||||
|     public function a_request_with_an_invalid_payload_will_be_logged_but_jobs_will_not_be_dispatched() | ||||
|     { | ||||
|         $payload = ['payload' => 'INVALID']; | ||||
| 
 | ||||
|         $this | ||||
|             ->postJson('/apple/server/notifications', $payload) | ||||
|             ->assertStatus(500); | ||||
| 
 | ||||
|         Queue::assertNotPushed(DummyJob::class); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										75
									
								
								tests/TestCase.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								tests/TestCase.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,75 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Appvise\AppStoreNotifications\Tests; | ||||
| 
 | ||||
| use Appvise\AppStoreNotifications\NotificationsServiceProvider; | ||||
| use Exception; | ||||
| use Illuminate\Foundation\Exceptions\Handler; | ||||
| use Illuminate\Contracts\Debug\ExceptionHandler; | ||||
| use Orchestra\Testbench\TestCase as OrchestraTestCase; | ||||
| use CreateAppleNotificationsTable; | ||||
| 
 | ||||
| abstract class TestCase extends OrchestraTestCase | ||||
| { | ||||
|     public function setUp(): void | ||||
|     { | ||||
|         parent::setUp(); | ||||
| 
 | ||||
|         $this->setUpDatabase(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set up the environment. | ||||
|      * | ||||
|      * @param \Illuminate\Foundation\Application $app | ||||
|      */ | ||||
|     protected function getEnvironmentSetUp($app) | ||||
|     { | ||||
|         $app['config']->set('database.default', 'sqlite'); | ||||
|         $app['config']->set('database.connections.sqlite', [ | ||||
|             'driver'   => 'sqlite', | ||||
|             'database' => ':memory:', | ||||
|             'prefix'   => '', | ||||
|         ]); | ||||
| 
 | ||||
|         config(['appstore-server-notifications.shared_secret' => 'test_shared_secret']); | ||||
|     } | ||||
| 
 | ||||
|     protected function setUpDatabase() | ||||
|     { | ||||
|         include_once __DIR__.'/../database/migrations/create_apple_notifications_table.php.stub'; | ||||
| 
 | ||||
|         (new CreateAppleNotificationsTable())->up(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param \Illuminate\Foundation\Application $app | ||||
|      * | ||||
|      * @return array | ||||
|      */ | ||||
|     protected function getPackageProviders($app) | ||||
|     { | ||||
|         return [ | ||||
|             NotificationsServiceProvider::class, | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     protected function disableExceptionHandling() | ||||
|     { | ||||
|         $this->app->instance(ExceptionHandler::class, new class extends Handler { | ||||
|             public function __construct() | ||||
|             { | ||||
|             } | ||||
| 
 | ||||
|             public function report(Exception $e) | ||||
|             { | ||||
|             } | ||||
| 
 | ||||
|             public function render($request, Exception $exception) | ||||
|             { | ||||
|                 throw $exception; | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										126
									
								
								tests/__fixtures__/request.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								tests/__fixtures__/request.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,126 @@ | ||||
| <?php | ||||
| return json_decode('{ | ||||
|         "environment": "Sandbox", | ||||
|         "notification_type": "INITIAL_BUY", | ||||
|         "password": "TEST_SHARED_SECRET", | ||||
|         "cancellation_date": "2018-03-27 07:11:12 Etc/GMT", | ||||
|         "cancellation_date_pst": "2018-03-27 00:11:12 America/Los_Angeles", | ||||
|         "cancellation_date_ms": "1522134672000", | ||||
|         "web_order_line_item_id": "1000000047417113", | ||||
|         "latest_receipt": "BASE64ENCODED_RECEIPT_INFO", | ||||
|         "latest_receipt_info": {}, | ||||
|         "latest_expired_receipt" : "BASE64ENCODED_LATEST_RECEIPT_INFO", | ||||
|         "latest_expired_receipt_info": { | ||||
|             "purchase_date_ms": "1521893342000", | ||||
|             "original_transaction_id": "1000000577061006", | ||||
|             "web_order_line_item_id": "1000000047417113", | ||||
|             "product_id": "PRODUCT_ID", | ||||
|             "purchase_date": "2018-03-24 12:09:02 Etc/GMT", | ||||
|             "purchase_date_pst": "2018-03-24 05:09:02 America/Los_Angeles", | ||||
|             "original_purchase_date": "2018-03-17 12:09:03 Etc/GMT", | ||||
|             "original_purchase_date_ms": "1521288543000", | ||||
|             "original_purchase_date_pst": "2018-03-17 05:09:03 America/Los_Angeles", | ||||
|             "cancellation_reason": "0", | ||||
|             "cancellation_date": "2018-03-27 07:11:12 Etc/GMT", | ||||
|             "cancellation_date_ms": "1522134672000", | ||||
|             "cancellation_date_pst": "2018-03-27 00:11:12 America/Los_Angeles", | ||||
|             "expires_date": "2019-10-09 07:43:26 Etc/GMT", | ||||
|             "expires_date_ms": "1570607006000", | ||||
|             "expires_date_formatted": "2019-03-24 12:09:02 Etc/GMT", | ||||
|             "expires_date_formatted_pst": "2019-03-24 05:09:02 America/Los_Angeles", | ||||
|             "quantity": "1", | ||||
|             "unique_identifier": "UNIQUE_IDENTIFIER", | ||||
|             "unique_vendor_identifier": "UNIQUE_VENDOR_IDENTIFIER", | ||||
|             "is_in_intro_offer_period": "false", | ||||
|             "is_trial_period": "false", | ||||
|             "item_id": "ITEM_ID", | ||||
|             "app_item_id": "APP_ITEM_ID", | ||||
|             "version_external_identifier": "VERSION_EXTERNAL_IDENTIFIER", | ||||
|             "transaction_id": "1000000577069202", | ||||
|             "bvrs": "2", | ||||
|             "bid": "com.example.app.ios" | ||||
|         }, | ||||
|         "auto_renew_status": "false", | ||||
|         "auto_renew_product_id": "PRODUCT_ID", | ||||
|         "auto_renew_status_change_date": "", | ||||
|         "auto_renew_status_change_date_pst": "", | ||||
|         "auto_renew_status_change_date_ms": "", | ||||
|         "pending_renewal_info": [ | ||||
|             { | ||||
|                 "auto_renew_product_id": "PRODUCT_ID", | ||||
|                 "auto_renew_status": "1", | ||||
|                 "expiration_intent": "", | ||||
|                 "original_transaction_id": "1000000577061006", | ||||
|                 "is_in_billing_retry_period": "1", | ||||
|                 "product_id": "PRODUCT_ID", | ||||
|                 "price_consent_status": "0", | ||||
|                 "grace_period_expires_date": "", | ||||
|                 "grace_period_expires_date_ms": "", | ||||
|                 "grace_period_expires_date_pst": "" | ||||
|             } | ||||
|         ] | ||||
|     }', true | ||||
| ); | ||||
| 
 | ||||
| 
 | ||||
|     //{
 | ||||
|     //     "environment": "Sandbox",
 | ||||
|     //     "notification_type": "INITIAL_BUY",
 | ||||
|     //     "password": "TEST_SHARED_SECRET",
 | ||||
|     //     "cancellation_date": "2018-03-27 07:11:12 Etc/GMT",
 | ||||
|     //     "cancellation_date_pst": "2018-03-27 00:11:12 America/Los_Angeles",
 | ||||
|     //     "cancellation_date_ms": "1522134672000", // important for cancel
 | ||||
|     //     "web_order_line_item_id": "1000000047417113",
 | ||||
|     //     "latest_receipt": "BASE64ENCODED_RECEIPT_INFO",
 | ||||
|     //     "latest_receipt_info": "ARRAY_WITH_RECEIPT_INFO",
 | ||||
|     //     "latest_expired_receipt" : "BASE64ENCODED_LATEST_RECEIPT_INFO",
 | ||||
|     //     "latest_expired_receipt_info": {
 | ||||
|     //         "purchase_date_ms": "1521893342000", // important for initial buy,  interactive_renewal, did_recover
 | ||||
|     //         "original_transaction_id": "1000000577061006", // important for initial buy, interactive_renewal, did_change_renewal_info, cancel, did_change_renewal_status, did_fail_to_renew, did_recover, price_increase_consent
 | ||||
|     //         "web_order_line_item_id": "1000000047417113", // important for initial buy,  interactive_renewal
 | ||||
|     //         "product_id": "PRODUCT_ID", // important for initial buy, interactive_renewal, cancel, did_change_renewal_status
 | ||||
|     //         "purchase_date": "2018-03-24 12:09:02 Etc/GMT",
 | ||||
|     //         "purchase_date_pst": "2018-03-24 05:09:02 America/Los_Angeles",
 | ||||
|     //         "original_purchase_date": "2018-03-17 12:09:03 Etc/GMT",
 | ||||
|     //         "original_purchase_date_ms": "1521288543000",
 | ||||
|     //         "original_purchase_date_pst": "2018-03-17 05:09:03 America/Los_Angeles",
 | ||||
|     //         "cancellation_reason": "0",
 | ||||
|     //         "cancellation_date": "2018-03-27 07:11:12 Etc/GMT",
 | ||||
|     //         "cancellation_date_ms": "1522134672000",
 | ||||
|     //         "cancellation_date_pst": "2018-03-27 00:11:12 America/Los_Angeles",
 | ||||
|     //         "expires_date": "2019-10-09 07:43:26 Etc/GMT",
 | ||||
|     //         "expires_date_ms": "1570607006000", // important for did_recover, price_increase_consent
 | ||||
|     //         "expires_date_formatted": "2019-03-24 12:09:02 Etc/GMT",
 | ||||
|     //         "expires_date_formatted_pst": "2019-03-24 05:09:02 America/Los_Angeles",
 | ||||
|     //         "quantity": "1",
 | ||||
|     //         "unique_identifier": "UNIQUE_IDENTIFIER",
 | ||||
|     //         "unique_vendor_identifier": "UNIQUE_VENDOR_IDENTIFIER",
 | ||||
|     //         "is_in_intro_offer_period": "false",
 | ||||
|     //         "is_trial_period": "false",
 | ||||
|     //         "item_id": "ITEM_ID",
 | ||||
|     //         "app_item_id": "APP_ITEM_ID",
 | ||||
|     //         "version_external_identifier": "VERSION_EXTERNAL_IDENTIFIER",
 | ||||
|     //         "transaction_id": "1000000577069202",
 | ||||
|     //         "bvrs": "2",
 | ||||
|     //         "bid": "com.example.ios.app"
 | ||||
|     //     },
 | ||||
|     //     "auto_renew_status": "false", // important for did_change_renewal_status
 | ||||
|     //     "auto_renew_product_id": "PRODUCT_ID", // important for did_change_renewal_info
 | ||||
|     //     "auto_renew_status_change_date": "",
 | ||||
|     //     "auto_renew_status_change_date_pst": "",
 | ||||
|     //     "auto_renew_status_change_date_ms": "", // important for did_change_renewal_status
 | ||||
|     //     "pending_renewal_info": [ // important for did_fail_to_renew
 | ||||
|     //         {
 | ||||
|     //             "auto_renew_product_id": "PRODUCT_ID",
 | ||||
|     //             "auto_renew_status": "1",
 | ||||
|     //             "expiration_intent": "",
 | ||||
|     //             "original_transaction_id": "1000000577061006",
 | ||||
|     //             "is_in_billing_retry_period": "1",
 | ||||
|     //             "product_id": "PRODUCT_ID",
 | ||||
|     //             "price_consent_status": "0", // important for price_increase_consent
 | ||||
|     //             "grace_period_expires_date": "",
 | ||||
|     //             "grace_period_expires_date_ms": "",
 | ||||
|     //             "grace_period_expires_date_pst": ""
 | ||||
|     //         }
 | ||||
|     //     ]
 | ||||
|     // }
 | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Daan Geurts
						Daan Geurts