From 9f756bdc3db976a208f321fbbb891ca9ad9f0cf6 Mon Sep 17 00:00:00 2001 From: Tyler King Date: Tue, 12 Jun 2018 00:29:13 -0230 Subject: [PATCH] Completed building most of requirements for billing controller.. todo: restore on trial --- src/ShopifyApp/Middleware/Billable.php | 2 +- src/ShopifyApp/Models/Charge.php | 18 +++++-- .../Traits/BillingControllerTrait.php | 47 ++++++++++++++----- tests/Controllers/BillingControllerTest.php | 10 ++-- tests/Middleware/BillableMiddlewareTest.php | 16 +++++++ tests/Models/ChargeModelTest.php | 10 +++- tests/TestCase.php | 29 +++++++++--- ...d13096715a537f9bb6e2ea2ac0d493a24344c.json | 18 +++---- 8 files changed, 111 insertions(+), 39 deletions(-) diff --git a/src/ShopifyApp/Middleware/Billable.php b/src/ShopifyApp/Middleware/Billable.php index 43310c56..0ce90c8d 100644 --- a/src/ShopifyApp/Middleware/Billable.php +++ b/src/ShopifyApp/Middleware/Billable.php @@ -29,7 +29,7 @@ public function handle(Request $request, Closure $next) $query->latestByType(Charge::CHARGE_ONETIME); })->latest()->first(); - if (!$shop->isGrandfathered() && (is_null($lastCharge) || $lastCharge->wasDeclined())) { + if (!$shop->isGrandfathered() && (is_null($lastCharge) || $lastCharge->isDeclined())) { // They're not grandfathered in, and there is no charge or charge was declined... redirect to billing return redirect()->route('billing'); } diff --git a/src/ShopifyApp/Models/Charge.php b/src/ShopifyApp/Models/Charge.php index 0120d60c..d508d0ed 100644 --- a/src/ShopifyApp/Models/Charge.php +++ b/src/ShopifyApp/Models/Charge.php @@ -96,7 +96,7 @@ public function isTrial() */ public function isActiveTrial() { - return $this->isTrial() && Carbon::now()->lte(Carbon::parse($this->trial_ends_on)); + return $this->isTrial() && Carbon::today()->lte(Carbon::parse($this->trial_ends_on)); } /** @@ -110,7 +110,7 @@ public function remainingTrialDays() return null; } - return $this->isActiveTrial() ? Carbon::now()->diffInDays($this->trial_ends_on) : 0; + return $this->isActiveTrial() ? Carbon::today()->diffInDays($this->trial_ends_on) : 0; } /** @@ -127,12 +127,22 @@ public function usedTrialDays() 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 wasAccepted() + public function isAccepted() { return $this->status === 'accepted'; } @@ -142,7 +152,7 @@ public function wasAccepted() * * @return bool */ - public function wasDeclined() + public function isDeclined() { return $this->status === 'declined'; } diff --git a/src/ShopifyApp/Traits/BillingControllerTrait.php b/src/ShopifyApp/Traits/BillingControllerTrait.php index 82102185..d0b2e7c3 100644 --- a/src/ShopifyApp/Traits/BillingControllerTrait.php +++ b/src/ShopifyApp/Traits/BillingControllerTrait.php @@ -4,6 +4,8 @@ use OhMyBrew\ShopifyApp\Facades\ShopifyApp; use OhMyBrew\ShopifyApp\Libraries\BillingPlan; +use OhMyBrew\ShopifyApp\Models\Charge; +use Carbon\Carbon; trait BillingControllerTrait { @@ -38,23 +40,46 @@ public function process() // Setup the plan and get the charge $plan = new BillingPlan($shop, $this->chargeType()); $plan->setChargeId($chargeId); + $status = $plan->getCharge()->status; - // Check the customer's answer to the billing - $charge = $plan->getCharge(); - if ($charge->status == 'accepted') { - // Customer accepted, activate the charge - $plan->activate(); + // Grab the plan detailed used + $planDetails = $this->planDetails(); + unset($planDetails['return_url']); - // Save the charge ID to the shop - $shop->charge_id = $chargeId; - $shop->save(); + // Create a charge (regardless of the status) + $charge = new Charge(); + $charge->type = $this->chargeType() === 'recurring' ? Charge::CHARGE_RECURRING : Charge::CHARGE_ONETIME; + $charge->charge_id = $chargeId; + $charge->status = $status; - // Go to homepage of app - return redirect()->route('home'); + // Check the customer's answer to the billing + if ($status === 'accepted') { + // Activate and add details to our charge + $response = $plan->activate(); + $charge->status = $response->status; + $charge->billing_on = $response->billing_on; + $charge->trial_ends_on = $response->trial_ends_on; + $charge->activated_on = $response->activated_on; } else { - // Customer declined the charge, abort + // Customer declined the charge + $charge->cancelled_on = Carbon::today()->format('Y-m-d'); + } + + // Merge in the plan details since the fields match the database columns + foreach ($planDetails as $key => $value) { + $charge->{$key} = $value; + } + + // Save and link to the shop + $shop->charges()->save($charge); + + if ($status === 'declined') { + // Show the error... don't allow access return abort(403, 'It seems you have declined the billing charge for this application'); } + + // All good... go to homepage of app + return redirect()->route('home'); } /** diff --git a/tests/Controllers/BillingControllerTest.php b/tests/Controllers/BillingControllerTest.php index 20bcf47c..081db981 100644 --- a/tests/Controllers/BillingControllerTest.php +++ b/tests/Controllers/BillingControllerTest.php @@ -1,5 +1,4 @@ first(); - $this->assertEquals(678298290, $shop->charge_id); // Based on seedDatabase() - $response = $this->call('get', '/billing/process', ['charge_id' => 1029266947]); - $shop = $shop->fresh(); // Reload model $response->assertStatus(302); - $this->assertEquals(1029266947, $shop->charge_id); + $this->assertEquals(1029266947, $shop->charges()->get()->last()->charge_id); } public function testShopDeclinesBilling() { + $shop = Shop::where('shopify_domain', 'example.myshopify.com')->first(); $response = $this->call('get', '/billing/process', ['charge_id' => 10292]); + $lastCharge = $shop->charges()->get()->last(); $response->assertStatus(403); + $this->assertEquals(10292, $lastCharge->charge_id); + $this->assertEquals('declined', $lastCharge->status); $this->assertEquals( 'It seems you have declined the billing charge for this application', $response->exception->getMessage() diff --git a/tests/Middleware/BillableMiddlewareTest.php b/tests/Middleware/BillableMiddlewareTest.php index 11adfd37..d723eb0d 100644 --- a/tests/Middleware/BillableMiddlewareTest.php +++ b/tests/Middleware/BillableMiddlewareTest.php @@ -23,6 +23,22 @@ public function testEnabledBillingWithUnpaidShop() $this->assertEquals(true, strpos($result, 'Redirecting to http://localhost/billing') !== false); } + public function testEnabledBillingWithShopWhoDeclinedCharges() + { + // Enable billing and set a shop + config(['shopify-app.billing_enabled' => true]); + session(['shopify_domain' => 'trashed-shop.myshopify.com']); + + $called = false; + $result = (new Billable())->handle(request(), function ($request) use (&$called) { + // Should never be called + $called = true; + }); + + $this->assertFalse($called); + $this->assertEquals(true, strpos($result, 'Redirecting to http://localhost/billing') !== false); + } + public function testEnabledBillingWithPaidShop() { // Enable billing and set a shop diff --git a/tests/Models/ChargeModelTest.php b/tests/Models/ChargeModelTest.php index 2d0f3f8e..09ba6e1e 100644 --- a/tests/Models/ChargeModelTest.php +++ b/tests/Models/ChargeModelTest.php @@ -80,7 +80,13 @@ public function testUsedTrialDays() public function testAcceptedAndDeclined() { - $this->assertTrue(Charge::find(1)->wasAccepted()); - $this->assertTrue(!Charge::find(1)->wasDeclined()); + $this->assertTrue(Charge::find(1)->isAccepted()); + $this->assertFalse(Charge::find(1)->isDeclined()); + } + + public function testActive() + { + $this->assertFalse(Charge::find(1)->isActive()); + $this->assertTrue(Charge::find(4)->isActive()); } } diff --git a/tests/TestCase.php b/tests/TestCase.php index cc7009fa..90a07f18 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -100,7 +100,8 @@ protected function createShops() } public function createCharges() - {; + { + // Test = true, status = accepted, trial = 7, active trial = no $charge = new Charge(); $charge->charge_id = 98298298; $charge->test = true; @@ -109,43 +110,57 @@ public function createCharges() $charge->type = 1; $charge->price = 15.00; $charge->trial_days = 7; - $charge->trial_ends_on = Carbon::createFromDate(2018, 6, 3, 'UTC')->addWeeks(1); + $charge->trial_ends_on = Carbon::createFromDate(2018, 6, 3, 'UTC')->addWeeks(1)->format('Y-m-d'); $charge->shop_id = Shop::where('shopify_domain', 'example.myshopify.com')->first()->id; $charge->save(); + // Test = false, status = active, trial = 7, active trial = yes $charge = new Charge(); $charge->charge_id = 67298298; $charge->test = false; $charge->name = 'Base Plan'; - $charge->status = 'accepted'; + $charge->status = 'active'; $charge->type = 1; $charge->price = 25.00; $charge->trial_days = 7; - $charge->trial_ends_on = Carbon::now()->addDays(2); + $charge->trial_ends_on = Carbon::today()->addDays(2)->format('Y-m-d'); $charge->shop_id = Shop::where('shopify_domain', 'example.myshopify.com')->first()->id; $charge->save(); + // Test = false, status = active, trial = 7, active trial = no $charge = new Charge(); $charge->charge_id = 78378873; $charge->test = false; $charge->name = 'Base Plan Old'; - $charge->status = 'accepted'; + $charge->status = 'active'; $charge->type = 1; $charge->price = 25.00; $charge->trial_days = 7; - $charge->trial_ends_on = Carbon::now()->subWeeks(4); + $charge->trial_ends_on = Carbon::today()->subWeeks(4)->format('Y-m-d'); $charge->shop_id = Shop::where('shopify_domain', 'example.myshopify.com')->first()->id; $charge->save(); + // Test = false, status = active, trial = 0 $charge = new Charge(); $charge->charge_id = 89389389; $charge->test = false; $charge->name = 'Base Plan Old Non-Trial'; - $charge->status = 'accepted'; + $charge->status = 'active'; $charge->type = 1; $charge->price = 25.00; $charge->trial_days = 0; $charge->shop_id = Shop::where('shopify_domain', 'example.myshopify.com')->first()->id; $charge->save(); + + // Test = false, status = declined, trial = 7, active trial = true + $charge = new Charge(); + $charge->charge_id = 78378378378; + $charge->test = false; + $charge->name = 'Base Plan Declined'; + $charge->status = 'declined'; + $charge->type = 1; + $charge->price = 25.00; + $charge->shop_id = Shop::where('shopify_domain', 'no-token-shop.myshopify.com')->first()->id; + $charge->save(); } } diff --git a/tests/fixtures/602d13096715a537f9bb6e2ea2ac0d493a24344c.json b/tests/fixtures/602d13096715a537f9bb6e2ea2ac0d493a24344c.json index bbe4d18d..e692b417 100644 --- a/tests/fixtures/602d13096715a537f9bb6e2ea2ac0d493a24344c.json +++ b/tests/fixtures/602d13096715a537f9bb6e2ea2ac0d493a24344c.json @@ -1,19 +1,19 @@ { "recurring_application_charge": { - "id": 1029266947, + "id": 455696195, "name": "Super Mega Plan", "api_client_id": 755357713, "price": "15.00", - "status": "accepted", - "return_url": "http:\/\/yourapp.com", - "billing_on": "2017-08-17", - "created_at": "2017-08-17T15:16:08-04:00", - "updated_at": "2017-08-17T15:29:50-04:00", + "status": "active", + "return_url": "http://yourapp.com", + "billing_on": "2018-06-06", + "created_at": "2018-05-07T15:33:38-04:00", + "updated_at": "2018-05-07T15:47:13-04:00", "test": null, - "activated_on": null, - "trial_ends_on": null, + "activated_on": "2018-05-07", + "trial_ends_on": "2018-05-07", "cancelled_on": null, "trial_days": 0, - "decorated_return_url": "http:\/\/yourapp.com?charge_id=1029266947" + "decorated_return_url": "http://yourapp.com?charge_id=455696195" } } \ No newline at end of file