Skip to content

Laravel Cashier (Paddle)

소개

exclamation

이 문서는 Paddle Billing과 통합된 Cashier Paddle 2.x용입니다. 여전히 Paddle Classic을 사용 중이라면 Cashier Paddle 1.x를 사용해야 합니다.

Laravel Cashier PaddlePaddle의 구독 청구 서비스에 대한 표현적이고 유연한 인터페이스를 제공합니다. 이를 통해 여러분이 꺼리는 대부분의 상용구 구독 청구 코드를 처리합니다. Cashier는 기본적인 구독 관리 외에도 구독 스와핑, 구독 "수량", 구독 일시 중지, 취소 유예 기간 등을 처리할 수 있습니다.

Cashier Paddle을 자세히 살펴보기 전에 Paddle의 개념 가이드API 문서를 검토하는 것이 좋습니다.

Cashier 업그레이드

새로운 버전의 Cashier로 업그레이드할 때는 업그레이드 가이드를 주의 깊게 검토하는 것이 중요합니다.

설치

먼저 Composer 패키지 관리자를 사용하여 Paddle용 Cashier 패키지를 설치합니다.

composer require laravel/cashier-paddle

다음으로, vendor:publish Artisan 명령어를 사용하여 Cashier 마이그레이션 파일을 게시해야 합니다:

php artisan vendor:publish --tag="cashier-migrations"

그런 다음, 애플리케이션의 데이터베이스 마이그레이션을 실행해야 합니다. Cashier 마이그레이션은 새로운 customers 테이블을 생성합니다. 또한, 고객의 모든 구독을 저장하기 위해 새로운 subscriptionssubscription_items 테이블이 생성됩니다. 마지막으로, 고객과 관련된 모든 Paddle 거래를 저장하기 위해 새로운 transactions 테이블이 생성됩니다:

php artisan migrate
exclamation

Cashier가 모든 Paddle 이벤트를 적절하게 처리하도록 하려면, Cashier의 웹훅 처리 설정을 기억해야 합니다.

Paddle 샌드박스

로컬 및 스테이징 개발 중에는 Paddle 샌드박스 계정을 등록해야 합니다. 이 계정은 실제 결제 없이 애플리케이션을 테스트하고 개발할 수 있는 샌드박스 환경을 제공합니다. Paddle의 테스트 카드 번호를 사용하여 다양한 결제 시나리오를 시뮬레이션할 수 있습니다.

Paddle 샌드박스 환경을 사용하는 경우, 애플리케이션의 .env 파일 내에서 PADDLE_SANDBOX 환경 변수를 true로 설정해야 합니다:

PADDLE_SANDBOX=true

애플리케이션 개발을 완료한 후 Paddle 판매자 계정을 신청할 수 있습니다. 애플리케이션이 프로덕션 환경에 배포되기 전에 Paddle은 애플리케이션의 도메인을 승인해야 합니다.

구성

빌링 가능 모델

Cashier를 사용하기 전에 사용자 모델 정의에 Billable 트레이트를 추가해야 합니다. 이 트레이트는 구독 생성 및 결제 수단 정보 업데이트와 같은 일반적인 청구 작업을 수행할 수 있는 다양한 메서드를 제공합니다.

use Laravel\Paddle\Billable;
 
class User extends Authenticatable
{
use Billable;
}

사용자가 아닌 빌링 가능한 엔터티가 있는 경우 해당 클래스에 트레이트를 추가할 수도 있습니다.

use Illuminate\Database\Eloquent\Model;
use Laravel\Paddle\Billable;
 
class Team extends Model
{
use Billable;
}

API 키

다음으로, 애플리케이션의 .env 파일에서 Paddle 키를 구성해야 합니다. Paddle API 키는 Paddle 제어판에서 가져올 수 있습니다.

PADDLE_CLIENT_SIDE_TOKEN=your-paddle-client-side-token
PADDLE_API_KEY=your-paddle-api-key
PADDLE_RETAIN_KEY=your-paddle-retain-key
PADDLE_WEBHOOK_SECRET="your-paddle-webhook-secret"
PADDLE_SANDBOX=true

PADDLE_SANDBOX 환경 변수는 Paddle의 샌드박스 환경을 사용할 때 true로 설정해야 합니다. 애플리케이션을 프로덕션 환경에 배포하고 Paddle의 라이브 벤더 환경을 사용하는 경우에는 PADDLE_SANDBOX 변수를 false로 설정해야 합니다.

PADDLE_RETAIN_KEY는 선택 사항이며, Retain과 함께 Paddle을 사용하는 경우에만 설정해야 합니다.

Paddle JS

Paddle은 Paddle 결제 위젯을 시작하기 위해 자체 JavaScript 라이브러리에 의존합니다. 애플리케이션 레이아웃의 닫는 </head> 태그 바로 앞에 @paddleJS Blade 지시어를 배치하여 JavaScript 라이브러리를 로드할 수 있습니다.

<head>
...
 
@paddleJS
</head>

통화 구성

청구서에 표시되는 금액 값을 포맷할 때 사용할 로케일을 지정할 수 있습니다. 내부적으로 Cashier는 통화 로케일을 설정하기 위해 PHP의 NumberFormatter 클래스를 활용합니다.

CASHIER_CURRENCY_LOCALE=nl_BE
exclamation

en 이외의 로케일을 사용하려면 서버에 ext-intl PHP 확장이 설치 및 구성되어 있는지 확인하십시오.

기본 모델 오버라이드

자신만의 모델을 정의하고 해당하는 Cashier 모델을 확장하여 Cashier에서 내부적으로 사용하는 모델을 자유롭게 확장할 수 있습니다.

use Laravel\Paddle\Subscription as CashierSubscription;
 
class Subscription extends CashierSubscription
{
// ...
}

모델을 정의한 후 Laravel\Paddle\Cashier 클래스를 통해 Cashier가 사용자 정의 모델을 사용하도록 지시할 수 있습니다. 일반적으로 애플리케이션의 App\Providers\AppServiceProvider 클래스의 boot 메서드에서 사용자 정의 모델에 대해 Cashier에 알려야 합니다.

use App\Models\Cashier\Subscription;
use App\Models\Cashier\Transaction;
 
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Cashier::useSubscriptionModel(Subscription::class);
Cashier::useTransactionModel(Transaction::class);
}

빠른 시작

제품 판매

lightbulb

Paddle Checkout을 사용하기 전에 Paddle 대시보드에서 고정 가격으로 제품을 정의해야 합니다. 또한 Paddle의 웹훅 처리를 구성해야 합니다.

애플리케이션을 통해 제품 및 구독 청구를 제공하는 것은 어려울 수 있습니다. 그러나 Cashier와 Paddle의 Checkout Overlay 덕분에 현대적이고 강력한 결제 통합을 쉽게 구축할 수 있습니다.

반복되지 않는 단일 요금 제품에 대해 고객에게 요금을 청구하려면 Cashier를 활용하여 Paddle의 Checkout Overlay로 고객에게 요금을 청구합니다. 여기서 고객은 결제 세부 정보를 제공하고 구매를 확인합니다. Checkout Overlay를 통해 결제가 완료되면 고객은 애플리케이션 내에서 선택한 성공 URL로 리디렉션됩니다.

use Illuminate\Http\Request;
 
Route::get('/buy', function (Request $request) {
$checkout = $request->user()->checkout('pri_deluxe_album')
->returnTo(route('dashboard'));
 
return view('buy', ['checkout' => $checkout]);
})->name('checkout');

위의 예에서 볼 수 있듯이, Cashier에서 제공하는 checkout 메서드를 사용하여 주어진 "가격 식별자"에 대한 Paddle Checkout Overlay를 고객에게 표시하기 위한 체크아웃 객체를 만듭니다. Paddle을 사용할 때 "가격"은 특정 제품에 대해 정의된 가격을 의미합니다.

