399 lines
10 KiB
PHP
399 lines
10 KiB
PHP
<?php
|
|
|
|
namespace Laravel\Paddle;
|
|
|
|
use Exception;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Laravel\Paddle\Exceptions\PaddleException;
|
|
use Money\Currencies\ISOCurrencies;
|
|
use Money\Currency;
|
|
use Money\Formatter\IntlMoneyFormatter;
|
|
use Money\Money;
|
|
use NumberFormatter;
|
|
|
|
class Cashier
|
|
{
|
|
const VERSION = '2.6.3';
|
|
|
|
/**
|
|
* The custom currency formatter.
|
|
*
|
|
* @var callable
|
|
*/
|
|
protected static $formatCurrencyUsing;
|
|
|
|
/**
|
|
* Indicates if Cashier routes will be registered.
|
|
*
|
|
* @var bool
|
|
*/
|
|
public static $registersRoutes = true;
|
|
|
|
/**
|
|
* Indicates if Cashier will mark past due subscriptions as invalid.
|
|
*
|
|
* @var bool
|
|
*/
|
|
public static $deactivatePastDue = true;
|
|
|
|
/**
|
|
* The customer model class name.
|
|
*
|
|
* @var string
|
|
*/
|
|
public static $customerModel = Customer::class;
|
|
|
|
/**
|
|
* The subscription model class name.
|
|
*
|
|
* @var string
|
|
*/
|
|
public static $subscriptionModel = Subscription::class;
|
|
|
|
/**
|
|
* The subscription item model class name.
|
|
*
|
|
* @var string
|
|
*/
|
|
public static $subscriptionItemModel = SubscriptionItem::class;
|
|
|
|
/**
|
|
* The transaction model class name.
|
|
*
|
|
* @var string
|
|
*/
|
|
public static $transactionModel = Transaction::class;
|
|
|
|
/**
|
|
* Preview prices for a given set of items.
|
|
*
|
|
* @param array|string $items
|
|
* @param array $options
|
|
* @return \Illuminate\Support\Collection
|
|
*/
|
|
public static function previewPrices($items, array $options = [])
|
|
{
|
|
$items = static::api('POST', 'pricing-preview', array_merge([
|
|
'items' => static::normalizeItems($items),
|
|
], $options))['data']['details']['line_items'];
|
|
|
|
return collect($items)->map(function (array $item) {
|
|
return new PricePreview($item);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get the customer instance by its Paddle customer ID.
|
|
*
|
|
* @param string $customerId
|
|
* @return \Laravel\Paddle\Billable|null
|
|
*/
|
|
public static function findBillable($customerId)
|
|
{
|
|
return (new static::$customerModel)->where('paddle_id', $customerId)->first()?->billable;
|
|
}
|
|
|
|
/**
|
|
* Get the Paddle webhook url.
|
|
*
|
|
* @return string
|
|
*/
|
|
public static function webhookUrl()
|
|
{
|
|
return config('cashier.webhook') ?? route('cashier.webhook');
|
|
}
|
|
|
|
/**
|
|
* Perform a Paddle API call.
|
|
*
|
|
* @param string $method
|
|
* @param string $uri
|
|
* @param array|null $payload
|
|
* @return \Illuminate\Http\Client\Response
|
|
*
|
|
* @throws \Laravel\Paddle\Exceptions\PaddleException
|
|
*/
|
|
public static function api($method, $uri, ?array $payload = null)
|
|
{
|
|
if (empty($apiKey = config('cashier.api_key', config('cashier.auth_code')))) {
|
|
throw new Exception('Paddle API key not set.');
|
|
}
|
|
|
|
$host = static::apiUrl();
|
|
|
|
/** @var \Illuminate\Http\Client\Response $response */
|
|
$response = Http::withToken($apiKey)
|
|
->withUserAgent('Laravel\Paddle/'.static::VERSION)
|
|
->withHeaders(['Paddle-Version' => 1])
|
|
->$method("{$host}/{$uri}", $payload);
|
|
|
|
if (isset($response['error'])) {
|
|
$message = "Paddle API error '{$response['error']['detail']}' occurred";
|
|
|
|
if (isset($response['error']['errors'])) {
|
|
$message .= ' with validation errors ('.json_encode($response['error']['errors']).')';
|
|
}
|
|
|
|
throw (new PaddleException($message))->setError($response['error']);
|
|
}
|
|
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Get the Paddle API url.
|
|
*
|
|
* @return string
|
|
*/
|
|
public static function apiUrl()
|
|
{
|
|
return 'https://'.(config('cashier.sandbox') ? 'sandbox-' : '').'api.paddle.com';
|
|
}
|
|
|
|
/**
|
|
* Normalize the given items to a Paddle accepted format.
|
|
*
|
|
* @param array|string $items
|
|
* @param string $priceKey
|
|
* @return array
|
|
*/
|
|
public static function normalizeItems($items, string $priceKey = 'price_id'): array
|
|
{
|
|
return collect($items)->map(function ($item, $key) use ($priceKey) {
|
|
if (is_array($item)) {
|
|
return $item;
|
|
}
|
|
|
|
if (is_string($key)) {
|
|
return [
|
|
$priceKey => $key,
|
|
'quantity' => $item,
|
|
];
|
|
}
|
|
|
|
return [
|
|
$priceKey => $item,
|
|
'quantity' => 1,
|
|
];
|
|
})->values()->all();
|
|
}
|
|
|
|
/**
|
|
* Set the custom currency formatter.
|
|
*
|
|
* @param callable $callback
|
|
* @return void
|
|
*/
|
|
public static function formatCurrencyUsing(callable $callback)
|
|
{
|
|
static::$formatCurrencyUsing = $callback;
|
|
}
|
|
|
|
/**
|
|
* Format the given amount into a displayable currency.
|
|
*
|
|
* @param int $amount
|
|
* @param string $currency
|
|
* @param string|null $locale
|
|
* @param array $options
|
|
* @return string
|
|
*/
|
|
public static function formatAmount($amount, $currency, $locale = null, array $options = [])
|
|
{
|
|
if (static::$formatCurrencyUsing) {
|
|
return call_user_func(static::$formatCurrencyUsing, $amount, $currency, $locale, $options);
|
|
}
|
|
|
|
$money = new Money($amount, new Currency(strtoupper($currency)));
|
|
|
|
$locale = $locale ?? config('cashier.currency_locale');
|
|
|
|
$numberFormatter = new NumberFormatter($locale, NumberFormatter::CURRENCY);
|
|
|
|
if (isset($options['min_fraction_digits'])) {
|
|
$numberFormatter->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $options['min_fraction_digits']);
|
|
}
|
|
|
|
$moneyFormatter = new IntlMoneyFormatter($numberFormatter, new ISOCurrencies());
|
|
|
|
return $moneyFormatter->format($money);
|
|
}
|
|
|
|
/**
|
|
* Determine if the given currency uses cents.
|
|
*
|
|
* @param \Money\Currency $currency
|
|
* @return bool
|
|
*/
|
|
public static function currencyUsesCents(Currency $currency)
|
|
{
|
|
return ! in_array($currency->getCode(), ['JPY', 'KRW'], true);
|
|
}
|
|
|
|
/**
|
|
* Configure Cashier to not register its routes.
|
|
*
|
|
* @return static
|
|
*/
|
|
public static function ignoreRoutes()
|
|
{
|
|
static::$registersRoutes = false;
|
|
|
|
return new static;
|
|
}
|
|
|
|
/**
|
|
* Configure Cashier to maintain past due subscriptions as active.
|
|
*
|
|
* @return static
|
|
*/
|
|
public static function keepPastDueSubscriptionsActive()
|
|
{
|
|
static::$deactivatePastDue = false;
|
|
|
|
return new static;
|
|
}
|
|
|
|
/**
|
|
* Set the customer model class name.
|
|
*
|
|
* @param string $customerModel
|
|
* @return void
|
|
*/
|
|
public static function useCustomerModel($customerModel)
|
|
{
|
|
static::$customerModel = $customerModel;
|
|
}
|
|
|
|
/**
|
|
* Set the subscription model class name.
|
|
*
|
|
* @param string $subscriptionModel
|
|
* @return void
|
|
*/
|
|
public static function useSubscriptionModel($subscriptionModel)
|
|
{
|
|
static::$subscriptionModel = $subscriptionModel;
|
|
}
|
|
|
|
/**
|
|
* Set the subscription item model class name.
|
|
*
|
|
* @param string $subscriptionItemModel
|
|
* @return void
|
|
*/
|
|
public static function useSubscriptionItemModel($subscriptionItemModel)
|
|
{
|
|
static::$subscriptionItemModel = $subscriptionItemModel;
|
|
}
|
|
|
|
/**
|
|
* Set the transaction model class name.
|
|
*
|
|
* @param string $transactionModel
|
|
* @return void
|
|
*/
|
|
public static function useTransactionModel($transactionModel)
|
|
{
|
|
static::$transactionModel = $transactionModel;
|
|
}
|
|
|
|
/**
|
|
* Create a fake Cashier instance.
|
|
*
|
|
* @return \Laravel\Paddle\CashierFake
|
|
*/
|
|
public static function fake(...$arguments)
|
|
{
|
|
return CashierFake::fake(...$arguments);
|
|
}
|
|
|
|
/**
|
|
* Pass-thru to the CashierFake method of the same name.
|
|
*
|
|
* @param callable|int|null $callback
|
|
* @return void
|
|
*/
|
|
public static function assertCustomerUpdated($callback = null)
|
|
{
|
|
CashierFake::assertCustomerUpdated($callback);
|
|
}
|
|
|
|
/**
|
|
* Pass-thru to the CashierFake method of the same name.
|
|
*
|
|
* @param callable|int|null $callback
|
|
* @return void
|
|
*/
|
|
public static function assertTransactionCompleted($callback = null)
|
|
{
|
|
CashierFake::assertTransactionCompleted($callback);
|
|
}
|
|
|
|
/**
|
|
* Pass-thru to the CashierFake method of the same name.
|
|
*
|
|
* @param callable|int|null $callback
|
|
* @return void
|
|
*/
|
|
public static function assertTransactionUpdated($callback = null)
|
|
{
|
|
CashierFake::assertTransactionUpdated($callback);
|
|
}
|
|
|
|
/**
|
|
* Pass-thru to the CashierFake method of the same name.
|
|
*
|
|
* @param callable|int|null $callback
|
|
* @return void
|
|
*/
|
|
public static function assertSubscriptionCreated($callback = null)
|
|
{
|
|
CashierFake::assertSubscriptionCreated($callback);
|
|
}
|
|
|
|
/**
|
|
* Pass-thru to the CashierFake method of the same name.
|
|
*
|
|
* @param callable|int|null $callback
|
|
* @return void
|
|
*/
|
|
public static function assertSubscriptionNotCreated($callback = null)
|
|
{
|
|
CashierFake::assertSubscriptionNotCreated($callback);
|
|
}
|
|
|
|
/**
|
|
* Pass-thru to the CashierFake method of the same name.
|
|
*
|
|
* @param callable|int|null $callback
|
|
* @return void
|
|
*/
|
|
public static function assertSubscriptionUpdated($callback = null)
|
|
{
|
|
CashierFake::assertSubscriptionUpdated($callback);
|
|
}
|
|
|
|
/**
|
|
* Pass-thru to the CashierFake method of the same name.
|
|
*
|
|
* @param callable|int|null $callback
|
|
* @return void
|
|
*/
|
|
public static function assertSubscriptionCanceled($callback = null)
|
|
{
|
|
CashierFake::assertSubscriptionCanceled($callback);
|
|
}
|
|
|
|
/**
|
|
* Pass-thru to the CashierFake method of the same name.
|
|
*
|
|
* @param callable|int|null $callback
|
|
* @return void
|
|
*/
|
|
public static function assertSubscriptionPaused($callback = null)
|
|
{
|
|
CashierFake::assertSubscriptionPaused($callback);
|
|
}
|
|
}
|