Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adjust for Paddle Billing v2 #100

Merged
merged 16 commits into from
Apr 1, 2024
Merged
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
JWT_SECRET=Jrsweag3Mf0srOqDizRkhjWm5CEFcrBy

PADDLE_VENDOR_ID=
PADDLE_VENDOR_AUTH_CODE=
PADDLE_API_KEY=
PADDLE_ENV=sandbox
PADDLE_PUBLIC_KEY=

Expand Down
4 changes: 3 additions & 1 deletion config/wave.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@

'paddle' => [
'vendor' => env('PADDLE_VENDOR_ID', ''),
'auth_code' => env('PADDLE_VENDOR_AUTH_CODE', ''),
'auth_code' => env('PADDLE_API_KEY', ''),
'api_key' => env('PADDLE_API_KEY', ''),
'client_side_token' => env('PADDLE_CLIENT_SIDE_TOKEN', ''),
'env' => env('PADDLE_ENV', 'sandbox'),
'public_key' => env('PADDLE_PUBLIC_KEY', ''),
]
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4,270 changes: 4,269 additions & 1 deletion public/themes/tailwind/css/app.css

Large diffs are not rendered by default.

9,958 changes: 9,956 additions & 2 deletions public/themes/tailwind/js/app.js

Large diffs are not rendered by default.

46 changes: 0 additions & 46 deletions resources/views/themes/tailwind/assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,52 +188,6 @@ window.popToast = function(type, message){

/********** END TOAST FUNCTIONALITY **********/

/********** Start Billing Checkout Functionality ***********/

/***** Payment Success Functionality */

window.checkoutComplete = function(data) {
var checkoutId = data.checkout.id;

Paddle.Order.details(checkoutId, function(data) {
// Order data, downloads, receipts etc... available within 'data' variable.
document.getElementById('fullscreenLoaderMessage').innerText = 'Finishing Up Your Order';
document.getElementById('fullscreenLoader').classList.remove('hidden');
axios.post('/checkout', { _token: csrf, checkout_id: data.checkout.checkout_id })
.then(function (response) {
console.log(response);
if(parseInt(response.data.status) == 1){
let queryParams = '';
if(parseInt(response.data.guest) == 1){
queryParams = '?complete=true';
}
window.location = '/checkout/welcome' + queryParams;
}
});
});
}

window.checkoutUpdate = function(data){
if(data.checkout.completed){
popToast('success', 'Your payment info has been successfully updated.');
} else {
popToast('danger', 'Sorry, there seems to be a problem updating your payment info');
}
}

window.checkoutCancel = function(data){
let subscriptionId = data.checkout.id;
axios.post('/cancel', { _token: csrf, id: subscriptionId })
.then(function (response) {
if(parseInt(response.data.status) == 1){
window.location = '/settings/subscription';
}
});
}

/***** End Payment Success Functionality */

/********** End Billing Checkout Functionality ***********/

/********** Switch Plans Button Click ***********/

Expand Down
27 changes: 22 additions & 5 deletions resources/views/themes/tailwind/partials/cancel-modal.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,9 @@
</div>
<div class="mt-5 sm:mt-6 sm:flex sm:flex-row-reverse">
<span class="flex flex-1 w-full rounded-md shadow-sm sm:ml-3 sm:w-full">
<div data-url="{{ auth()->user()->subscription->cancel_url }}" @click="$store.confirmCancel.open=false" class="inline-flex justify-center w-full px-4 py-2 text-base font-medium leading-6 text-white transition duration-150 ease-in-out bg-red-600 border border-transparent rounded-md shadow-sm cursor-pointer checkout-cancel hover:bg-red-500 focus:outline-none focus:border-red-700 focus:shadow-outline-red sm:text-sm sm:leading-5">
Cancel Subscription
</div>

<div data-url="{{ auth()->user()->subscription->cancel_url }}" id="cancelSubscriptionButton" class="inline-flex justify-center w-full px-4 py-2 text-base font-medium leading-6 text-white transition duration-150 ease-in-out bg-red-600 border border-transparent rounded-md shadow-sm cursor-pointer checkout-cancel hover:bg-red-500 focus:outline-none focus:border-red-700 focus:shadow-outline-red sm:text-sm sm:leading-5">
Cancel Subscription
</div>
</span>
<span class="flex flex-1 w-full mt-3 rounded-md shadow-sm sm:mt-0 sm:w-full">
<button onclick="closeCancelModal()" type="button" class="inline-flex justify-center w-full px-4 py-2 text-base font-medium leading-6 text-gray-700 transition duration-150 ease-in-out bg-white border border-gray-300 rounded-md shadow-sm hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue sm:text-sm sm:leading-5">
Expand All @@ -50,4 +49,22 @@
window.closeCancelModal = function(){
Alpine.store('confirmCancel').close();
}
</script>
document.getElementById('cancelSubscriptionButton').addEventListener('click', function() {
fetch('/cancel', {
method: 'POST',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
'Content-Type': 'application/json'
},
body: JSON.stringify({ subscription_id: this.getAttribute('data-url') })
})
.then(response => response.json())
.then(data => {
console.log('Success:', data);
closeCancelModal();
})
.catch(error => {
console.error('Error:', error);
});
});
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
@subscriber
@php
$subscription = new \Wave\Http\Controllers\SubscriptionController;
$invoices = $subscription->invoices( auth()->user() );
$transactions = $subscription->transactions( auth()->user() );
@endphp