필요한 경우 checkout 메서드는 자동으로 Paddle에 고객을 생성하고 해당 Paddle 고객 레코드를 애플리케이션의 데이터베이스에 있는 해당 사용자와 연결합니다. 체크아웃 세션이 완료되면 고객은 고객에게 정보 메시지를 표시할 수 있는 전용 성공 페이지로 리디렉션됩니다.

buy 뷰에서는 Checkout Overlay를 표시하는 버튼을 포함합니다. paddle-button Blade 컴포넌트는 Cashier Paddle에 포함되어 있습니다. 그러나 오버레이 체크아웃을 수동으로 렌더링할 수도 있습니다.

<x-paddle-button :checkout="$checkout" class="px-8 py-4">
제품 구매
</x-paddle-button>

Paddle 결제에 메타 데이터 제공하기

제품을 판매할 때, 자체 애플리케이션에서 정의한 CartOrder 모델을 통해 완료된 주문과 구매한 제품을 추적하는 것이 일반적입니다. 고객이 구매를 완료하기 위해 Paddle의 결제 오버레이로 리디렉션될 때, 고객이 애플리케이션으로 다시 리디렉션될 때 완료된 구매를 해당 주문과 연결할 수 있도록 기존 주문 식별자를 제공해야 할 수 있습니다.

이를 위해 checkout 메서드에 사용자 정의 데이터 배열을 제공할 수 있습니다. 사용자가 결제 프로세스를 시작할 때 애플리케이션 내에서 보류 중인 Order가 생성된다고 가정해 보겠습니다. 이 예시에서 CartOrder 모델은 예시이며 Cashier에서 제공하지 않는다는 점을 기억하세요. 이러한 개념은 애플리케이션의 요구 사항에 따라 자유롭게 구현할 수 있습니다.

use App\Models\Cart;
use App\Models\Order;
use Illuminate\Http\Request;
 
Route::get('/cart/{cart}/checkout', function (Request $request, Cart $cart) {
$order = Order::create([
'cart_id' => $cart->id,
'price_ids' => $cart->price_ids,
'status' => 'incomplete',
]);
 
$checkout = $request->user()->checkout($order->price_ids)
->customData(['order_id' => $order->id]);
 
return view('billing', ['checkout' => $checkout]);
})->name('checkout');

위의 예에서 볼 수 있듯이, 사용자가 결제 프로세스를 시작하면 장바구니/주문에 연결된 모든 Paddle 가격 식별자를 checkout 메서드에 제공합니다. 물론, 고객이 항목을 추가할 때 이러한 항목을 "장바구니" 또는 주문과 연결하는 것은 애플리케이션의 책임입니다. 또한 customData 메서드를 통해 주문 ID를 Paddle 결제 오버레이에 제공합니다.

물론, 고객이 결제 프로세스를 완료하면 주문을 "완료됨"으로 표시해야 할 가능성이 높습니다. 이를 위해 Paddle에서 발송하고 Cashier에서 이벤트를 통해 발생하는 웹훅을 수신하여 데이터베이스에 주문 정보를 저장할 수 있습니다.

시작하려면 Cashier에서 발송한 TransactionCompleted 이벤트를 수신하세요. 일반적으로 애플리케이션의 AppServiceProviderboot 메서드에서 이벤트 리스너를 등록해야 합니다.

use App\Listeners\CompleteOrder;
use Illuminate\Support\Facades\Event;
use Laravel\Paddle\Events\TransactionCompleted;
 
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Event::listen(TransactionCompleted::class, CompleteOrder::class);
}

이 예에서 CompleteOrder 리스너는 다음과 같을 수 있습니다.

namespace App\Listeners;
 
use App\Models\Order;
use Laravel\Paddle\Cashier;
use Laravel\Paddle\Events\TransactionCompleted;
 
class CompleteOrder
{
/**
* Handle the incoming Cashier webhook event.
*/
public function handle(TransactionCompleted $event): void
{
$orderId = $event->payload['data']['custom_data']['order_id'] ?? null;
 
$order = Order::findOrFail($orderId);
 
$order->update(['status' => 'completed']);
}
}

transaction.completed 이벤트에 포함된 데이터에 대한 자세한 내용은 Paddle 문서를 참조하세요.

구독 판매

lightbulb

Paddle Checkout을 활용하기 전에 Paddle 대시보드에서 고정 가격으로 제품을 정의해야 합니다. 또한 Paddle의 웹훅 처리 구성을 구성해야 합니다.

애플리케이션을 통해 제품 및 구독 청구를 제공하는 것은 어려울 수 있습니다. 그러나 Cashier와 Paddle의 결제 오버레이 덕분에 현대적이고 강력한 결제 통합을 쉽게 구축할 수 있습니다.

Cashier와 Paddle의 결제 오버레이를 사용하여 구독을 판매하는 방법을 알아보려면 기본 월간 (price_basic_monthly) 및 연간 (price_basic_yearly) 요금제가 있는 구독 서비스의 간단한 시나리오를 고려해 보겠습니다. 이러한 두 가격은 Paddle 대시보드에서 "기본" 제품 (pro_basic)으로 그룹화할 수 있습니다. 또한 구독 서비스는 전문가 플랜을 pro_expert로 제공할 수 있습니다.

먼저 고객이 서비스에 가입하는 방법을 알아보겠습니다. 물론 고객이 애플리케이션의 가격 책정 페이지에서 기본 플랜에 대한 "구독" 버튼을 클릭한다고 상상할 수 있습니다. 이 버튼은 선택한 플랜에 대한 Paddle 결제 오버레이를 호출합니다. 시작하려면 checkout 메서드를 통해 결제 세션을 시작해 보겠습니다.

use Illuminate\Http\Request;
 
Route::get('/subscribe', function (Request $request) {
$checkout = $request->user()->checkout('price_basic_monthly')
->returnTo(route('dashboard'));
 
return view('subscribe', ['checkout' => $checkout]);
})->name('subscribe');

subscribe 보기에서 결제 오버레이를 표시하는 버튼을 포함합니다. paddle-button Blade 컴포넌트는 Cashier Paddle에 포함되어 있습니다. 그러나 오버레이 결제를 수동으로 렌더링할 수도 있습니다.

<x-paddle-button :checkout="$checkout" class="px-8 py-4">
구독
</x-paddle-button>

이제 구독 버튼을 클릭하면 고객은 결제 정보를 입력하고 구독을 시작할 수 있습니다. 일부 결제 방식은 처리하는 데 몇 초가 걸릴 수 있으므로 구독이 실제로 시작된 시점을 알려면 Cashier의 웹훅 처리 구성도 해야 합니다.

이제 고객이 구독을 시작할 수 있으므로 구독한 사용자만 특정 애플리케이션 부분에 접근할 수 있도록 제한해야 합니다. 물론 Cashier의 Billable trait에서 제공하는 subscribed 메서드를 통해 사용자의 현재 구독 상태를 항상 확인할 수 있습니다.

@if ($user->subscribed())
<p>구독 중입니다.</p>
@endif

특정 제품 또는 가격을 구독했는지도 쉽게 확인할 수 있습니다.

@if ($user->subscribedToProduct('pro_basic'))
<p>기본 제품을 구독 중입니다.</p>
@endif
 
@if ($user->subscribedToPrice('price_basic_monthly'))
<p>월별 기본 요금제를 구독 중입니다.</p>
@endif

구독된 미들웨어 구축

편의를 위해, 들어오는 요청이 구독한 사용자의 요청인지 확인하는 미들웨어를 생성할 수 있습니다. 이 미들웨어가 정의되면, 구독하지 않은 사용자가 해당 경로에 접근하는 것을 방지하기 위해 경로에 쉽게 할당할 수 있습니다.

<?php
 
namespace App\Http\Middleware;
 
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
 
class Subscribed
{
/**
* 들어오는 요청을 처리합니다.
*/
public function handle(Request $request, Closure $next): Response
{
if (! $request->user()?->subscribed()) {
// 사용자를 결제 페이지로 리디렉션하고 구독을 요청합니다...
return redirect('/subscribe');
}
 
return $next($request);
}
}

