Compare commits

..

No commits in common. "master" and "v0.0.1" have entirely different histories.

11 changed files with 21 additions and 285 deletions

1
.gitignore vendored
View File

@ -3,4 +3,3 @@ composer.lock
vendor
coverage.clover
.phpunit.result.cache
.vscode

View File

@ -1,14 +1,20 @@
language: php
php:
- 7.2
cache:
directories:
- $HOME/.composer/cache
matrix:
fast_finish: true
include:
- php: 7.2
env: LARAVEL='5.7.*' TESTBENCH='3.7.*' PHPENUM='1.*' PHPUNIT='7.*' COMPOSER_FLAGS='--prefer-stable'
- php: 7.3
env: LARAVEL='5.7.*' TESTBENCH='3.7.*' PHPENUM='1.*' PHPUNIT='7.*' COMPOSER_FLAGS='--prefer-lowest'
- php: 7.2
env: LARAVEL='5.8.*' TESTBENCH='3.8.*' PHPENUM='1.*' PHPUNIT='8.*' COMPOSER_FLAGS='--prefer-stable'
- php: 7.3
env: LARAVEL='5.8.*' TESTBENCH='3.8.*' PHPENUM='1.*' PHPUNIT='8.*' COMPOSER_FLAGS='--prefer-lowest'
before_install:
- travis_retry composer self-update

View File

