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

Refactor Checkouts to API calls #3

Merged
merged 6 commits into from
Apr 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 26 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,12 @@ LEMON_SQUEEZY_API_KEY=your-lemon-squeezy-api-key

When you're deploying your app to production, you'll have to create a new key in production mode to work with live data.

### Store URL
### Store Identifier

Your store url will be used when creating checkouts for your products. Go to [your Lemon Squeezy general settings](https://app.lemonsqueezy.com/settings/general) and copy the Store URL subdomain (the part before `.lemonsqueezy.com`) into the env value below:
Your store identifier will be used when creating checkouts for your products. Go to [your Lemon Squeezy general settings](https://app.lemonsqueezy.com/settings/general) and copy the Store ID (the part before `#` sign) into the env value below:

```ini
LEMON_SQUEEZY_STORE=your-lemon-squeezy-subdomain
LEMON_SQUEEZY_STORE=your-lemon-squeezy-store-id
```

### Billable Model
Expand Down Expand Up @@ -136,22 +136,22 @@ With this package, you can easily create checkouts for your customers.

### Single Payments

For example, to create a checkout for a single-payment, click the "share" button of the product you want to share, copy its UUID from the share url and create a checkout using the snippet below:
For example, to create a checkout for a single-payment, use a variant ID of a product variant you want to sell and create a checkout using the snippet below:

```php
use Illuminate\Http\Request;

Route::get('/buy', function (Request $request) {
return response()->redirect(
$request->user()->checkout('your-product-uuid')
$request->user()->checkout('variant-id')
);
});
```

This will automatically redirect your customer to a Lemon Squeezy checkout where the customer can buy your product.
This will automatically redirect your customer to a Lemon Squeezy checkout where the customer can buy your product.

> **Note**
> Please note that the product UUID is not the same as the variant ID.
> **Note**
> When creating a checkout for your store, each time you redirect a checkout object or call `url` on the checkout object, an API call to Lemon Squeezy will be made. These calls are expensive and can be time and resource consuming for your app. If you are creating the same session over and over again you may want to cache these urls.

### Overlay Widget

Expand All @@ -161,7 +161,7 @@ Instead of redirecting your customer to a checkout screen, you can also create a
use Illuminate\Http\Request;

Route::get('/buy', function (Request $request) {
$checkout = $request->user()->checkout('your-product-uuid');
$checkout = $request->user()->checkout('variant-id');

return view('billing', ['checkout' => $checkout]);
});
Expand All @@ -175,14 +175,6 @@ Now, create the button using the shipped Laravel Blade component from the packag
</x-lemon-button>
```

When a user clicks this button, it'll trigger the Lemon Squeezy checkout overlay. You can also, optionally request it to be rendered in dark mode:

```blade
<x-lemon-button :href="$checkout" class="px-8 py-4" dark>
Buy Product
</x-lemon-button>
```

### Prefill User Data

You can easily prefill user data for checkouts by overwriting the following methods on your billable model:
Expand All @@ -191,7 +183,6 @@ You can easily prefill user data for checkouts by overwriting the following meth
public function lemonSqueezyName(): ?string; // name
public function lemonSqueezyEmail(): ?string; // email
public function lemonSqueezyCountry(): ?string; // country
public function lemonSqueezyState(): ?string; // state
public function lemonSqueezyZip(): ?string; // zip
public function lemonSqueezyTaxNumber(): ?string; // tax_number
```
Expand All @@ -205,10 +196,10 @@ use Illuminate\Http\Request;

Route::get('/buy', function (Request $request) {
return response()->redirect(
$request->user()->checkout('your-product-uuid')
$request->user()->checkout('variant-id')
->withName('John Doe')
->withEmail('john@example.com')
->withBillingAddress('US', 'NY', '10038')
->withBillingAddress('US', '10038') // Country & Zip Code
->withTaxNumber('123456679')
->withDiscountCode('PROMO')
);
Expand All @@ -217,7 +208,18 @@ Route::get('/buy', function (Request $request) {

### Redirects After Purchase

After a purchase the customer will be redirected to your Lemon Squeezy's store. If you want them to be redirected back to your app, you'll have to configure the url in your settings in your Lemon Squeezy dashboard for each individual product.
To redirect customers back to your app after purchase, you may use the `redirectTo` method:

```php
$request->user()->checkout('variant-id')
->redirectTo(url('/'));
```

You may also set a default url for this by configuring the `lemon-squeezy.redirect_url` in your config file:

```php
'redirect_url' => 'https://my-app.com/dashboard',
```

### Custom Data

Expand All @@ -228,7 +230,7 @@ use Illuminate\Http\Request;

Route::get('/buy', function (Request $request) {
return response()->redirect(
$request->user()->checkout('your-product-uuid', custom: ['foo' => 'bar'])
$request->user()->checkout('variant-id', custom: ['foo' => 'bar'])
);
});
```
Expand Down Expand Up @@ -257,14 +259,14 @@ This gives you the advantage later on to make use of the `hasProduct` method on

### Creating Subscriptions

Starting subscriptions is easy. For this, we need the UUID from our product. Click the "share" button of the subscription product you want to share, copy its UUID from the share url and initiate a new subscription checkout from your billable model:
Starting subscriptions is easy. For this, we need the variant id from our product. Copy the variant id and initiate a new subscription checkout from your billable model:

```php
use Illuminate\Http\Request;

Route::get('/buy', function (Request $request) {
return response()->redirect(
$request->user()->subscribe('your-product-uuid')
$request->user()->subscribe('variant-id')
);
});
```
Expand Down
19 changes: 16 additions & 3 deletions config/lemon-squeezy.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,27 @@
| Lemon Squeezy Store
|--------------------------------------------------------------------------
|
| This is the URL to your Lemon Squeezy store. You can find your store
| URL in the Lemon Squeezy dashboard. The entered value should be the
| subdomain of your store URL right before the .lemonsqueezy.com part.
| This is the ID of your Lemon Squeezy store. You can find your store
| ID in the Lemon Squeezy dashboard. The entered value should be the
| part after the # sign.
|
*/

'store' => env('LEMON_SQUEEZY_STORE'),

/*
|--------------------------------------------------------------------------
| Default Redirect URL
|--------------------------------------------------------------------------
|
| This is the default redirect URL that will be used when a customer
| is redirected back to your application after completing a purchase
| from a checkout session in your Lemon Squeezy store.
|
*/

'redirect_url' => null,

/*
|--------------------------------------------------------------------------
| Lemon Squeezy Products
Expand Down
2 changes: 2 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,7 @@
</testsuites>
<php>
<env name="DB_CONNECTION" value="testing"/>
<env name="LEMON_SQUEEZY_API_KEY" value="fake"/>
<env name="LEMON_SQUEEZY_STORE" value="fake"/>
</php>
</phpunit>
4 changes: 2 additions & 2 deletions resources/views/components/button.blade.php
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
@props(['href', 'dark' => false])
@props(['href'])

@php($href = $href instanceof LemonSqueezy\Laravel\Checkout ? $href->url() : $href)

<a
href="{!! $href !!}{!! str_contains($href, '?') ? '&' : '?' !!}embed=1{!! $dark ? '&dark=1' : '' !!}"
href="{!! $href !!}"
{{ $attributes->merge(['class' => 'lemonsqueezy-button']) }}
>
{{ $slot }}
Expand Down
78 changes: 53 additions & 25 deletions src/Checkout.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ class Checkout implements Responsable

private bool $desc = true;

private bool $code = true;
private bool $discount = true;

private array $fields = [];
private array $checkoutData = [];

private array $custom = [];

private ?string $redirectUrl;

public function __construct(private string $store, private string $variant)
{
}
Expand Down Expand Up @@ -51,32 +53,31 @@ public function withoutDescription(): self
return $this;
}

public function withoutCode(): self
public function withoutDiscountField(): self
{
$this->code = false;
$this->discount = false;

return $this;
}

public function withName(string $name): self
{
$this->fields['name'] = $name;
$this->checkoutData['name'] = $name;

return $this;
}

public function withEmail(string $email): self
{
$this->fields['email'] = $email;
$this->checkoutData['email'] = $email;

return $this;
}

public function withBillingAddress(string $country, string $state = null, string $zip = null): self
public function withBillingAddress(string $country, string $zip = null): self
{
$this->fields['billing_address'] = array_filter([
$this->checkoutData['billing_address'] = array_filter([
'country' => $country,
'state' => $state,
'zip' => $zip,
]);

Expand All @@ -85,14 +86,14 @@ public function withBillingAddress(string $country, string $state = null, string

public function withTaxNumber(string $taxNumber): self
{
$this->fields['tax_number'] = $taxNumber;
$this->checkoutData['tax_number'] = $taxNumber;

return $this;
}

public function withDiscountCode(string $discountCode): self
{
$this->fields['discount_code'] = $discountCode;
$this->checkoutData['discount_code'] = $discountCode;

return $this;
}
Expand All @@ -115,24 +116,51 @@ public function withCustomData(array $custom): self
return $this;
}

public function url(): string
public function redirectTo(string $url): self
{
$params = collect(['logo', 'media', 'desc', 'code'])
->filter(fn ($toggle) => ! $this->{$toggle})
->mapWithKeys(fn ($toggle) => [$toggle => 0])
->all();
$this->redirectUrl = $url;

if ($this->fields) {
$params['checkout'] = array_filter($this->fields);
}

if ($this->custom) {
$params['checkout']['custom'] = $this->custom;
}
return $this;
}

$params = ! empty($params) ? '?'.http_build_query($params) : '';
public function url(): string
{
$response = LemonSqueezy::api('POST', 'checkouts', [
'data' => [
'type' => 'checkouts',
'attributes' => [
'checkout_data' => array_merge(
array_filter($this->checkoutData, fn ($value) => $value !== ''),
['custom' => $this->custom]
),
'checkout_options' => [
'logo' => $this->logo,
'media' => $this->media,
'desc' => $this->desc,
'discount' => $this->discount,
],
'product_options' => [
'redirect_url' => $this->redirectUrl ?? config('lemon-squeezy.redirect_url'),
],
],
'relationships' => [
'store' => [
'data' => [
'type' => 'stores',
'id' => $this->store,
],
],
'variant' => [
'data' => [
'type' => 'variants',
'id' => $this->variant,
],
],
],
],
]);

return "https://{$this->store}.lemonsqueezy.com/checkout/buy/{$this->variant}".$params;
return $response['data']['attributes']['url'];
}

public function redirect(): RedirectResponse
Expand Down
3 changes: 1 addition & 2 deletions src/Concerns/ManagesCheckouts.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public function checkout(string $variant, array $options = [], array $custom = [
// we make an API request we'll attach the authentication identifier to this
// checkout so we can match it back to a user when handling Lemon Squeezy webhooks.
$custom = array_merge($custom, [
'billable_id' => $this->getKey(),
'billable_id' => (string) $this->getKey(),
'billable_type' => $this->getMorphClass(),
]);

Expand All @@ -25,7 +25,6 @@ public function checkout(string $variant, array $options = [], array $custom = [
->withEmail($options['email'] ?? (string) $this->lemonSqueezyEmail())
->withBillingAddress(
$options['country'] ?? (string) $this->lemonSqueezyCountry(),
$options['state'] ?? (string) $this->lemonSqueezyState(),
$options['zip'] ?? (string) $this->lemonSqueezyZip(),
)
->withTaxNumber($options['tax_number'] ?? (string) $this->lemonSqueezyTaxNumber())
Expand Down
8 changes: 0 additions & 8 deletions src/Concerns/ManagesCustomer.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,6 @@ public function lemonSqueezyCountry(): ?string
return $this->country ?? null; // 'US'
}

/**
* Get the billable model's state to associate with Lemon Squeezy.
*/
public function lemonSqueezyState(): ?string
{
return $this->state ?? null; // 'NY'
}

/**
* Get the billable model's zip code to associate with Lemon Squeezy.
*/
Expand Down
Loading