미들웨어가 정의되면, 다음과 같이 경로에 할당할 수 있습니다.

use App\Http\Middleware\Subscribed;
 
Route::get('/dashboard', function () {
// ...
})->middleware([Subscribed::class]);

고객이 결제 플랜을 관리하도록 허용

물론, 고객은 구독 플랜을 다른 제품 또는 "티어"로 변경하고 싶을 수 있습니다. 위의 예에서, 고객이 월간 구독에서 연간 구독으로 플랜을 변경하도록 허용하고 싶을 것입니다. 이를 위해 다음 경로로 이어지는 버튼과 같은 것을 구현해야 합니다.

use Illuminate\Http\Request;
 
Route::put('/subscription/{price}/swap', function (Request $request, $price) {
$user->subscription()->swap($price); // 여기서 "$price"는 이 예시의 경우 "price_basic_yearly"입니다.
 
return redirect()->route('dashboard');
})->name('subscription.swap');

플랜을 스왑하는 것 외에도 고객이 구독을 취소할 수 있도록 허용해야 합니다. 플랜 스왑과 마찬가지로, 다음 경로로 이어지는 버튼을 제공합니다.

use Illuminate\Http\Request;
 
Route::put('/subscription/cancel', function (Request $request, $price) {
$user->subscription()->cancel();
 
return redirect()->route('dashboard');
})->name('subscription.cancel');

이제 구독은 결제 기간이 끝날 때 취소됩니다.

lightbulb

Cashier의 웹훅 처리를 구성한 경우, Cashier는 Paddle에서 수신되는 웹훅을 검사하여 자동으로 애플리케이션의 Cashier 관련 데이터베이스 테이블을 동기화된 상태로 유지합니다. 예를 들어, Paddle의 대시보드를 통해 고객의 구독을 취소하면, Cashier는 해당 웹훅을 수신하고 애플리케이션의 데이터베이스에서 해당 구독을 "취소됨"으로 표시합니다.

결제 세션

고객에게 청구하는 대부분의 작업은 Paddle의 결제 오버레이 위젯 또는 인라인 결제를 사용하여 "결제"를 통해 수행됩니다.

Paddle을 사용하여 결제 처리를 시작하기 전에, Paddle 결제 설정 대시보드에서 애플리케이션의 기본 결제 링크를 정의해야 합니다.

오버레이 결제

결제 오버레이 위젯을 표시하기 전에, Cashier를 사용하여 결제 세션을 생성해야 합니다. 결제 세션은 수행해야 할 결제 작업을 결제 위젯에 알려줍니다.

use Illuminate\Http\Request;
 
Route::get('/buy', function (Request $request) {
$checkout = $user->checkout('pri_34567')
->returnTo(route('dashboard'));
 
return view('billing', ['checkout' => $checkout]);
});

Cashier에는 paddle-button Blade 컴포넌트가 포함되어 있습니다. 결제 세션을 이 컴포넌트에 "prop"으로 전달할 수 있습니다. 그러면 이 버튼을 클릭하면 Paddle의 결제 위젯이 표시됩니다.

<x-paddle-button :checkout="$checkout" class="px-8 py-4">
구독하기
</x-paddle-button>

기본적으로, 이는 Paddle의 기본 스타일을 사용하여 위젯을 표시합니다. data-theme='light' 속성과 같은 Paddle 지원 속성을 컴포넌트에 추가하여 위젯을 사용자 정의할 수 있습니다:

<x-paddle-button :checkout="$checkout" class="px-8 py-4" data-theme="light">
구독하기
</x-paddle-button>

Paddle 결제 위젯은 비동기식입니다. 사용자가 위젯 내에서 구독을 생성하면, Paddle은 애플리케이션의 데이터베이스에서 구독 상태를 적절하게 업데이트할 수 있도록 애플리케이션에 웹훅을 보냅니다. 따라서 Paddle의 상태 변경을 수용하기 위해 웹훅을 적절하게 설정하는 것이 중요합니다.

exclamation

구독 상태 변경 후 해당 웹훅을 수신하는 데 걸리는 지연 시간은 일반적으로 최소화되지만, 결제를 완료한 후 사용자의 구독이 즉시 사용 가능하지 않을 수 있다는 점을 고려하여 애플리케이션에서 이를 고려해야 합니다.

오버레이 결제 수동 렌더링

Laravel의 내장 Blade 컴포넌트를 사용하지 않고 수동으로 오버레이 결제를 렌더링할 수도 있습니다. 시작하려면 이전 예제에서 설명한 대로 결제 세션을 생성합니다:

use Illuminate\Http\Request;
 
Route::get('/buy', function (Request $request) {
$checkout = $user->checkout('pri_34567')
->returnTo(route('dashboard'));
 
return view('billing', ['checkout' => $checkout]);
});

다음으로, Paddle.js를 사용하여 결제를 초기화할 수 있습니다. 이 예에서는 paddle_button 클래스가 할당된 링크를 생성합니다. Paddle.js는 이 클래스를 감지하고 링크를 클릭하면 오버레이 결제를 표시합니다.

<?php
$items = $checkout->getItems();
$customer = $checkout->getCustomer();
$custom = $checkout->getCustomData();
?>
 
<a
href='#!'
class='paddle_button'
data-items='{!! json_encode($items) !!}'
@if ($customer) data-customer-id='{{ $customer->paddle_id }}' @endif
@if ($custom) data-custom-data='{{ json_encode($custom) }}' @endif
@if ($returnUrl = $checkout->getReturnUrl()) data-success-url='{{ $returnUrl }}' @endif
>
제품 구매
</a>

인라인 결제

패들의 "오버레이" 스타일 결제 위젯을 사용하고 싶지 않다면, 패들은 위젯을 인라인으로 표시하는 옵션도 제공합니다. 이 접근 방식은 결제의 HTML 필드를 조정할 수 없지만, 애플리케이션 내에 위젯을 임베드할 수 있습니다.

인라인 결제를 쉽게 시작할 수 있도록 Cashier는 paddle-checkout Blade 컴포넌트를 포함합니다. 시작하려면, 결제 세션을 생성해야 합니다:

use Illuminate\Http\Request;
 
Route::get('/buy', function (Request $request) {
$checkout = $user->checkout('pri_34567')
->returnTo(route('dashboard'));
 
return view('billing', ['checkout' => $checkout]);
});

그런 다음, 컴포넌트의 checkout 속성에 결제 세션을 전달할 수 있습니다:

<x-paddle-checkout :checkout="$checkout" class="w-full" />

인라인 결제 컴포넌트의 높이를 조정하려면, Blade 컴포넌트에 height 속성을 전달할 수 있습니다:

<x-paddle-checkout :checkout="$checkout" class="w-full" height="500" />

인라인 체크아웃의 사용자 정의 옵션에 대한 자세한 내용은 Paddle의 인라인 체크아웃 가이드사용 가능한 체크아웃 설정을 참조하십시오.

인라인 체크아웃 수동 렌더링

Laravel의 내장 Blade 컴포넌트를 사용하지 않고 인라인 체크아웃을 수동으로 렌더링할 수도 있습니다. 시작하려면 이전 예제에서 설명한 대로 체크아웃 세션을 생성하십시오.

use Illuminate\Http\Request;
 
Route::get('/buy', function (Request $request) {
$checkout = $user->checkout('pri_34567')
->returnTo(route('dashboard'));
 
return view('billing', ['checkout' => $checkout]);
});

다음으로 Paddle.js를 사용하여 체크아웃을 초기화할 수 있습니다. 이 예에서는 Alpine.js를 사용하여 이를 시연합니다. 그러나 자신의 프런트엔드 스택에 맞게 이 예제를 자유롭게 수정할 수 있습니다.

<?php
$options = $checkout->options();
 
$options['settings']['frameTarget'] = 'paddle-checkout';
$options['settings']['frameInitialHeight'] = 366;
?>
 
<div class="paddle-checkout" x-data="{}" x-init="
Paddle.Checkout.open(@json($options));
">
</div>

게스트 결제