@ -1,6 +1,6 @@
# Handle Appstore server-to-server notifications for auto-renewable subscriptions
[![Latest Version on Packagist](https://img.shields.io/packagist/v/tag/app-vise/laravel-appstore-server-notifications.svg?style=flat-square&sort=semver)](https://packagist.org/packages/app-vise/laravel-appstore-server-notifications)
[![Latest Version on Packagist](https://img.shields.io/packagist/v/app-vise/laravel-appstore-server-notifications.svg?style=flat-square)](https://packagist.org/packages/app-vise/laravel-appstore-server-notifications)
[![Build Status](https://travis-ci.org/app-vise/laravel-appstore-notifications.svg?branch=master)](https://travis-ci.org/app-vise/laravel-appstore-notifications)
[![StyleCI](https://styleci.io/repos/215539443/shield?branch=master)](https://styleci.io/repos/215539443)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/app-vise/laravel-appstore-notifications/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/app-vise/laravel-appstore-notifications/?branch=master)

View File

@ -1,5 +1,5 @@
{
"name": "rjgonzale/laravel-appstore-server-notifications",
"name": "app-vise/laravel-appstore-server-notifications",
"description": "Handling Appstore server to server notifications",
"keywords": [
"app-vise",
@ -19,9 +19,9 @@
}
],
"require": {
"php": "^7.",
"bensampo/laravel-enum": "^1.0",
"illuminate/support": "^5.5|^6.0|^7."
"php": "^7.1",
"illuminate/support": "~5.7.0|^6.0",
"bensampo/laravel-enum": "^1.0"
},
"require-dev": {
"orchestra/testbench": "~3.7.0|^4.0",

View File

@ -22,5 +22,4 @@ return [
// 'did_change_renewal_pref' => \App\Jobs\AppstoreNotifications\HandleDidChangeRenewalPreferences::class,
// 'did_change_renewal_status' => \App\Jobs\AppstoreNotifications\HandleDidChangeRenewalStatus::class,
],
'proxy_host' => env('APPLE_PROXY_HOST'),
];

View File

@ -1,200 +0,0 @@
<?php
namespace Appvise\AppStoreNotifications;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Http\Client\Response;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile as HttpUploadedFile;
use Illuminate\Support\Facades\Http;
class ProxyHelper {
//Original request
private Request $originalRequest;
//Params from multipart requests
private $multipartParams;
//Custom Headers
private $headers;
//Custom Authorization
private $authorization;
//Custom Method
private $customMethod;
private $addQuery;
//If needed add cookies support with:
// - PendingRequest->withCookies
// - Request->hasCookie
// - or custom
//Settings
private $useDefaultAuth;
//It's recommandable check manually (for multipart exceptions and other things)
// private $useDefaultHeaders;
public function CreateProxy(Request $request, $useDefaultAuth = true /*, $useDefaultHeaders = false*/){
$this->originalRequest = $request;
$this->multipartParams = $this->GetMultipartParams();
$this->useDefaultAuth = $useDefaultAuth;
// $this->useDefaultHeaders = $useDefaultHeaders;
return $this;
}
public function withHeaders($headers){ $this->headers = $headers; return $this; }
public function withBasicAuth($user, $secret){ $this->authorization = ['type' => 'basic', 'user' => $user, 'secret' => $secret ]; return $this; }
public function withDigestAuth($user, $secret){ $this->authorization = ['type' => 'digest', 'user' => $user, 'secret' => $secret ]; return $this; }
public function withToken($token){ $this->authorization = ['type' => 'token', 'token' => $token ]; return $this; }
public function withMethod($method = 'POST'){ $this->customMethod = $method; return $this; }
public function preserveQuery($preserve){ $this->addQuery = $preserve; return $this; }
public function getResponse($url){
$info = $this->getRequestInfo();
$http = $this->createHttp($info['type']);
$http = $this->setAuth($http, $info['token']);
$http = $this->setHeaders($http);
if($this->addQuery && $info['query'])
$url = $url.'?'.http_build_query($info['query']);
$response = $this->call($http, $info['method'], $url, $this->getParams($info));
return response($this->isJson($response) ? $response->json() : $response->body(), $response->status());
}
public function toUrl($url){ return $this->getResponse($url); }
public function toHost($host, $proxyController){
return $this->getResponse($host.str_replace($proxyController, '', $this->originalRequest->path()));
}
private function getParams($info){
$defaultParams = [];
if($info['method'] == 'GET')
return $info['params'];
if($info['type'] == 'multipart')
$defaultParams = $this->multipartParams;
else
$defaultParams = $info['params'];
if($info['query'])
foreach ($info['query'] as $key => $value)
unset($defaultParams[array_search(['name' => $key,'contents' => $value], $defaultParams)]);
return $defaultParams;
}
private function setAuth(PendingRequest $request, $currentAuth = null){
if(!$this->authorization)
return $request;
switch ($this->authorization['type']) {
case 'basic':
return $request->withBasicAuth($this->authorization['user'],$this->authorization['secret']);
case 'digest':
return $request->withDigestAuth($this->authorization['user'],$this->authorization['secret']);
case 'token':
return $request->withToken($this->authorization['token']);
default:
if($currentAuth && $this->useDefaultAuth)
return $request->withToken($currentAuth);
return $request;
}
}
private function setHeaders(PendingRequest $request){
if(!$this->headers)
return $request;
return $request->withHeaders($this->headers);
}
private function createHttp($type){
switch ($type) {
case 'multipart':
return Http::asMultipart();
case 'form':
return Http::asForm();
case 'json':
return Http::asJson();
case null:
return new PendingRequest();
default:
return Http::contentType($type);
}
}
private function call(PendingRequest $request, $method, $url, $params){
if($this->customMethod)
$method = $this->customMethod;
switch ($method) {
case 'GET':
return $request->get($url, $params);
case 'HEAD':
return $request->head($url, $params);
default:
case 'POST':
return $request->post($url, $params);
case 'PATCH':
return $request->patch($url, $params);
case 'PUT':
return $request->put($url, $params);
case 'DELETE':
return $request->delete($url, $params);
}
}
private function getRequestInfo(){
return [
'type' => ($this->originalRequest->isJson() ? 'json' :
(strpos($this->originalRequest->header('Content-Type'),'multipart') !== false ? 'multipart' :
($this->originalRequest->header('Content-Type') == 'application/x-www-form-urlencoded' ? 'form' : $this->originalRequest->header('Content-Type')))),
'agent' => $this->originalRequest->userAgent(),
'method' => $this->originalRequest->method(),
'token' => $this->originalRequest->bearerToken(),
'full_url'=>$this->originalRequest->fullUrl(),
'url'=>$this->originalRequest->url(),
'format'=>$this->originalRequest->format(),
'query' =>$this->originalRequest->query(),
'params' => $this->originalRequest->all(),
];
}
private function GetMultipartParams(){
$multipartParams = [];
if ($this->originalRequest->isMethod('post')) {
$formParams = $this->originalRequest->all();
$fileUploads = [];
foreach ($formParams as $key => $param)
if ($param instanceof HttpUploadedFile) {
$fileUploads[$key] = $param;
unset($formParams[$key]);
}
if (count($fileUploads) > 0){
$multipartParams = [];
foreach ($formParams as $key => $value)
$multipartParams[] = [
'name' => $key,
'contents' => $value
];
foreach ($fileUploads as $key => $value)
$multipartParams[] = [
'name' => $key,
'contents' => fopen($value->getRealPath(), 'r'),
'filename' => $value->getClientOriginalName(),
'headers' => [
'Content-Type' => $value->getMimeType()
]
];
}
}
return $multipartParams;
}
private function isJson(Response $response){
return strpos($response->header('Content-Type'),'json') !== false;
}
}

View File

@ -1,12 +0,0 @@
<?php
namespace Appvise\AppStoreNotifications;
use Illuminate\Support\Facades\Facade;
class ProxyHelperFacade extends Facade {
protected static function getFacadeAccessor(){
return "ProxyHelper";
}
}

View File

@ -2,7 +2,6 @@
namespace Appvise\AppStoreNotifications;
use Appvise\AppStoreNotifications\ProxyHelperFacade;
use Illuminate\Http\Request;
use Appvise\AppStoreNotifications\Model\NotificationType;
use Appvise\AppStoreNotifications\Model\AppleNotification;
@ -13,17 +12,11 @@ class WebhooksController
{
public function __invoke(Request $request)
{
$proxy_host = config('appstore-server-notifications.proxy_host');
if (!empty($proxy_host)) {
ProxyHelperFacade::CreateProxy($request)->toUrl($proxy_host);
}
$jobConfigKey = NotificationType::{$request->input('notification_type')}();
$this->determineValidRequest($request->input('password'));
AppleNotification::storeNotification($jobConfigKey, $request->input());
//$this->determineValidRequest($request->input('password'));
$payload = NotificationPayload::createFromRequest($request);
$jobClass = config("appstore-server-notifications.jobs.{$jobConfigKey}", null);

View File

@ -8,7 +8,7 @@ class WebhookFailed extends Exception
{
public static function nonValidRequest()
{
return new static("Your shared secret does not match password in Apple's request", 500);
return new static("Your shared secret does not match password in Apple's request", 400);
}
public static function jobClassDoesNotExist(string $jobClass)

View File

@ -28,43 +28,6 @@ class NotificationPayload
{
}
public static function createFromArray($notification) {
$instance = new self();
$instance->environment = $notification['environment'];
$instance->password = $notification['password'];
$instance->notificationType = $notification['notification_type'];
$instance->cancellationDate = $notification['cancellation_date'] ?? null;
$instance->cancellationDatePst = $notification['cancellation_date_pst'] ?? null;
$instance->cancellationDateMs = $notification['cancellation_date_ms'] ?? null;
$instance->webOrderLineItemId = $notification['web_order_line_item_id'] ?? null;
$instance->latestReceipt = $notification['latest_receipt'] ?? null;
if (isset($notification['latest_receipt_info'])) {
$instance->latestReceiptInfo = Receipt::createFromArray($notification['latest_receipt_info']);
} else {
$instance->latestReceiptInfo = null;
}
$instance->latestExpiredReceipt = $notification['latest_expired_receipt'] ?? null;
if (isset($notification['latest_expired_receipt_info'])) {
$instance->latestExpiredReceiptInfo = Receipt::createFromArray($notification['latest_expired_receipt_info']);
} else {
$instance->latestExpiredReceiptInfo = null;
}
$instance->autoRenewStatus = $notification['auto_renew_status'];
$instance->autoRenewProductId = $notification['auto_renew_product_id'];
$instance->autoRenewStatusChangeDate = $notification['auto_renew_status_change_date'] ?? null;
$instance->autoRenewStatusChangeDatePst = $notification['auto_renew_status_change_date_pst'] ?? null;
$instance->autoRenewStatusChangeDateMs = $notification['auto_renew_status_change_date_ms'] ?? null;
if (isset($notification['pending_renewal_info'])) {
foreach ($notification['pending_renewal_info'] as $pendingRenewalInfo) {
$instance->pendingRenewalInfo[] = RenewalInfo::createFromRequest($pendingRenewalInfo);
}
} else {
$instance->pendingRenewalInfo = null;
}
return $instance;
}
public static function createFromRequest(Request $request)
{
$instance = new self();
@ -75,22 +38,11 @@ class NotificationPayload
$instance->cancellationDatePst = $request->input('cancellation_date_pst');
$instance->cancellationDateMs = $request->input('cancellation_date_ms');
$instance->webOrderLineItemId = $request->input('web_order_line_item_id');
$unified_receipt = $request->input('unified_receipt');
$instance->latestReceipt = $request->input('unified_receipt.latest_receipt');
if ($request->has('unified_receipt.latest_receipt_info')) {
if (count($request->input('unified_receipt.latest_receipt_info')) > 0) {
$instance->latestReceiptInfo = Receipt::createFromArray($request->input('unified_receipt.latest_receipt_info')[0]);
}
} else {
$instance->latestReceiptInfo = null;
}
$instance->latestExpiredReceipt = $request->input('unified_receipt.latest_expired_receipt');
if ($request->has('unified_receipt.latest_expired_receipt_info')) {
if (count($request->input('unified_receipt.latest_expired_receipt_info')) > 0) {
$instance->latestExpiredReceiptInfo = Receipt::createFromArray($request->input('unified_receipt.latest_expired_receipt_info')[0]);
}
$instance->latestReceipt = $request->input('latest_receipt');
$instance->latestReceiptInfo = Receipt::createFromArray($request->input('latest_receipt_info'));
$instance->latestExpiredReceipt = $request->input('latest_expired_receipt');
if ($request->has('latest_expired_receipt_info')) {
$instance->latestExpiredReceiptInfo = Receipt::createFromArray($request->input('latest_expired_receipt_info'));
} else {
$instance->latestExpiredReceiptInfo = null;
}

View File

@ -15,5 +15,4 @@ class NotificationType extends Enum
const DID_FAIL_TO_RENEW = 'did_fail_to_renew';
const DID_RECOVER = 'did_recover'; // replaces RENEWAL
const PRICE_INCREASE_CONSENT = 'price_increase_consent';
const DID_RENEW = 'did_renew';
}