@if(isset($invoices->success) && $invoices->success == true)
@if(count($transactions) > 0)

<table class="min-w-full overflow-hidden divide-y divide-gray-200 rounded-lg">
<thead>
Expand All @@ -25,17 +25,17 @@
</tr>
</thead>
<tbody>
@foreach($invoices->response as $invoice)
@foreach($transactions as $transaction)
<tr class="@if($loop->index%2 == 0){{ 'bg-gray-50' }}@else{{ 'bg-gray-100' }}@endif">
<td class="px-6 py-4 text-sm font-medium leading-5 text-gray-900 whitespace-no-wrap">
{{ Carbon\Carbon::parse($invoice->payout_date)->toFormattedDateString() }}
{{ Carbon\Carbon::parse($transaction->created_at)->toFormattedDateString() }}
</td>
<td class="px-6 py-4 text-sm font-medium leading-5 text-right text-gray-900 whitespace-no-wrap">
${{ $invoice->amount }}
{{ $transaction->details->totals->subtotal }}
</td>
<td class="px-6 py-4 text-sm font-medium leading-5 text-right whitespace-no-wrap">
<a href="{{ $invoice->receipt_url }}" target="_blank" class="mr-2 text-indigo-600 hover:underline focus:outline-none">
Download
<a href="/settings/invoices/{{ $transaction->id }}" target="_blank" class="mr-2 text-indigo-600 hover:underline focus:outline-none">
Generate Invoice
</a>
</td>

Expand All @@ -45,7 +45,7 @@
</table>

@else
<p>Sorry, there seems to be an issue retrieving your invoices or you may not have any invoices yet.</p>
<p>You currently do not have any invoices associated with your account</p>
@endif

@notsubscriber
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<div class="flex flex-col">
<h5 class="mb-2 text-xl font-bold text-gray-700">Modify Payment Information</h5>
<p>Click the button below to update your default payment method</p>
<button data-url="{{ auth()->user()->subscription->update_url }}" class="inline-flex self-start justify-center w-auto px-4 py-2 mt-5 text-sm font-medium text-white transition duration-150 ease-in-out border border-transparent rounded-md checkout-update bg-wave-600 hover:bg-wave-500 focus:outline-none focus:border-wave-700 focus:shadow-outline-wave active:bg-wave-700">Update Payment Info</button>
<a href="{{ auth()->user()->subscription->update_url }}" class="inline-flex self-start justify-center w-auto px-4 py-2 mt-5 text-sm font-medium text-white transition duration-150 ease-in-out border border-transparent rounded-md checkout-update bg-wave-600 hover:bg-wave-500 focus:outline-none focus:border-wave-700 focus:shadow-outline-wave active:bg-wave-700">Update Payment Info</a>
</div>