애플리케이션에 계정이 필요 없는 사용자를 위한 결제 세션을 만들어야 할 때가 있습니다. 이렇게 하려면 guest 메서드를 사용할 수 있습니다.

use Illuminate\Http\Request;
use Laravel\Paddle\Checkout;
 
Route::get('/buy', function (Request $request) {
$checkout = Checkout::guest('pri_34567')
->returnTo(route('home'));
 
return view('billing', ['checkout' => $checkout]);
});

그런 다음 결제 세션을 Paddle 버튼 또는 인라인 결제 Blade 컴포넌트에 제공할 수 있습니다.

가격 미리보기

Paddle을 사용하면 통화별로 가격을 사용자 지정할 수 있으며, 본질적으로 국가별로 다른 가격을 구성할 수 있습니다. Cashier Paddle을 사용하면 previewPrices 메서드를 사용하여 이러한 모든 가격을 검색할 수 있습니다. 이 메서드는 가격을 검색하려는 가격 ID를 허용합니다.

use Laravel\Paddle\Cashier;
 
$prices = Cashier::previewPrices(['pri_123', 'pri_456']);

통화는 요청의 IP 주소를 기준으로 결정되지만, 선택적으로 특정 국가를 제공하여 가격을 검색할 수도 있습니다.

use Laravel\Paddle\Cashier;
 
$prices = Cashier::previewPrices(['pri_123', 'pri_456'], ['address' => [
'country_code' => 'BE',
'postal_code' => '1234',
]]);

가격을 검색한 후 원하는 대로 표시할 수 있습니다.

<ul>
@foreach ($prices as $price)
<li>{{ $price->product['name'] }} - {{ $price->total() }}</li>
@endforeach
</ul>

소계 가격과 세금 금액을 별도로 표시할 수도 있습니다.

<ul>
@foreach ($prices as $price)
<li>{{ $price->product['name'] }} - {{ $price->subtotal() }} (+ {{ $price->tax() }} tax)</li>
@endforeach
</ul>

자세한 내용은 Paddle의 가격 미리보기 관련 API 문서를 확인하세요.

고객 가격 미리보기

사용자가 이미 고객이고 해당 고객에게 적용되는 가격을 표시하고 싶다면 고객 인스턴스에서 직접 가격을 검색하여 할 수 있습니다.

use App\Models\User;
 
$prices = User::find(1)->previewPrices(['pri_123', 'pri_456']);

내부적으로 Cashier는 사용자의 고객 ID를 사용하여 해당 통화의 가격을 검색합니다. 예를 들어, 미국에 거주하는 사용자는 미국 달러로 가격을 보고, 벨기에에 거주하는 사용자는 유로로 가격을 봅니다. 일치하는 통화를 찾을 수 없으면 제품의 기본 통화가 사용됩니다. Paddle 제어판에서 제품 또는 구독 플랜의 모든 가격을 사용자 지정할 수 있습니다.

할인

할인이 적용된 후의 가격을 표시하도록 선택할 수도 있습니다. previewPrices 메서드를 호출할 때 discount_id 옵션을 통해 할인 ID를 제공합니다.

use Laravel\Paddle\Cashier;
 
$prices = Cashier::previewPrices(['pri_123', 'pri_456'], [
'discount_id' => 'dsc_123'
]);

그런 다음 계산된 가격을 표시합니다.

<ul>
@foreach ($prices as $price)
<li>{{ $price->product['name'] }} - {{ $price->total() }}</li>
@endforeach
</ul>

고객

고객 기본값

Cashier를 사용하면 결제 세션을 생성할 때 고객에 대한 유용한 기본값을 정의할 수 있습니다. 이러한 기본값을 설정하면 고객의 이메일 주소와 이름을 미리 채워 넣어 결제 위젯의 결제 부분으로 즉시 이동할 수 있습니다. 청구 가능 모델에서 다음 메서드를 재정의하여 이러한 기본값을 설정할 수 있습니다.

/**
* Paddle에 연결할 고객의 이름을 가져옵니다.
*/
public function paddleName(): string|null
{
return $this->name;
}
 
/**
* Paddle에 연결할 고객의 이메일 주소를 가져옵니다.
*/
public function paddleEmail(): string|null
{
return $this->email;
}

이러한 기본값은 결제 세션을 생성하는 Cashier의 모든 작업에 사용됩니다.

고객 검색

Cashier::findBillable 메서드를 사용하여 Paddle 고객 ID로 고객을 검색할 수 있습니다. 이 메서드는 청구 가능 모델의 인스턴스를 반환합니다.

use Laravel\Paddle\Cashier;
 
$user = Cashier::findBillable($customerId);

고객 생성

경우에 따라 구독을 시작하지 않고 Paddle 고객을 생성하고 싶을 수도 있습니다. createAsCustomer 메서드를 사용하여 이를 수행할 수 있습니다.

$customer = $user->createAsCustomer();

Laravel\Paddle\Customer의 인스턴스가 반환됩니다. Paddle에서 고객이 생성되면 나중에 구독을 시작할 수 있습니다. 선택적인 $options 배열을 제공하여 Paddle API에서 지원하는 추가 고객 생성 매개변수를 전달할 수 있습니다.

$customer = $user->createAsCustomer($options);

구독

구독 생성

구독을 생성하려면 먼저 데이터베이스에서 청구 가능 모델의 인스턴스를 검색합니다. 일반적으로 App\Models\User의 인스턴스일 것입니다. 모델 인스턴스를 검색했으면 subscribe 메서드를 사용하여 모델의 결제 세션을 생성할 수 있습니다.

use Illuminate\Http\Request;
 
Route::get('/user/subscribe', function (Request $request) {
$checkout = $request->user()->subscribe($premium = 12345, 'default')
->returnTo(route('home'));
 
return view('billing', ['checkout' => $checkout]);
});

subscribe 메서드에 제공되는 첫 번째 인수는 사용자가 구독하는 특정 가격입니다. 이 값은 Paddle의 가격 식별자에 해당해야 합니다. returnTo 메서드는 사용자가 결제를 성공적으로 완료한 후 리디렉션될 URL을 허용합니다. subscribe 메서드에 전달된 두 번째 인수는 구독의 내부 "유형"이어야 합니다. 애플리케이션에서 단일 구독만 제공하는 경우 이를 default 또는 primary라고 부를 수 있습니다. 이 구독 유형은 내부 애플리케이션 용도로만 사용되며 사용자에게 표시하기 위한 것이 아닙니다. 또한 공백을 포함해서는 안 되며 구독을 생성한 후에는 절대 변경해서는 안 됩니다.

customData 메서드를 사용하여 구독에 대한 사용자 정의 메타데이터 배열을 제공할 수도 있습니다.

$checkout = $request->user()->subscribe($premium = 12345, 'default')
->customData(['key' => 'value'])
->returnTo(route('home'));

구독 결제 세션이 생성되면 결제 세션을 Cashier Paddle에 포함된 paddle-button Blade 컴포넌트에 제공할 수 있습니다.

<x-paddle-button :checkout="$checkout" class="px-8 py-4">
구독하기
</x-paddle-button>

사용자가 결제를 완료하면 Paddle에서 subscription_created 웹훅이 디스패치됩니다. Cashier는 이 웹훅을 수신하고 고객의 구독을 설정합니다. 애플리케이션에서 모든 웹훅을 제대로 수신하고 처리하려면 웹훅 처리 설정을 올바르게 해야 합니다.

구독 상태 확인

사용자가 애플리케이션을 구독하면 다양한 편리한 메서드를 사용하여 구독 상태를 확인할 수 있습니다. 먼저, subscribed 메서드는 사용자가 유효한 구독을 가지고 있는 경우 true를 반환하며, 구독이 현재 평가판 기간 내에 있는 경우에도 마찬가지입니다.

if ($user->subscribed()) {
// ...
}

애플리케이션에서 여러 구독을 제공하는 경우 subscribed 메서드를 호출할 때 구독을 지정할 수 있습니다.

if ($user->subscribed('default')) {
// ...
}

