Daan Geurts
5 years ago
commit
7f4b49d98d
28 changed files with 1340 additions and 0 deletions
@ -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 |
@ -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 |
@ -0,0 +1,5 @@ |
|||||
|
build |
||||
|
composer.lock |
||||
|
vendor |
||||
|
coverage.clover |
||||
|
.phpunit.result.cache |
@ -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 |
@ -0,0 +1,4 @@ |
|||||
|
preset: laravel |
||||
|
|
||||
|
disabled: |
||||
|
- single_class_element_per_statement |
@ -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,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. |
@ -0,0 +1,47 @@ |
|||||
|
# Handle Appstore server-to-server notifications for auto-renewable subscriptions |
||||
|
|
||||
|
[![Latest Version on Packagist](https://img.shields.io/packagist/v/app-vise/laravel-appstore-notifications.svg?style=flat-square)](https://packagist.org/packages/app-vise/laravel-appstore-notifications) |
||||
|
[![Build Status](https://img.shields.io/travis/app-vise/laravel-appstore-notifications/master.svg?style=flat-square)](https://travis-ci.org/app-vise/laravel-appstore-notifications) |
||||
|
[![StyleCI](https://styleci.io/repos/105920179/shield?branch=master)](https://styleci.io/repos/105920179) |
||||
|
[![Quality Score](https://img.shields.io/scrutinizer/g/app-vise/laravel-appstore-notifications.svg?style=flat-square)](https://scrutinizer-ci.com/g/app-vise/laravel-appstore-notifications) |
||||
|
[![Total Downloads](https://img.shields.io/packagist/dt/app-vise/laravel-appstore-notifications.svg?style=flat-square)](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 [email protected] 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,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": "[email protected]", |
||||
|
"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" |
||||
|
] |
||||
|
} |
||||
|
} |
||||
|
} |
@ -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'); |
||||
|
} |
||||
|
} |
@ -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> |
@ -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'); |
||||
|
} |
||||
|
} |
@ -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; |
||||
|
} |
||||
|
} |
@ -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); |
||||
|
} |
||||
|
} |
@ -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), |
||||
|
]); |
||||
|
} |
||||
|
|
||||
|
} |
@ -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; |
||||
|
} |
||||
|
} |
@ -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'; |
||||
|
} |
@ -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; |
||||
|
} |
||||
|
} |
@ -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; |
||||
|
} |
||||
|
} |
@ -0,0 +1,3 @@ |
|||||
|
<?php |
||||
|
|
||||
|
Route::post('/apple/server/notifications', "\Appvise\AppStoreNotifications\WebhooksController"); |
@ -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() |
||||
|
{ |
||||
|
} |
||||
|
} |
@ -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); |
||||
|
} |
||||
|
|
||||
|
} |
@ -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; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
} |
@ -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…
Reference in new issue