Laravel Cashier (Paddle)
업데이트됨번역일: 2026년 10월 20일
이 페이지는 원문이 업데이트되어 번역이 갱신되었습니다.
- 원문 수정
- 2026년 10월 13일
- 번역 갱신
- 2026년 10월 20일
Laravel Cashier (Paddle)
소개
WARNING
이 문서는 Paddle Billing과 연동하는 Cashier Paddle 2.x를 다룹니다. Paddle Classic을 사용 중이라면 Cashier Paddle 1.x를 사용해야 합니다.
Laravel Cashier Paddle은 Paddle의 구독 결제 서비스를 표현력 있고 유연하게 사용할 수 있는 인터페이스를 제공합니다. 반복해서 작성해야 하는 구독 결제 코드를 대부분 처리해 줍니다. 기본적인 구독 관리 외에도 구독 전환, 구독 수량, 구독 일시 중지, 취소 유예 기간 등을 지원합니다.
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 테이블, 고객의 구독을 저장할 subscriptions 및 subscription_items 테이블, 고객과 관련된 모든 Paddle 거래를 저장할 transactions 테이블을 생성합니다.
php artisan migrateWARNING
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=truePaddle 샌드박스 환경에서는 PADDLE_SANDBOX를 true로 설정해야 합니다. 운영 환경에 배포하여 Paddle의 실제 판매자 환경을 사용한다면 false로 설정해야 합니다.
PADDLE_RETAIN_KEY는 선택 사항이며, Paddle의 Retain을 사용할 때만 설정합니다.
Paddle JS
Paddle은 결제 위젯을 시작할 때 자체 JavaScript 라이브러리를 사용합니다. 애플리케이션 레이아웃에서 닫는 </head> 태그 바로 앞에 @paddleJS Blade 지시어를 넣어 라이브러리를 불러올 수 있습니다.
<head>
...
@paddleJS
</head>통화 설정
청구서에 표시할 금액의 형식을 지정할 로케일을 설정할 수 있습니다. Cashier는 내부적으로 PHP의 NumberFormatter 클래스를 사용해 통화 로케일을 설정합니다.
CASHIER_CURRENCY_LOCALE=nl_BEWARNING
en 이외의 로케일을 사용하려면 서버에 ext-intl PHP 확장 모듈이 설치되고 설정되어 있어야 합니다.
기본 모델 재정의
자체 모델을 정의하고 해당 Cashier 모델을 상속하면 Cashier가 내부적으로 사용하는 모델을 확장할 수 있습니다.
use Laravel\Paddle\Subscription as CashierSubscription;
class Subscription extends CashierSubscription
{
// ...
}모델을 정의한 다음에는 Laravel\Paddle\Cashier 클래스를 통해 Cashier가 사용자 정의 모델을 사용하도록 지정할 수 있습니다. 일반적으로 애플리케이션의 App\Providers\AppServiceProvider 클래스에 있는 boot 메서드에서 설정합니다.
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);
}빠른 시작
상품 판매
NOTE
Paddle Checkout을 사용하기 전에 Paddle 대시보드에서 고정 가격을 가진 상품을 정의해야 합니다. 또한 Paddle 웹훅 처리를 설정해야 합니다.
애플리케이션에 상품 결제와 구독 결제를 도입하는 일이 막막하게 느껴질 수 있습니다. Cashier와 Paddle의 Checkout Overlay를 사용하면 현대적이고 안정적인 결제 연동을 쉽게 구현할 수 있습니다.
정기 결제가 아닌 일회성 상품을 판매할 때는 Cashier로 Paddle 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를 여는 버튼을 넣습니다. Cashier Paddle에는 paddle-button Blade 컴포넌트가 포함되어 있습니다. 오버레이 결제를 직접 렌더링할 수도 있습니다.
<x-paddle-button :checkout="$checkout" class="px-8 py-4">
Buy Product
</x-paddle-button>Paddle Checkout에 메타데이터 전달
상품을 판매할 때는 애플리케이션에서 정의한 Cart와 Order 모델로 완료된 주문과 구매 상품을 관리하는 경우가 많습니다. 구매를 완료하도록 고객을 Paddle Checkout Overlay로 안내할 때 기존 주문 식별자를 전달해야 할 수 있습니다. 그러면 고객이 애플리케이션으로 돌아왔을 때 완료된 구매를 해당 주문과 연결할 수 있습니다.
이를 위해 checkout 메서드에 사용자 정의 데이터 배열을 전달할 수 있습니다. 사용자가 결제를 시작할 때 애플리케이션에서 대기 중인 Order를 생성한다고 가정하겠습니다. 이 예제의 Cart와 Order 모델은 설명을 위한 것으로, 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 Checkout Overlay에 전달합니다.
고객이 결제를 마치면 주문을 “완료” 상태로 표시해야 합니다. Paddle이 보내고 Cashier가 이벤트로 전달하는 웹훅을 수신하여 주문 정보를 데이터베이스에 저장할 수 있습니다.
먼저 Cashier가 전달하는 TransactionCompleted 이벤트를 수신합니다. 일반적으로 애플리케이션의 AppServiceProvider에 있는 boot 메서드에서 이벤트 리스너를 등록합니다.
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']);
}
}자세한 내용은 Paddle 문서의 transaction.completed 이벤트에 포함되는 데이터를 참고하세요.
구독 판매
NOTE
Paddle Checkout을 사용하기 전에 Paddle 대시보드에서 고정 가격을 가진 상품을 정의해야 합니다. 또한 Paddle 웹훅 처리를 설정해야 합니다.
애플리케이션에 상품 결제와 구독 결제를 도입하는 일이 막막하게 느껴질 수 있습니다. Cashier와 Paddle의 Checkout Overlay를 사용하면 현대적이고 안정적인 결제 연동을 쉽게 구현할 수 있습니다.
Cashier와 Paddle Checkout Overlay로 구독을 판매하는 방법을 살펴보겠습니다. 기본 월간 요금제(price_basic_monthly)와 연간 요금제(price_basic_yearly)를 제공하는 구독 서비스를 예로 들겠습니다. Paddle 대시보드에서는 두 가격을 “Basic” 상품(pro_basic) 아래에 묶을 수 있습니다. 이 서비스에서 “Expert” 요금제를 pro_expert로 제공할 수도 있습니다.
먼저 고객이 서비스에 가입하는 과정을 살펴보겠습니다. 고객이 애플리케이션의 가격 안내 페이지에서 Basic 요금제의 “구독” 버튼을 누르면, 선택한 요금제의 Paddle Checkout Overlay가 열립니다. 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 뷰에는 Checkout Overlay를 여는 버튼을 넣습니다. Cashier Paddle에는 paddle-button Blade 컴포넌트가 포함되어 있습니다. 오버레이 결제를 직접 렌더링할 수도 있습니다.
<x-paddle-button :checkout="$checkout" class="px-8 py-4">
Subscribe
</x-paddle-button>이제 구독 버튼을 누르면 고객이 결제 정보를 입력하고 구독을 시작할 수 있습니다. 일부 결제 수단은 처리에 몇 초가 걸리므로 구독이 실제로 시작된 시점을 알 수 있도록 Cashier 웹훅 처리도 설정해야 합니다.
고객이 구독을 시작할 수 있게 되었으니, 애플리케이션의 일부 영역은 구독자만 접근하도록 제한해야 합니다. Cashier의 Billable 트레이트가 제공하는 subscribed 메서드로 사용자의 현재 구독 상태를 확인할 수 있습니다.
@if ($user->subscribed())
<p>You are subscribed.</p>
@endif특정 상품이나 가격을 구독하고 있는지도 쉽게 확인할 수 있습니다.
@if ($user->subscribedToProduct('pro_basic'))
<p>You are subscribed to our Basic product.</p>
@endif
@if ($user->subscribedToPrice('price_basic_monthly'))
<p>You are subscribed to our monthly Basic plan.</p>
@endif구독 확인 미들웨어 작성
들어온 요청이 구독자의 요청인지 확인하는 미들웨어를 만들면 편리합니다. 미들웨어를 정의한 뒤 라우트에 지정하면 구독하지 않은 사용자의 접근을 막을 수 있습니다.
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class Subscribed
{
/**
* Handle an incoming request.
*/
public function handle(Request $request, Closure $next): Response
{
if (! $request->user()?->subscribed()) {
// Redirect user to billing page and ask them to subscribe...
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); // With "$price" being "price_basic_yearly" for this example.
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');이렇게 하면 현재 결제 기간이 끝날 때 구독이 취소됩니다.
NOTE
Cashier 웹훅 처리를 설정했다면 Cashier는 Paddle에서 들어온 웹훅을 확인하여 관련 데이터베이스 테이블을 자동으로 동기화합니다. 예를 들어 Paddle 대시보드에서 고객의 구독을 취소하면 Cashier가 해당 웹훅을 받아 애플리케이션 데이터베이스의 구독을 “취소됨”으로 표시합니다.
결제 세션
고객에게 요금을 청구하는 대부분의 작업은 Paddle의 Checkout Overlay 위젯이나 인라인 결제를 통한 “체크아웃”으로 수행합니다.
Paddle로 결제를 처리하기 전에 Paddle 결제 설정 대시보드에서 애플리케이션의 기본 결제 링크를 정의해야 합니다.
오버레이 결제
Checkout Overlay 위젯을 표시하려면 먼저 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">
Subscribe
</x-paddle-button>기본적으로 위젯에는 Paddle의 기본 스타일이 적용됩니다. 컴포넌트에 data-theme='light'와 같은 Paddle 지원 속성을 추가하여 위젯을 꾸밀 수 있습니다.
<x-paddle-button :checkout="$checkout" class="px-8 py-4" data-theme="light">
Subscribe
</x-paddle-button>Paddle 결제 위젯은 비동기로 동작합니다. 사용자가 위젯에서 구독을 생성하면 Paddle이 애플리케이션에 웹훅을 보내 데이터베이스의 구독 상태를 갱신할 수 있게 합니다. 따라서 Paddle의 상태 변경을 반영하도록 웹훅을 올바르게 설정해야 합니다.
WARNING
구독 상태 변경 후 해당 웹훅이 도착하기까지의 지연은 대개 짧습니다. 그래도 결제를 마친 직후에는 사용자의 구독을 아직 조회하지 못할 수 있으므로 애플리케이션에서 이를 고려해야 합니다.
오버레이 결제 직접 렌더링
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
>
Buy Product
</a>인라인 결제
Paddle의 오버레이 방식 결제 위젯 대신 인라인으로 위젯을 표시할 수도 있습니다. 이 방식에서도 결제 화면의 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에서는 결제 세션을 생성할 때 사용할 고객 기본값을 정의할 수 있습니다. 고객의 이메일 주소와 이름을 미리 채워 두면 고객이 바로 결제 위젯의 결제 단계로 넘어갈 수 있습니다. 청구 가능 모델에서 다음 메서드를 재정의하여 기본값을 설정합니다.
/**
* Get the customer's name to associate with Paddle.
*/
public function paddleName(): string|null
{
return $this->name;
}
/**
* Get the customer's email address to associate with 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 = 'pri_123', 'default')
->returnTo(route('home'));
return view('billing', ['checkout' => $checkout]);
});subscribe 메서드의 첫 번째 인수는 사용자가 구독할 특정 가격이며, Paddle의 가격 식별자와 일치해야 합니다. returnTo 메서드에는 사용자가 결제를 성공적으로 마친 뒤 이동할 URL을 전달합니다. subscribe의 두 번째 인수는 구독을 구분하는 내부 “유형”입니다. 애플리케이션에서 구독을 하나만 제공한다면 default 또는 primary처럼 지정할 수 있습니다. 이 유형은 애플리케이션 내부에서만 사용하며 사용자에게 표시하는 값이 아닙니다. 공백을 포함해서는 안 되며, 구독을 생성한 후에는 절대로 변경해서는 안 됩니다.
customData 메서드로 구독에 관한 사용자 정의 메타데이터 배열을 전달할 수도 있습니다.
$checkout = $request->user()->subscribe($premium = 'pri_123', 'default')
->customData(['key' => 'value'])
->returnTo(route('home'));구독 결제 세션을 생성한 뒤에는 Cashier Paddle에 포함된 paddle-button Blade 컴포넌트에 전달할 수 있습니다.
<x-paddle-button :checkout="$checkout" class="px-8 py-4">
Subscribe
</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
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if ($request->user() && ! $request->user()->subscribed()) {
// This user is not a paying customer...
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 메서드를 사용할 수 있습니다. 일반적으로 AppServiceProvider의 register 메서드에서 호출합니다.
use Laravel\Paddle\Cashier;
/**
* Register any application services.
*/
public function register(): void
{
Cashier::keepPastDueSubscriptionsActive();
}WARNING
past_due 상태의 구독은 결제 정보를 갱신하기 전까지 변경할 수 없습니다. 따라서 이 상태에서 swap 또는 updateQuantity 메서드를 호출하면 예외가 발생합니다.
구독 스코프
대부분의 구독 상태는 쿼리 스코프로도 제공됩니다. 이를 사용하면 특정 상태의 구독을 데이터베이스에서 쉽게 조회할 수 있습니다.
// Get all valid subscriptions...
$subscriptions = Subscription::query()->valid()->get();
// Get all of the canceled subscriptions for a user...
$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를 전달해야 합니다.
// Charge a single price...
$response = $user->subscription()->charge('pri_123');
// Charge multiple prices at once...
$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 웹훅을 보내고, 애플리케이션 데이터베이스의 구독 정보가 갱신됩니다.
요금제 변경
사용자는 애플리케이션을 구독한 뒤 새 요금제로 변경하고 싶을 수 있습니다. 구독 요금제를 갱신하려면 구독의 swap 메서드에 Paddle 가격 식별자를 전달합니다.
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의 일할 계산 정책에 관한 자세한 내용은 일할 계산 문서를 참고하세요.
구독 수량
구독 요금이 “수량”에 따라 달라지는 경우가 있습니다. 예를 들어 프로젝트 관리 애플리케이션이 프로젝트당 월 10달러를 청구할 수 있습니다. incrementQuantity와 decrementQuantity 메서드로 구독 수량을 쉽게 늘리거나 줄일 수 있습니다.
$user = User::find(1);
$user->subscription()->incrementQuantity();
// Add five to the subscription's current quantity...
$user->subscription()->incrementQuantity(5);
$user->subscription()->decrementQuantity();
// Subtract five from the subscription's current quantity...
$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(['price_monthly', 'price_chat' => 5]);기존 구독에 가격을 추가하려면 구독의 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]);WARNING
구독의 마지막 가격은 제거할 수 없습니다. 이 경우 구독 자체를 취소해야 합니다.
여러 구독
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월 5일인 구독을 고객이 3월 1일에 일시 중지했다면, paused 메서드는 3월 5일까지 false를 반환합니다. 일반적으로 사용자는 이미 결제한 주기가 끝날 때까지 애플리케이션을 계속 사용할 수 있기 때문입니다.
기본적으로 일시 중지는 다음 결제 주기부터 적용되므로 고객은 결제한 기간의 남은 부분을 사용할 수 있습니다. 즉시 중지하려면 pauseNow 메서드를 사용합니다.
$user->subscription()->pauseNow();pauseUntil 메서드로 특정 시점까지 구독을 일시 중지할 수 있습니다.
$user->subscription()->pauseUntil(now()->plus(months: 1));지금 즉시 구독을 중지하고 지정한 시점까지 유지하려면 pauseNowUntil 메서드를 사용합니다.
$user->subscription()->pauseNowUntil(now()->plus(months: 1));일시 중지를 요청했지만 아직 유예 기간 중인지 확인하려면 onPausedGracePeriod 메서드를 사용합니다.
if ($user->subscription()->onPausedGracePeriod()) {
// ...
}일시 중지한 구독을 재개하려면 구독의 resume 메서드를 호출합니다.
$user->subscription()->resume();WARNING
일시 중지된 구독은 수정할 수 없습니다. 요금제를 변경하거나 수량을 갱신하려면 먼저 구독을 재개해야 합니다.
구독 취소
구독을 취소하려면 사용자 구독의 cancel 메서드를 호출합니다.
$user->subscription()->cancel();구독을 취소하면 Cashier는 데이터베이스의 ends_at 열을 자동으로 설정합니다. 이 열은 subscribed 메서드가 언제부터 false를 반환할지 결정하는 데 사용됩니다. 예를 들어 3월 5일 종료 예정인 구독을 고객이 3월 1일에 취소했다면, subscribed는 3월 5일까지 true를 반환합니다. 일반적으로 이미 결제한 주기가 끝날 때까지 애플리케이션을 계속 사용할 수 있도록 하기 위해서입니다.
구독을 취소했지만 아직 유예 기간 중인지 확인하려면 onGracePeriod 메서드를 사용합니다.
if ($user->subscription()->onGracePeriod()) {
// ...
}구독을 즉시 취소하려면 cancelNow 메서드를 호출합니다.
$user->subscription()->cancelNow();아직 유예 기간인 구독의 취소를 철회하려면 stopCancelation 메서드를 호출합니다.
$user->subscription()->stopCancelation();WARNING
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에 지시합니다.
WARNING
체험 종료일 전에 구독을 취소하지 않으면 체험 기간이 끝나는 즉시 요금이 청구됩니다. 사용자에게 체험 종료일을 반드시 안내하세요.
사용자 인스턴스의 onTrial 메서드로 체험 기간 중인지 확인할 수 있습니다.
if ($user->onTrial()) {
// ...
}기존 체험 기간이 만료되었는지는 hasExpiredTrial 메서드로 확인합니다.
if ($user->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()->plus(days: 10)
]);Cashier는 이 체험이 기존 구독에 연결되어 있지 않기 때문에 “일반 체험(generic trial)”이라고 부릅니다. 현재 날짜가 trial_ends_at 값을 지나지 않았다면 User 인스턴스의 onTrial 메서드는 true를 반환합니다.
if ($user->onTrial()) {
// User is within their trial period...
}실제 구독을 생성할 준비가 되면 평소와 같이 subscribe 메서드를 사용합니다.
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]);
});사용자의 체험 종료일은 trialEndsAt 메서드로 조회할 수 있습니다. 체험 중이면 Carbon 날짜 인스턴스를, 그렇지 않으면 null을 반환합니다. 기본 구독이 아닌 특정 구독의 체험 종료일을 조회하려면 선택 사항인 구독 유형 매개변수를 전달할 수 있습니다.
if ($user->onTrial('default')) {
$trialEndsAt = $user->trialEndsAt();
}사용자가 아직 실제 구독을 생성하지 않은 “일반 체험” 기간에 있는지 구체적으로 확인하려면 onGenericTrial 메서드를 사용합니다.
if ($user->onGenericTrial()) {
// User is within their "generic" trial period...
}체험 기간 연장 또는 구독 활성화
기존 구독의 체험 기간은 extendTrial 메서드에 체험이 끝날 시점을 전달하여 연장할 수 있습니다.
$user->subscription()->extendTrial(now()->plus(days: 5));또는 구독의 activate 메서드를 호출해 체험을 끝내고 구독을 즉시 활성화할 수 있습니다.
$user->subscription()->activate();Paddle 웹훅 처리
Paddle은 웹훅을 통해 다양한 이벤트를 애플리케이션에 알릴 수 있습니다. Cashier 서비스 프로바이더는 기본적으로 Cashier 웹훅 컨트롤러를 가리키는 라우트를 등록합니다. 이 컨트롤러가 들어오는 모든 웹훅 요청을 처리합니다.
기본 컨트롤러는 결제 실패가 너무 많이 발생한 구독의 취소, 구독 갱신, 결제 수단 변경 등을 자동 처리합니다. 필요하다면 이 컨트롤러를 확장해 다른 Paddle 웹훅 이벤트도 처리할 수 있습니다.
애플리케이션이 Paddle 웹훅을 처리할 수 있도록 Paddle 제어판에서 웹훅 URL을 설정해야 합니다. Cashier 웹훅 컨트롤러의 기본 경로는 /paddle/webhook입니다. Paddle 제어판에서 활성화해야 할 웹훅 전체 목록은 다음과 같습니다.
- Customer Updated
- Transaction Completed
- Transaction Updated
- Subscription Created
- Subscription Updated
- Subscription Paused
- Subscription Canceled
WARNING
Cashier에 포함된 웹훅 서명 검증 미들웨어로 수신 요청을 반드시 보호하세요.
웹훅과 CSRF 보호
Paddle 웹훅은 Laravel의 CSRF 보호를 우회해야 합니다. 들어오는 Paddle 웹훅의 CSRF 토큰을 Laravel이 검증하지 않도록 설정하세요. 애플리케이션의 bootstrap/app.php에서 paddle/*를 CSRF 보호 대상에서 제외하면 됩니다.
->withMiddleware(function (Middleware $middleware): void {
$middleware->preventRequestForgery(except: [
'paddle/*',
]);
})웹훅과 로컬 개발
로컬 개발 중 Paddle이 웹훅을 보낼 수 있도록 하려면 Ngrok이나 Expose 같은 사이트 공유 서비스로 애플리케이션을 외부에 노출해야 합니다. Laravel Sail로 로컬 개발 중이라면 Sail의 사이트 공유 명령어를 사용할 수 있습니다.
웹훅 이벤트 핸들러 정의
Cashier는 결제 실패에 따른 구독 취소를 비롯한 일반적인 Paddle 웹훅을 자동 처리합니다. 추가로 처리할 웹훅이 있다면 Cashier가 전달하는 다음 이벤트를 수신할 수 있습니다.
Laravel\Paddle\Events\WebhookReceivedLaravel\Paddle\Events\WebhookHandled
두 이벤트 모두 Paddle 웹훅의 전체 페이로드를 포함합니다. 예를 들어 transaction.billed 웹훅을 처리하려면 해당 이벤트를 처리할 리스너를 등록할 수 있습니다.
<?php
namespace App\Listeners;
use Laravel\Paddle\Events\WebhookReceived;
class PaddleEventListener
{
/**
* Handle received Paddle webhooks.
*/
public function handle(WebhookReceived $event): void
{
if ($event->payload['event_type'] === 'transaction.billed') {
// Handle the incoming event...
}
}
}Cashier는 수신한 웹훅 유형별 전용 이벤트도 전달합니다. 이 이벤트에는 Paddle의 전체 페이로드뿐 아니라 웹훅 처리에 사용된 청구 가능 모델, 구독, 영수증 등 관련 모델도 포함됩니다.
Laravel\Paddle\Events\CustomerUpdatedLaravel\Paddle\Events\TransactionCompletedLaravel\Paddle\Events\TransactionUpdatedLaravel\Paddle\Events\SubscriptionCreatedLaravel\Paddle\Events\SubscriptionUpdatedLaravel\Paddle\Events\SubscriptionPausedLaravel\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_123과 pri_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', // Fully refund this price...
'pri_456' => 200, // Only partially refund this price...
]);위 예제는 거래의 특정 항목을 환불합니다. 전체 거래를 환불하려면 사유만 전달하면 됩니다.
$response = $transaction->refund('Accidental charge');자세한 내용은 Paddle 환불 문서를 참고하세요.
WARNING
환불 처리가 완료되려면 반드시 Paddle의 승인이 필요합니다.
거래 크레딧 지급
환불과 마찬가지로 거래에 크레딧을 지급할 수도 있습니다. 크레딧 지급은 고객 잔액에 금액을 추가하여 나중에 구매할 때 사용할 수 있게 합니다. 수동 수금 거래에만 적용할 수 있으며 구독과 같은 자동 수금 거래에는 적용할 수 없습니다. 구독 크레딧은 Paddle이 자동 처리하기 때문입니다.
$transaction = $user->transactions()->first();
// Credit a specific line item fully...
$response = $transaction->credit('Compensation', 'pri_123');자세한 내용은 Paddle 크레딧 문서를 참고하세요.
WARNING
크레딧은 수동 수금 거래에만 적용할 수 있습니다. 자동 수금 거래의 크레딧은 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');지난 결제와 예정된 결제
lastPayment와 nextPayment 메서드로 정기 구독의 지난 결제와 예정된 결제를 조회하여 표시할 수 있습니다.
use App\Models\User;
$user = User::find(1);
$subscription = $user->subscription();
$lastPayment = $subscription->lastPayment();
$nextPayment = $subscription->nextPayment();두 메서드는 Laravel\Paddle\Payment 인스턴스를 반환합니다. 다만 웹훅을 통해 거래가 아직 동기화되지 않았다면 lastPayment는 null을 반환합니다. 구독 취소 등으로 결제 주기가 끝났다면 nextPayment는 null을 반환합니다.
Next payment: {{ $nextPayment->amount() }} due on {{ $nextPayment->date()->format('d/m/Y') }}테스트
연동이 예상대로 동작하는지 확인하려면 청구 흐름을 직접 수동으로 테스트해야 합니다.
CI 환경을 포함한 자동 테스트에서는 Laravel HTTP 클라이언트로 Paddle에 보내는 HTTP 호출을 가짜 응답으로 대체할 수 있습니다. 이 방법은 Paddle의 실제 응답을 검증하지는 않지만, Paddle API를 실제로 호출하지 않고 애플리케이션을 테스트할 수 있게 합니다.