subscribed 메서드는 라우트 미들웨어에 적합하여 사용자의 구독 상태에 따라 라우트 및 컨트롤러에 대한 액세스를 필터링할 수 있습니다.

<?php
 
namespace App\Http\Middleware;
 
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
 
class EnsureUserIsSubscribed
{
/**
* 수신 요청 처리.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if ($request->user() && ! $request->user()->subscribed()) {
// 이 사용자는 유료 고객이 아닙니다...
return redirect('/billing');
}
 
return $next($request);
}
}

사용자가 아직 평가판 기간 내에 있는지 확인하려면 onTrial 메서드를 사용할 수 있습니다. 이 메서드는 사용자가 아직 평가판 기간에 있음을 사용자에게 경고해야 하는지 여부를 결정하는 데 유용할 수 있습니다.

if ($user->subscription()->onTrial()) {
// ...
}

subscribedToPrice 메서드는 지정된 Paddle 가격 ID를 기반으로 사용자가 지정된 요금제를 구독하고 있는지 확인하는 데 사용할 수 있습니다. 이 예에서는 사용자 default 구독이 월간 요금제를 활발하게 구독하고 있는지 확인합니다.

if ($user->subscribedToPrice($monthly = 'pri_123', 'default')) {
// ...
}

recurring 메서드는 사용자가 현재 활성 구독 중이며 더 이상 평가판 기간 또는 유예 기간 내에 있지 않은지 확인하는 데 사용할 수 있습니다.

if ($user->subscription()->recurring()) {
// ...
}

취소된 구독 상태

사용자가 이전에 활성 구독자였지만 구독을 취소했는지 확인하려면 canceled 메서드를 사용할 수 있습니다.

if ($user->subscription()->canceled()) {
// ...
}

사용자가 구독을 취소했지만 구독이 완전히 만료될 때까지 "유예 기간"에 있는지 확인할 수도 있습니다. 예를 들어 사용자가 원래 3월 10일에 만료될 예정이었던 구독을 3월 5일에 취소하는 경우 사용자는 3월 10일까지 "유예 기간"에 있습니다. 또한 이 기간 동안 subscribed 메서드는 계속해서 true를 반환합니다.

if ($user->subscription()->onGracePeriod()) {
// ...
}

연체 상태

구독에 대한 결제가 실패하면 past_due로 표시됩니다. 구독이 이 상태에 있는 경우 고객이 결제 정보를 업데이트할 때까지 활성화되지 않습니다. 구독 인스턴스에서 pastDue 메서드를 사용하여 구독이 연체되었는지 여부를 확인할 수 있습니다.

if ($user->subscription()->pastDue()) {
// ...
}

구독이 연체되면 사용자에게 결제 정보 업데이트 방법을 안내해야 합니다.

구독이 past_due일 때도 유효한 것으로 간주하려면 Cashier에서 제공하는 keepPastDueSubscriptionsActive 메서드를 사용할 수 있습니다. 일반적으로 이 메서드는 AppServiceProviderregister 메서드에서 호출해야 합니다.

use Laravel\Paddle\Cashier;
 
/**
* 애플리케이션 서비스 등록.
*/
public function register(): void
{
Cashier::keepPastDueSubscriptionsActive();
}
exclamation

구독이 past_due 상태인 경우 결제 정보가 업데이트될 때까지 변경할 수 없습니다. 따라서 구독이 past_due 상태일 때 swapupdateQuantity 메서드는 예외를 throw합니다.

구독 스코프

대부분의 구독 상태는 쿼리 스코프로도 사용할 수 있으므로 지정된 상태의 구독에 대해 데이터베이스를 쉽게 쿼리할 수 있습니다.

// 모든 유효한 구독 가져오기...
$subscriptions = Subscription::query()->valid()->get();
 
// 사용자의 모든 취소된 구독 가져오기...
$subscriptions = $user->subscriptions()->canceled()->get();

사용 가능한 스코프의 전체 목록은 다음과 같습니다.

Subscription::query()->valid();
Subscription::query()->onTrial();
Subscription::query()->expiredTrial();
Subscription::query()->notOnTrial();
Subscription::query()->active();
Subscription::query()->recurring();
Subscription::query()->pastDue();
Subscription::query()->paused();
Subscription::query()->notPaused();
Subscription::query()->onPausedGracePeriod();
Subscription::query()->notOnPausedGracePeriod();
Subscription::query()->canceled();
Subscription::query()->notCanceled();
Subscription::query()->onGracePeriod();
Subscription::query()->notOnGracePeriod();

구독 단일 요금

구독 단일 요금을 사용하면 구독자에게 구독 외에 일회성 요금을 청구할 수 있습니다. charge 메서드를 호출할 때 하나 이상의 가격 ID를 제공해야 합니다.

// 단일 가격 청구...
$response = $user->subscription()->charge('pri_123');
 
// 한 번에 여러 가격 청구...
$response = $user->subscription()->charge(['pri_123', 'pri_456']);

charge 메서드는 실제로 구독의 다음 결제 간격까지 고객에게 청구하지 않습니다. 고객에게 즉시 청구하려면 대신 chargeAndInvoice 메서드를 사용할 수 있습니다.

$response = $user->subscription()->chargeAndInvoice('pri_123');

결제 정보 업데이트

Paddle은 항상 구독당 하나의 결제 수단을 저장합니다. 구독의 기본 결제 수단을 업데이트하려면 구독 모델에서 redirectToUpdatePaymentMethod 메서드를 사용하여 고객을 Paddle의 호스팅 결제 수단 업데이트 페이지로 리디렉션해야 합니다.

use Illuminate\Http\Request;
 
Route::get('/update-payment-method', function (Request $request) {
$user = $request->user();
 
return $user->subscription()->redirectToUpdatePaymentMethod();
});

사용자가 정보 업데이트를 완료하면 Paddle에서 subscription_updated 웹훅이 디스패치되고 애플리케이션의 데이터베이스에서 구독 세부 정보가 업데이트됩니다.

요금제 변경

사용자가 애플리케이션을 구독한 후에는 때때로 새 구독 요금제로 변경하고 싶을 수 있습니다. 사용자의 구독 요금제를 업데이트하려면 Paddle 가격 식별자를 구독의 swap 메서드에 전달해야 합니다.

use App\Models\User;
 
$user = User::find(1);
 
$user->subscription()->swap($premium = 'pri_456');

요금제를 교체하고 다음 청구 주기까지 기다리는 대신 사용자에게 즉시 청구하려면 swapAndInvoice 메서드를 사용할 수 있습니다.

$user = User::find(1);
 
$user->subscription()->swapAndInvoice($premium = 'pri_456');

비례 배분

기본적으로 Paddle은 요금제 간에 교체할 때 요금을 비례 배분합니다. noProrate 메서드를 사용하여 요금을 비례 배분하지 않고 구독을 업데이트할 수 있습니다.

$user->subscription('default')->noProrate()->swap($premium = 'pri_456');

비례 배분을 비활성화하고 고객에게 즉시 청구하려면 noProrate와 함께 swapAndInvoice 메서드를 사용할 수 있습니다.

$user->subscription('default')->noProrate()->swapAndInvoice($premium = 'pri_456');

또는 구독 변경에 대해 고객에게 청구하지 않으려면 doNotBill 메서드를 사용할 수 있습니다.

$user->subscription('default')->doNotBill()->swap($premium = 'pri_456');

Paddle의 비례 배분 정책에 대한 자세한 내용은 Paddle의 비례 배분 문서를 참조하십시오.

구독 수량

때로는 구독이 "수량"의 영향을 받습니다. 예를 들어 프로젝트 관리 애플리케이션에서 프로젝트당 월 $10를 청구할 수 있습니다. 구독 수량을 쉽게 늘리거나 줄이려면 incrementQuantitydecrementQuantity 메서드를 사용하십시오.

$user = User::find(1);
 
$user->subscription()->incrementQuantity();
 
// 구독의 현재 수량에 5를 더합니다...
$user->subscription()->incrementQuantity(5);
 
$user->subscription()->decrementQuantity();
 
// 구독의 현재 수량에서 5를 뺍니다...
$user->subscription()->decrementQuantity(5);

또는 updateQuantity 메서드를 사용하여 특정 수량을 설정할 수 있습니다.

$user->subscription()->updateQuantity(10);

noProrate 메서드를 사용하여 요금을 비례 배분하지 않고 구독 수량을 업데이트할 수 있습니다.

$user->subscription()->noProrate()->updateQuantity(10);

여러 제품이 있는 구독의 수량

구독이 여러 제품이 있는 구독인 경우 증가/감소 메서드에 대한 두 번째 인수로 증가 또는 감소시키려는 가격의 ID를 전달해야 합니다.

$user->subscription()->incrementQuantity(1, 'price_chat');

여러 제품이 있는 구독

여러 제품이 있는 구독을 사용하면 단일 구독에 여러 청구 제품을 할당할 수 있습니다. 예를 들어 기본 구독 가격이 월 $10이지만 월 $15의 추가 라이브 채팅 추가 기능을 제공하는 고객 서비스 "헬프데스크" 애플리케이션을 구축한다고 가정해 보겠습니다.

구독 결제 세션을 생성할 때 subscribe 메서드에 대한 첫 번째 인수로 가격 배열을 전달하여 지정된 구독에 대해 여러 제품을 지정할 수 있습니다.

use Illuminate\Http\Request;
 
Route::post('/user/subscribe', function (Request $request) {
$checkout = $request->user()->subscribe([
'price_monthly',
'price_chat',
]);
 
return view('billing', ['checkout' => $checkout]);
});

위의 예에서 고객은 default 구독에 두 개의 가격이 연결됩니다. 두 가격 모두 각 결제 간격에 따라 청구됩니다. 필요한 경우 각 가격에 대한 특정 수량을 나타내기 위해 키/값 쌍의 연관 배열을 전달할 수 있습니다.

$user = User::find(1);
 
$checkout = $user->subscribe('default', ['price_monthly', 'price_chat' => 5]);

기존 구독에 다른 가격을 추가하려면 구독의 swap 메서드를 사용해야 합니다. swap 메서드를 호출할 때 구독의 현재 가격과 수량도 포함해야 합니다.

$user = User::find(1);
 
$user->subscription()->swap(['price_chat', 'price_original' => 2]);

위의 예에서는 새 가격이 추가되지만 고객은 다음 결제 주기까지 이에 대한 요금을 청구하지 않습니다. 고객에게 즉시 청구하려면 swapAndInvoice 메서드를 사용할 수 있습니다.

$user->subscription()->swapAndInvoice(['price_chat', 'price_original' => 2]);

swap 메서드를 사용하고 제거하려는 가격을 생략하여 구독에서 가격을 제거할 수 있습니다.

$user->subscription()->swap(['price_original' => 2]);
exclamation

구독에서 마지막 가격을 제거할 수 없습니다. 대신 구독을 취소해야 합니다.

다중 구독

Paddle을 통해 고객은 여러 구독을 동시에 가질 수 있습니다. 예를 들어 수영 구독과 역도 구독을 제공하는 체육관을 운영할 수 있으며 각 구독에는 다른 가격이 있을 수 있습니다. 물론 고객은 하나 또는 두 요금제 모두 구독할 수 있어야 합니다.

애플리케이션에서 구독을 생성할 때 subscribe 메서드에 대한 두 번째 인수로 구독 유형을 제공할 수 있습니다. 유형은 사용자가 시작하는 구독 유형을 나타내는 모든 문자열일 수 있습니다.

use Illuminate\Http\Request;
 
Route::post('/swimming/subscribe', function (Request $request) {
$checkout = $request->user()->subscribe($swimmingMonthly = 'pri_123', 'swimming');
 
return view('billing', ['checkout' => $checkout]);
});

이 예에서는 고객에 대해 월간 수영 구독을 시작했습니다. 그러나 나중에 연간 구독으로 교체할 수 있습니다. 고객의 구독을 조정할 때 swimming 구독에서 가격을 교체하기만 하면 됩니다.

$user->subscription('swimming')->swap($swimmingYearly = 'pri_456');

물론 구독을 완전히 취소할 수도 있습니다.

$user->subscription('swimming')->cancel();

구독 일시 중지

구독을 일시 중지하려면 사용자 구독에서 pause 메서드를 호출합니다.

$user->subscription()->pause();

구독이 일시 중지되면 Cashier는 데이터베이스에 paused_at 열을 자동으로 설정합니다. 이 열은 paused 메서드가 true를 반환하기 시작해야 하는 시기를 결정하는 데 사용됩니다. 예를 들어 고객이 3월 1일에 구독을 일시 중지했지만 구독이 3월 5일까지 반복되도록 예약되지 않은 경우 paused 메서드는 3월 5일까지 계속해서 false를 반환합니다. 이는 일반적으로 사용자가 결제 주기가 끝날 때까지 애플리케이션을 계속 사용할 수 있기 때문입니다.

기본적으로 일시 중지는 다음 결제 간격에서 발생하므로 고객이 결제한 기간의 나머지를 사용할 수 있습니다. 구독을 즉시 일시 중지하려면 pauseNow 메서드를 사용할 수 있습니다.

$user->subscription()->pauseNow();

pauseUntil 메서드를 사용하면 특정 시점까지 구독을 일시 중지할 수 있습니다.

$user->subscription()->pauseUntil(now()->addMonth());

또는 pauseNowUntil 메서드를 사용하여 지정된 시점까지 구독을 즉시 일시 중지할 수 있습니다.

$user->subscription()->pauseNowUntil(now()->addMonth());

onPausedGracePeriod 메서드를 사용하여 사용자가 구독을 일시 중지했지만 여전히 "유예 기간"에 있는지 확인할 수 있습니다.

if ($user->subscription()->onPausedGracePeriod()) {
// ...
}

일시 중지된 구독을 재개하려면 구독에서 resume 메서드를 호출할 수 있습니다.

$user->subscription()->resume();
exclamation

구독이 일시 중지된 동안에는 수정할 수 없습니다. 다른 요금제로 교체하거나 수량을 업데이트하려면 먼저 구독을 재개해야 합니다.

구독 취소

구독을 취소하려면 사용자 구독에서 cancel 메서드를 호출합니다.

$user->subscription()->cancel();

구독이 취소되면 Cashier는 데이터베이스에 ends_at 열을 자동으로 설정합니다. 이 열은 subscribed 메서드가 false를 반환하기 시작해야 하는 시기를 결정하는 데 사용됩니다. 예를 들어 고객이 3월 1일에 구독을 취소했지만 구독이 3월 5일까지 종료되도록 예약되지 않은 경우 subscribed 메서드는 3월 5일까지 계속해서 true를 반환합니다. 이는 일반적으로 사용자가 결제 주기가 끝날 때까지 애플리케이션을 계속 사용할 수 있기 때문입니다.

onGracePeriod 메서드를 사용하여 사용자가 구독을 취소했지만 여전히 "유예 기간"에 있는지 확인할 수 있습니다.

if ($user->subscription()->onGracePeriod()) {
// ...
}

구독을 즉시 취소하려면 구독에서 cancelNow 메서드를 호출할 수 있습니다.

$user->subscription()->cancelNow();

유예 기간 중인 구독이 취소되는 것을 중지하려면 stopCancelation 메서드를 호출할 수 있습니다.

$user->subscription()->stopCancelation();
exclamation

Paddle의 구독은 취소 후 재개할 수 없습니다. 고객이 구독을 재개하려면 새 구독을 만들어야 합니다.

구독 평가판

결제 수단 선불

결제 수단 정보를 선불로 수집하면서 고객에게 평가판 기간을 제공하려면 고객이 구독하는 가격에 대한 Paddle 대시보드에서 평가판 시간을 설정해야 합니다. 그런 다음 결제 세션을 정상적으로 시작합니다.

use Illuminate\Http\Request;
 
Route::get('/user/subscribe', function (Request $request) {
$checkout = $request->user()->subscribe('pri_monthly')
->returnTo(route('home'));
 
return view('billing', ['checkout' => $checkout]);
});

애플리케이션이 subscription_created 이벤트를 수신하면 Cashier는 애플리케이션 데이터베이스 내의 구독 기록에 평가판 기간 종료 날짜를 설정하고 Paddle에 이 날짜 이후까지 고객에게 청구를 시작하지 않도록 지시합니다.

exclamation

평가판 종료 날짜 전에 고객의 구독이 취소되지 않으면 평가판이 만료되는 즉시 요금이 청구되므로 사용자에게 평가판 종료 날짜를 알려야 합니다.

사용자가 사용자 인스턴스의 onTrial 메서드 또는 구독 인스턴스의 onTrial 메서드를 사용하여 평가판 기간 내에 있는지 확인할 수 있습니다. 아래의 두 예는 동일합니다.

if ($user->onTrial()) {
// ...
}
 
if ($user->subscription()->onTrial()) {
// ...
}

기존 평가판이 만료되었는지 확인하려면 hasExpiredTrial 메서드를 사용할 수 있습니다.

if ($user->hasExpiredTrial()) {
// ...
}
 
if ($user->subscription()->hasExpiredTrial()) {
// ...
}

사용자가 특정 구독 유형에 대한 평가판을 사용 중인지 확인하려면 onTrial 또는 hasExpiredTrial 메서드에 유형을 제공할 수 있습니다.

if ($user->onTrial('default')) {
// ...
}
 
if ($user->hasExpiredTrial('default')) {
// ...
}

결제 수단 선불 없이

사용자의 결제 수단 정보를 선불로 수집하지 않고 평가판 기간을 제공하려면 사용자에게 연결된 고객 기록의 trial_ends_at 열을 원하는 평가판 종료 날짜로 설정할 수 있습니다. 일반적으로 사용자 등록 중에 수행됩니다.

use App\Models\User;
 
$user = User::create([
// ...
]);
 
$user->createAsCustomer([
'trial_ends_at' => now()->addDays(10)
]);

Cashier는 이 유형의 평가판을 기존 구독에 연결되지 않으므로 "일반 평가판"이라고 합니다. 현재 날짜가 trial_ends_at 값을 지나지 않은 경우 User 인스턴스의 onTrial 메서드는 true를 반환합니다.

if ($user->onTrial()) {
// 사용자가 평가판 기간 내에 있습니다...
}

사용자에 대한 실제 구독을 생성할 준비가 되면 평소와 같이 subscribe 메서드를 사용할 수 있습니다.

use Illuminate\Http\Request;
 
Route::get('/user/subscribe', function (Request $request) {
$checkout = $user->subscribe('pri_monthly')
->returnTo(route('home'));
 
return view('billing', ['checkout' => $checkout]);
});

사용자의 평가판 종료 날짜를 검색하려면 trialEndsAt 메서드를 사용할 수 있습니다. 이 메서드는 사용자가 평가판에 있는 경우 Carbon 날짜 인스턴스를 반환하고 그렇지 않은 경우 null을 반환합니다. 기본 구독이 아닌 특정 구독에 대한 평가판 종료 날짜를 가져오려는 경우 선택적 구독 유형 매개변수를 전달할 수도 있습니다.

if ($user->onTrial('default')) {
$trialEndsAt = $user->trialEndsAt();
}

사용자가 "일반" 평가판 기간 내에 있고 아직 실제 구독을 생성하지 않았다는 것을 구체적으로 알고 싶다면 onGenericTrial 메서드를 사용할 수 있습니다.

if ($user->onGenericTrial()) {
// 사용자가 "일반" 평가판 기간 내에 있습니다...
}

평가판 연장 또는 활성화

extendTrial 메서드를 호출하고 평가판이 종료되어야 하는 시점을 지정하여 구독에 대한 기존 평가판 기간을 연장할 수 있습니다.

$user->subscription()->extendTrial(now()->addDays(5));

또는 구독에서 activate 메서드를 호출하여 평가판을 종료하여 구독을 즉시 활성화할 수 있습니다.

$user->subscription()->activate();

Paddle 웹훅 처리

Paddle은 웹훅을 통해 다양한 이벤트를 애플리케이션에 알릴 수 있습니다. 기본적으로 Cashier 서비스 제공자가 Cashier의 웹훅 컨트롤러를 가리키는 라우트가 등록됩니다. 이 컨트롤러는 수신되는 모든 웹훅 요청을 처리합니다.

기본적으로 이 컨트롤러는 실패한 요금이 너무 많은 구독, 구독 업데이트 및 결제 수단 변경을 자동으로 처리합니다. 하지만 곧 알게 되겠지만 이 컨트롤러를 확장하여 원하는 Paddle 웹훅 이벤트를 처리할 수 있습니다.

애플리케이션이 Paddle 웹훅을 처리할 수 있도록 하려면 Paddle 제어판에서 웹훅 URL을 구성해야 합니다. 기본적으로 Cashier의 웹훅 컨트롤러는 /paddle/webhook URL 경로에 응답합니다. Paddle 제어판에서 활성화해야 하는 모든 웹훅의 전체 목록은 다음과 같습니다.

