Skip to content
This repository has been archived by the owner on Nov 30, 2022. It is now read-only.

Commit

Permalink
Merge pull request #54 from ohmybrew/issue-25-charge-tracking
Browse files Browse the repository at this point in the history
Issue 25 charge tracking
  • Loading branch information
gnikyt authored Jul 20, 2018
2 parents adde112 + 1b27b9d commit e55c3e8
Show file tree
Hide file tree
Showing 27 changed files with 1,077 additions and 71 deletions.
3 changes: 2 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ Well, you can:

You don't have to be a superstar, or someone with experience, you can just dive in and help if you feel you can.


## Adding a new feature?

Its best to:
Expand Down Expand Up @@ -61,6 +60,8 @@ We use PHPUnit to run tests. Simply run `composer install` and a symlink `phpuni

Next, run `bin/phpunit` and the rest will be taken care of. Upon any pull requests and merges, TravisCI will check the code to ensure all test suites pass.

For quicker tests, be sure to disable coverage with `bin/php --no-coverage`.

-----

Thats it! Enjoy.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright (C) 2017 Tyler King <tyler.n.king@gmail.com>
Copyright (C) 2018 Tyler King <tyler.n.king@gmail.com>

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:

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ For more information, tutorials, etc., please view the project's [wiki](https://
- [x] Integration with Shopify API
- [x] Authentication & installation for shops
- [x] Billing integration built-in for single and recurring application charges
- [x] Tracking charges to a shop (recurring, single, usage, etc) with trial support
- [x] Auto install app webhooks and scripttags thorugh background jobs
- [x] Provide basic ESDK views
- [x] Handles and processes incoming webhooks
Expand Down
111 changes: 111 additions & 0 deletions src/ShopifyApp/Jobs/AppUninstalledJob.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php

namespace OhMyBrew\ShopifyApp\Jobs;

use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use OhMyBrew\ShopifyApp\Models\Charge;
use OhMyBrew\ShopifyApp\Models\Shop;

class AppUninstalledJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

/**
* Shop's instance.
*
* @var string
*/
protected $shop;

/**
* Shop's myshopify domain.
*
* @var string
*/
protected $shopDomain;

/**
* The webhook data.
*
* @var object
*/
protected $data;

/**
* Create a new job instance.
*
* @param string $shopDomain The shop's myshopify domain
* @param object $data The webhook data (JSON decoded)
*
* @return void
*/
public function __construct($shopDomain, $data)
{
$this->data = $data;
$this->shopDomain = $shopDomain;
$this->shop = $this->findShop();
}

/**
* Execute the job.
*
* @return bool
*/
public function handle()
{
if (!$this->shop) {
return false;
}

$this->softDeleteShop();
$this->cancelCharge();

return true;
}

/**
* Soft deletes the shop in the database.
*
* @return void
*/
protected function softDeleteShop()
{
$this->shop->delete();
$this->shop->charges()->delete();
}

/**
* Cancels a recurring or one-time charge.
*
* @return void
*/
protected function cancelCharge()
{
$lastCharge = $this->shop->charges()
->withTrashed()
->whereIn('type', [Charge::CHARGE_RECURRING, Charge::CHARGE_ONETIME])
->orderBy('created_at', 'desc')
->first();

if ($lastCharge && !$lastCharge->isDeclined() && !$lastCharge->isCancelled()) {
$lastCharge->status = 'cancelled';
$lastCharge->cancelled_on = Carbon::today()->format('Y-m-d');
$lastCharge->save();
}
}

/**
* Finds the shop based on domain from the webhook.
*
* @return Shop|null
*/
protected function findShop()
{
return Shop::where(['shopify_domain' => $this->shopDomain])->first();
}
}
3 changes: 2 additions & 1 deletion src/ShopifyApp/Middleware/AuthShop.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ public function handle(Request $request, Closure $next)
if (
$shop === null ||
($shopParam && $shopParam !== $shop->shopify_domain) === true ||
$shop->shopify_token === null
$shop->shopify_token === null ||
$shop->trashed()
) {
// Either no shop session or shops do not match
session()->forget('shopify_domain');
Expand Down
14 changes: 12 additions & 2 deletions src/ShopifyApp/Middleware/Billable.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Closure;
use Illuminate\Http\Request;
use OhMyBrew\ShopifyApp\Facades\ShopifyApp;
use OhMyBrew\ShopifyApp\Models\Charge;

class Billable
{
Expand All @@ -19,9 +20,18 @@ class Billable
public function handle(Request $request, Closure $next)
{
if (config('shopify-app.billing_enabled') === true) {
// Grab the shop and last recurring or one-time charge
$shop = ShopifyApp::shop();
if (!$shop->isPaid() && !$shop->isGrandfathered()) {
// No charge in database and they're not grandfathered in, redirect to billing
$lastCharge = $shop->charges()
->whereIn('type', [Charge::CHARGE_RECURRING, Charge::CHARGE_ONETIME])
->orderBy('created_at', 'desc')
->first();

if (
!$shop->isGrandfathered() &&
(is_null($lastCharge) || $lastCharge->isDeclined() || $lastCharge->isCancelled())
) {
// They're not grandfathered in, and there is no charge or charge was declined... redirect to billing
return redirect()->route('billing');
}
}
Expand Down
178 changes: 178 additions & 0 deletions src/ShopifyApp/Models/Charge.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
<?php

namespace OhMyBrew\ShopifyApp\Models;

use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Charge extends Model
{
use SoftDeletes;

// Types of charges
const CHARGE_RECURRING = 1;
const CHARGE_ONETIME = 2;
const CHARGE_USAGE = 3;
const CHARGE_CREDIT = 4;

/**
* The attributes that should be mutated to dates.
*
* @var array
*/
protected $dates = ['deleted_at'];

/**
* Gets the shop for the charge.
*
* @return OhMyBrew\ShopifyApp\Models\Shop
*/
public function shop()
{
return $this->belongsTo('OhMyBrew\ShopifyApp\Models\Shop');
}

/**
* Checks if the charge is a test.
*
* @return bool
*/
public function isTest()
{
return (bool) $this->test;
}

/**
* Checks if the charge is a type.
*
* @param int $type The charge type.
*
* @return bool
*/
public function isType(int $type)
{
return (int) $this->type === $type;
}

/**
* Checks if the charge is a trial-type charge.
*
* @return bool
*/
public function isTrial()
{
return !is_null($this->trial_ends_on);
}

/**
* Checks if the charge is currently in trial.
*
* @return bool
*/
public function isActiveTrial()
{
return $this->isTrial() && Carbon::today()->lte(Carbon::parse($this->trial_ends_on));
}

/**
* Returns the remaining trial days.
*
* @return int
*/
public function remainingTrialDays()
{
if (!$this->isTrial()) {
return;
}

return $this->isActiveTrial() ? Carbon::today()->diffInDays($this->trial_ends_on) : 0;
}

/**
* Returns the remaining trial days from cancellation date.
*
* @return int
*/
public function remainingTrialDaysFromCancel()
{
if (!$this->isTrial()) {
return;
}

$cancelledDate = Carbon::parse($this->cancelled_on);
$trialEndsDate = Carbon::parse($this->trial_ends_on);

// Ensure cancelled date happened before the trial was supposed to end
if ($this->isCancelled() && $cancelledDate->lte($trialEndsDate)) {
// Diffeence the two dates and subtract from the total trial days to get whats remaining
return $this->trial_days - ($this->trial_days - $cancelledDate->diffInDays($trialEndsDate));
}

return 0;
}

/**
* Returns the used trial days.
*
* @return int|null
*/
public function usedTrialDays()
{
if (!$this->isTrial()) {
return;
}

return $this->trial_days - $this->remainingTrialDays();
}

/**
* Checks if the charge is active.
*
* @return bool
*/
public function isActive()
{
return $this->status === 'active';
}

/**
* Checks if the charge was accepted (for one-time and reccuring).
*
* @return bool
*/
public function isAccepted()
{
return $this->status === 'accepted';
}

/**
* Checks if the charge was declined (for one-time and reccuring).
*
* @return bool
*/
public function isDeclined()
{
return $this->status === 'declined';
}

/**
* Checks if the charge was cancelled.
*
* @return bool
*/
public function isCancelled()
{
return !is_null($this->cancelled_on) || $this->status === 'cancelled';
}

/**
* Checks if the charge is "active" (non-API check).
*
* @return bool
*/
public function isOngoing()
{
return $this->isActive() && !$this->isCancelled();
}
}
Loading

0 comments on commit e55c3e8

Please sign in to comment.