<hr class="my-8 border-gray-200">
Expand All @@ -29,4 +29,4 @@
window.cancelClicked = function(){
Alpine.store('confirmCancel').openModal();
}
</script>
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class ChangeSubscriptionIdToString extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('paddle_subscriptions', function (Blueprint $table) {
// Change subscription_id and plan_id columns to string type
$table->string('subscription_id', 255)->change();
$table->string('plan_id', 255)->change();
// Adjusting the length of the cancel_url and update_url columns to 500 (or a value that suits your needs)
$table->text('cancel_url')->change();
$table->text('update_url')->change();
});
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('paddle_subscriptions', function (Blueprint $table) {
// Revert back to integer type if needed
$table->integer('subscription_id')->change();
$table->integer('plan_id')->change();
// Reverting the length back to 255
$table->string('cancel_url', 255)->change();
$table->string('update_url', 255)->change();
});
}
}
3 changes: 2 additions & 1 deletion wave/docs/configurations.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ return [

'paddle' => [
'vendor' => env('PADDLE_VENDOR_ID', ''),
'auth_code' => env('PADDLE_VENDOR_AUTH_CODE', ''),
'auth_code' => env('PADDLE_API_KEY', ''),
'client_side_token' => env('PADDLE_CLIENT_SIDE_TOKEN', ''),
'env' => env('PADDLE_ENV', 'sandbox')
]

Expand Down
29 changes: 25 additions & 4 deletions wave/docs/features/billing.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ In order to integrate your application with Paddle you will need to signup for a

After you have created your Paddle Account you'll be able to login and see your dashboard, which should look similar to the following:

![paddle-dashboard.png](https://cdn.devdojo.com/images/april2021/paddle-dashboard.png)
![paddle-dashboard.png](https://imgur.com/SyNZ0W9.png)

Next, let's add your Paddle API credentials.

Expand All @@ -24,18 +24,39 @@ Next, let's add your Paddle API credentials.

Inside of your Paddle Dashboard you'll see a button under the **Developer Tools** menu, called **Authentication**, click on that button to get your API Authentication Credentials.

![paddle-authentication.png](https://cdn.devdojo.com/images/april2021/paddle-authentication.png)
![paddle-authentication.png](https://imgur.com/xdDuVKn.png)

On this page you'll find your **Vendor ID** and your **API Auth Code**. These are the credentials that you will need to add to your `.env` file for `PADDLE_VENDOR_ID` and `PADDLE_VENDOR_AUTH_CODE`:
Along with the **API Auth Code**, you'll also need to get your **Client Side Token**.

On this page you'll find your **Seller ID** and your **API Auth Code**. These are the credentials that you will need to add to your `.env` file for `PADDLE_VENDOR_ID`, `PADDLE_API_KEY` and `PADDLE_CLIENT_SIDE_TOKEN`:

```
PADDLE_VENDOR_ID=9999
PADDLE_VENDOR_AUTH_CODE=YOUR_REALLY_LONG_API_KEY_HERE
PADDLE_API_KEY=YOUR_REALLY_API_KEY_HERE
PADDLE_CLIENT_SIDE_TOKEN=YOUR_CLIENT_SIDE_TOKEN
PADDLE_ENV=sandbox
```

After adding these credentials, your application has been successfully configured with Paddle.

## Default payment link

Wave uses the default Paddle payment link to handle the payment process. You have to set up the default payment link in your Paddle account. To do this, go to your Paddle dashboard and click on **Checkout Settings** scroll down to **Payment Links**.

The default payment link should be set to `http://yourdomain.com/settings/subscription`.

![](https://imgur.com/zboWobt.png)

## Webhooks

Wave uses Paddle webhooks to handle the payment process. You have to set up the webhooks in your Paddle account. To do this, go to your Paddle dashboard and click on **Developer Tools** -> **Notifications**.

![](https://imgur.com/QqJTggu.png)

Make sure to select the `subscription.cancelled` event so that Wave can handle the subscription cancellation process in case a user cancels their subscription or their payment fails.

> **Note**: Wave currently only supports the `subscription.cancelled` event. More events will be supported in the future.

#### Ready to go Live?

When you are ready to go live and take live payments you'll want to change the `PADDLE_ENV` from `sandbox` to `live`, and you'll be ready to accept live payments 💵
Expand Down
29 changes: 21 additions & 8 deletions wave/docs/features/subscription-plans.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,33 @@ Fill out the rest of the info on the plan and click `Save` to create your new pl
<a name="create-plans-paddle"></a>
### Creating Plans in Paddle

To create a new plan in Paddle, login to your dashboard and click **Catalog**->**Subscription Plans**. Click on the **+ New Plan** button at the top right to create a new plan.
To create a new plan in Paddle, login to your dashboard and click **Catalog**->**Products**. Click on the **New Product** button at the top right to create a new plan.

![paddle-plans-01.png](https://cdn.devdojo.com/images/april2021/paddle-plans-01.png)
![paddle-plans-01.png](https://imgur.com/PL8mO1n.png)

You'll see a pop-up that will ask for the plan name, icon, and price. Fill out the info for your plan.
You'll see a pop-up that will ask for the plan name and a description. Fill out the info for your plan.

![paddle-plans-02.png](https://cdn.devdojo.com/images/april2021/paddle-plans-02.png)
![paddle-plans-02.png](https://imgur.com/J4fKEYe.png)

Scroll down to the bottom and click the **Save Plan** button.
![paddle-plans-03.png](https://cdn.devdojo.com/images/april2021/paddle-plans-03.png)
Click the **Save Plan** button on the top right to save your new plan.

After creating your new plan, you'll see the **Plan ID** you need to associate with the Wave Plan you create from the previous step.
After creating your new plan, you'll need to create a new subscription as part of the plan. Click on the **New Price** button to create a new subscription.

![paddle-plans-04.png](https://cdn.devdojo.com/images/april2021/paddle-plans-04.png)
![](https://imgur.com/f2ropW0.png)

Fill out the info for your subscription, make sure to select **Recurring** for the billing type and click the **Save** button on the top right to save your new subscription.

![](https://imgur.com/SQPN1YB.png)

Next, get the subscription ID for your new subscription. It starts with `pri_` and is located under the **Subscription ID** column.

![](https://imgur.com/fRxW2yF.png)

Make sure to copy this ID as you will need it to associate the plan with the subscription in Wave.

Note that you should not use the `Product ID` for the plan, but the `Subscription ID` for the subscription.

Next, go back to your Wave dashboard and click on the plan you created. You will see a field called **Plan ID**. Paste the subscription ID you copied from Paddle into this field and click **Save**.

After adding all your plans, we're ready to [test out the billing process](/docs/features/billing#test-billing).

Expand Down