  • 고객 업데이트됨
  • 거래 완료됨
  • 거래 업데이트됨
  • 구독 생성됨
  • 구독 업데이트됨
  • 구독 일시 중지됨
  • 구독 취소됨
exclamation

Cashier에 포함된 웹훅 서명 확인 미들웨어로 수신 요청을 보호해야 합니다.

웹훅 및 CSRF 보호

Paddle 웹훅은 Laravel의 CSRF 보호를 우회해야 하므로 Laravel이 수신되는 Paddle 웹훅에 대한 CSRF 토큰을 확인하지 않도록 해야 합니다. 이를 수행하려면 애플리케이션의 bootstrap/app.php 파일에서 CSRF 보호에서 paddle/*를 제외해야 합니다.

->withMiddleware(function (Middleware $middleware) {
$middleware->validateCsrfTokens(except: [
'paddle/*',
]);
})

웹훅 및 로컬 개발

로컬 개발 중에 Paddle에서 애플리케이션 웹훅을 보낼 수 있도록 하려면 Ngrok 또는 Expose와 같은 사이트 공유 서비스를 통해 애플리케이션을 노출해야 합니다. Laravel Sail을 사용하여 로컬에서 애플리케이션을 개발하는 경우 Sail의 사이트 공유 명령을 사용할 수 있습니다.

웹훅 이벤트 핸들러 정의

Cashier는 실패한 요금 및 기타 일반적인 Paddle 웹훅에 대한 구독 취소를 자동으로 처리합니다. 그러나 처리하려는 추가 웹훅 이벤트가 있는 경우 Cashier에서 디스패치하는 다음 이벤트를 수신하여 처리할 수 있습니다.

  • Laravel\Paddle\Events\WebhookReceived
  • Laravel\Paddle\Events\WebhookHandled

두 이벤트 모두 Paddle 웹훅의 전체 페이로드를 포함합니다. 예를 들어 transaction.billed 웹훅을 처리하려는 경우 이벤트를 처리할 수신기를 등록할 수 있습니다.

<?php
 
namespace App\Listeners;
 
use Laravel\Paddle\Events\WebhookReceived;
 
class PaddleEventListener
{
/**
* 수신된 Paddle 웹훅 처리.
*/
public function handle(WebhookReceived $event): void
{
if ($event->payload['event_type'] === 'transaction.billed') {
// 수신 이벤트 처리...
}
}
}

Cashier는 수신된 웹훅 유형에 전용인 이벤트도 발생시킵니다. Paddle의 전체 페이로드 외에도 청구 가능 모델, 구독 또는 영수증과 같이 웹훅을 처리하는 데 사용된 관련 모델도 포함합니다.

  • Laravel\Paddle\Events\CustomerUpdated
  • Laravel\Paddle\Events\TransactionCompleted
  • Laravel\Paddle\Events\TransactionUpdated
  • Laravel\Paddle\Events\SubscriptionCreated
  • Laravel\Paddle\Events\SubscriptionUpdated
  • Laravel\Paddle\Events\SubscriptionPaused
  • Laravel\Paddle\Events\SubscriptionCanceled

애플리케이션의 .env 파일에서 CASHIER_WEBHOOK 환경 변수를 정의하여 기본 제공 웹훅 라우트를 재정의할 수도 있습니다. 이 값은 웹훅 라우트에 대한 전체 URL이어야 하며 Paddle 제어판에 설정된 URL과 일치해야 합니다.

CASHIER_WEBHOOK=https://example.com/my-paddle-webhook-url

웹훅 서명 확인

웹훅을 안전하게 보호하기 위해 Paddle의 웹훅 서명을 사용할 수 있습니다. 편의를 위해 Cashier는 들어오는 Paddle 웹훅 요청이 유효한지 검증하는 미들웨어를 자동으로 포함합니다.

웹훅 검증을 활성화하려면 애플리케이션의 .env 파일에 PADDLE_WEBHOOK_SECRET 환경 변수가 정의되어 있는지 확인하십시오. 웹훅 비밀 키는 Paddle 계정 대시보드에서 검색할 수 있습니다.

단일 결제

제품 결제

고객을 위한 제품 구매를 시작하려면 청구 가능한 모델 인스턴스에서 checkout 메서드를 사용하여 구매에 대한 결제 세션을 생성할 수 있습니다. checkout 메서드는 하나 이상의 가격 ID를 허용합니다. 필요한 경우 연관 배열을 사용하여 구매 중인 제품의 수량을 제공할 수 있습니다.

use Illuminate\Http\Request;
 
Route::get('/buy', function (Request $request) {
$checkout = $request->user()->checkout(['pri_tshirt', 'pri_socks' => 5]);
 
return view('buy', ['checkout' => $checkout]);
});

결제 세션을 생성한 후에는 Cashier에서 제공하는 paddle-button Blade 컴포넌트를 사용하여 사용자가 Paddle 결제 위젯을 보고 구매를 완료할 수 있도록 할 수 있습니다.

<x-paddle-button :checkout="$checkout" class="px-8 py-4">
Buy
</x-paddle-button>

체크아웃 세션에는 customData 메서드가 있어 기본 트랜잭션 생성에 원하는 사용자 지정 데이터를 전달할 수 있습니다. 사용자 지정 데이터를 전달할 때 사용할 수 있는 옵션에 대해 자세히 알아보려면 Paddle 문서를 참조하십시오.

$checkout = $user->checkout('pri_tshirt')
->customData([
'custom_option' => $value,
]);

트랜잭션 환불

트랜잭션 환불은 구매 시 사용된 고객의 결제 수단으로 환불된 금액을 반환합니다. Paddle 구매를 환불해야 하는 경우 Cashier\Paddle\Transaction 모델에서 refund 메서드를 사용할 수 있습니다. 이 메서드는 첫 번째 인수로 이유를 받고, 연관 배열로 선택적 금액과 함께 환불할 가격 ID를 하나 이상 받습니다. transactions 메서드를 사용하여 지정된 청구 가능 모델에 대한 트랜잭션을 검색할 수 있습니다.

예를 들어 가격 pri_123pri_456에 대한 특정 트랜잭션을 환불한다고 가정해 보겠습니다. pri_123은 전액 환불하고 pri_456은 2달러만 환불하려고 합니다.

use App\Models\User;
 
$user = User::find(1);
 
$transaction = $user->transactions()->first();
 
$response = $transaction->refund('Accidental charge', [
'pri_123', // 이 가격은 전액 환불...
'pri_456' => 200, // 이 가격은 부분적으로만 환불...
]);

위의 예는 트랜잭션에서 특정 품목을 환불합니다. 전체 트랜잭션을 환불하려면 이유만 제공하면 됩니다.

$response = $transaction->refund('Accidental charge');

환불에 대한 자세한 내용은 Paddle의 환불 문서를 참조하십시오.

exclamation

환불은 완전히 처리되기 전에 항상 Paddle의 승인을 받아야 합니다.

트랜잭션 크레딧

환불과 마찬가지로 트랜잭션을 크레딧 처리할 수도 있습니다. 트랜잭션을 크레딧 처리하면 자금이 고객의 잔액에 추가되어 향후 구매에 사용할 수 있습니다. 트랜잭션 크레딧 처리는 수동으로 수집된 트랜잭션에만 수행할 수 있으며, Paddle이 구독 크레딧을 자동으로 처리하므로 자동으로 수집된 트랜잭션(예: 구독)에는 수행할 수 없습니다.

$transaction = $user->transactions()->first();
 
// 특정 품목을 완전히 크레딧 처리...
$response = $transaction->credit('Compensation', 'pri_123');

자세한 내용은 크레딧에 대한 Paddle 문서를 참조하십시오.

exclamation

크레딧은 수동으로 수집된 트랜잭션에만 적용할 수 있습니다. 자동으로 수집된 트랜잭션은 Paddle에서 자체적으로 크레딧 처리합니다.

트랜잭션

transactions 속성을 통해 청구 가능 모델의 트랜잭션 배열을 쉽게 검색할 수 있습니다.

use App\Models\User;
 
$user = User::find(1);
 
$transactions = $user->transactions;

트랜잭션은 제품 및 구매에 대한 지불을 나타내며 송장이 함께 제공됩니다. 완료된 트랜잭션만 애플리케이션의 데이터베이스에 저장됩니다.

고객의 트랜잭션을 나열할 때 트랜잭션 인스턴스의 메서드를 사용하여 관련 결제 정보를 표시할 수 있습니다. 예를 들어 사용자가 송장을 쉽게 다운로드할 수 있도록 테이블에 모든 트랜잭션을 나열할 수 있습니다.

<table>
@foreach ($transactions as $transaction)
<tr>
<td>{{ $transaction->billed_at->toFormattedDateString() }}</td>
<td>{{ $transaction->total() }}</td>
<td>{{ $transaction->tax() }}</td>
<td><a href="{{ route('download-invoice', $transaction->id) }}" target="_blank">Download</a></td>
</tr>
@endforeach
</table>

download-invoice 라우트는 다음과 같을 수 있습니다:

use Illuminate\Http\Request;
use Laravel\Paddle\Transaction;
 
Route::get('/download-invoice/{transaction}', function (Request $request, Transaction $transaction) {
return $transaction->redirectToInvoicePdf();
})->name('download-invoice');

과거 및 예정된 결제

lastPaymentnextPayment 메서드를 사용하여 반복 구독에 대한 고객의 과거 또는 예정된 결제를 검색하고 표시할 수 있습니다.

use App\Models\User;
 
$user = User::find(1);
 
$subscription = $user->subscription();
 
$lastPayment = $subscription->lastPayment();
$nextPayment = $subscription->nextPayment();

이러한 메서드는 모두 Laravel\Paddle\Payment의 인스턴스를 반환합니다. 그러나 lastPayment는 웹후크에 의해 트랜잭션이 아직 동기화되지 않은 경우 null을 반환하고, nextPayment는 청구 주기가 종료된 경우 (예: 구독이 취소된 경우) null을 반환합니다.

다음 결제: {{ $nextPayment->amount() }} (은)는 {{ $nextPayment->date()->format('d/m/Y') }}에 예정되어 있습니다.

테스팅

테스트하는 동안에는 통합이 예상대로 작동하는지 확인하기 위해 청구 흐름을 수동으로 테스트해야 합니다.

CI 환경 내에서 실행되는 테스트를 포함하여 자동화된 테스트의 경우, Laravel의 HTTP 클라이언트를 사용하여 Paddle에 대한 HTTP 호출을 모방할 수 있습니다. 이것은 Paddle의 실제 응답을 테스트하지는 않지만, Paddle의 API를 실제로 호출하지 않고도 애플리케이션을 테스트할 수 있는 방법을 제공합니다.