Skip to content

Commit

Permalink
Stripe checkout support
Browse files Browse the repository at this point in the history
  • Loading branch information
philipxyc committed Sep 1, 2022
1 parent 0099fcf commit e27424d
Show file tree
Hide file tree
Showing 7 changed files with 386 additions and 1 deletion.
5 changes: 5 additions & 0 deletions Extension.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ public function registerPaymentGateways()
'name' => 'lang:igniter.payregister::default.stripe.text_payment_title',
'description' => 'lang:igniter.payregister::default.stripe.text_payment_desc',
],
\Igniter\PayRegister\Payments\StripeCheckout::class => [
'code' => 'stripecheckout',
'name' => 'lang:igniter.payregister::default.stripecheckout.text_payment_title',
'description' => 'lang:igniter.payregister::default.stripecheckout.text_payment_desc',
],
\Igniter\PayRegister\Payments\Mollie::class => [
'code' => 'mollie',
'name' => 'lang:igniter.payregister::default.mollie.text_payment_title',
Expand Down
29 changes: 29 additions & 0 deletions language/en/default.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,35 @@
'help_locale_code' => 'See <a href="https://stripe.com/docs/js/appendix/supported_locales">Stripe.js supported locales</a',
],

'stripecheckout' => [
'text_tab_general' => 'General',
'text_payment_title' => 'Stripe Checkout',
'text_payment_desc' => 'Accept credit card, Apple Pay, Google Pay using Stripe builtin Checkout page',
'text_credit_or_debit' => 'Credit or debit card',

'text_auth_only' => 'Authorization Only',
'text_auth_capture' => 'Authorization & Capture',
'text_description' => 'Pay by Credit Card using Stripe',
'text_live' => 'Live',
'text_test' => 'Test',
'text_stripe_charge_description' => '%s Charge for %s',
'text_payment_status' => 'Payment %s (%s)',

'label_title' => 'Title',
'label_description' => 'Description',
'label_transaction_mode' => 'Transaction Mode',
'label_transaction_type' => 'Transaction Type',
'label_test_secret_key' => 'Test Secret Key',
'label_test_publishable_key' => 'Test Publishable Key',
'label_live_secret_key' => 'Live Secret Key',
'label_live_publishable_key' => 'Live Publishable Key',
'label_locale_code' => 'Locale Code',
'label_priority' => 'Priority',
'label_status' => 'Status',

'help_locale_code' => 'See <a href="https://stripe.com/docs/js/appendix/supported_locales">Stripe.js supported locales</a',
],

'mollie' => [
'text_payment_title' => 'Mollie Payment',
'text_payment_desc' => 'Accept credit card payments using Mollie API',
Expand Down
2 changes: 1 addition & 1 deletion payments/Stripe.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class Stripe extends BasePaymentGateway
public function registerEntryPoints()
{
return [
'stripe_webhook' => 'processWebhookUrl',
// 'stripe_webhook' => 'processWebhookUrl',
];
}

Expand Down
222 changes: 222 additions & 0 deletions payments/StripeCheckout.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
<?php

namespace Igniter\PayRegister\Payments;

use Admin\Classes\BasePaymentGateway;
use Admin\Models\Orders_model;
use Exception;
use Igniter\Flame\Exception\ApplicationException;
use Igniter\Flame\Traits\EventEmitter;
use Igniter\PayRegister\Traits\PaymentHelpers;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Str;
use Stripe\StripeClient;

class StripeCheckout extends BasePaymentGateway
{
use EventEmitter;
use PaymentHelpers;

public function registerEntryPoints()
{
return [
'stripe_webhook' => 'processWebhookUrl',
'stripe_checkout_return_url' => 'processSuccessUrl',
'stripe_checkout_cancel_url' => 'processCancelUrl',
];
}

public function isTestMode()
{
return $this->model->transaction_mode != 'live';
}

public function getPublishableKey()
{
return $this->isTestMode() ? $this->model->test_publishable_key : $this->model->live_publishable_key;
}

public function getSecretKey()
{
return $this->isTestMode() ? $this->model->test_secret_key : $this->model->live_secret_key;
}

public function shouldAuthorizePayment()
{
return $this->model->transaction_type == 'auth_only';
}

public function isApplicable($total, $host)
{
return $host->order_total <= $total;
}

/**
* @param array $data
* @param \Admin\Models\Payments_model $host
* @param \Admin\Models\Orders_model $order
*
* @return mixed
*/
public function processPaymentForm($data, $host, $order)
{
$this->validatePaymentMethod($order, $host);

$fields = $this->getPaymentFormFields($order, $data);

try {
$gateway = $this->createGateway();
$response = $gateway->checkout->sessions->create($fields);

return Redirect::to($response->url);
} catch (Exception $ex) {
$order->logPaymentAttempt('Payment error -> '.$ex->getMessage(), 0, $fields, []);
throw new ApplicationException('Sorry, there was an error processing your payment. Please try again later.');
}
}

public function processSuccessUrl($params)
{
$hash = $params[0] ?? null;
$redirectPage = input('redirect');
$cancelPage = input('cancel');

$order = $this->createOrderModel()->whereHash($hash)->first();

try {
if (!$hash || !$order instanceof Orders_model)
throw new ApplicationException('No order found');

if (!strlen($redirectPage))
throw new ApplicationException('No redirect page found');

if (!strlen($cancelPage))
throw new ApplicationException('No cancel page found');

$paymentMethod = $order->payment_method;
if (!$paymentMethod || $paymentMethod->getGatewayClass() != static::class)
throw new ApplicationException('No valid payment method found');

$order->logPaymentAttempt('Payment successful', 1, [], $paymentMethod, true);
$order->updateOrderStatus($paymentMethod->order_status, ['notify' => false]);
$order->markAsPaymentProcessed();

return Redirect::to(page_url($redirectPage, [
'id' => $order->getKey(),
'hash' => $order->hash,
]));
} catch (Exception $ex) {
$order->logPaymentAttempt('Payment error -> '.$ex->getMessage(), 0, [], []);
flash()->warning($ex->getMessage())->important();
}

return Redirect::to(page_url($cancelPage));
}

public function processCancelUrl($params)
{
$hash = $params[0] ?? null;
$order = $this->createOrderModel()->whereHash($hash)->first();
if (!$hash || !$order instanceof Orders_model)
throw new ApplicationException('No order found');

if (!strlen($redirectPage = input('redirect')))
throw new ApplicationException('No redirect page found');

$paymentMethod = $order->payment_method;
if (!$paymentMethod || $paymentMethod->getGatewayClass() != static::class)
throw new ApplicationException('No valid payment method found');

$order->logPaymentAttempt('Payment canceled by customer', 0, input());

return Redirect::to(page_url($redirectPage));
}

protected function createGateway()
{
\Stripe\Stripe::setAppInfo(
'TastyIgniter Stripe',
'1.0.0',
'https://tastyigniter.com/marketplace/item/igniter-payregister',
'pp_partner_JZyCCGR3cOwj9S' // Used by Stripe to identify this integration
);

$stripeClient = new StripeClient([
'api_key' => $this->getSecretKey(),
]);

$this->fireSystemEvent('payregister.stripe.extendClient', [$stripeClient]);

return $stripeClient;
}

protected function getPaymentFormFields($order, $data = [])
{
$cancelUrl = $this->makeEntryPointUrl('stripe_checkout_cancel_url').'/'.$order->hash;
$successUrl = $this->makeEntryPointUrl('stripe_checkout_return_url').'/'.$order->hash;
$successUrl .= '?redirect='.array_get($data, 'successPage').'&cancel='.array_get($data, 'cancelPage');

$fields = [
'line_items' => [
[
'price_data' => [
'currency' => currency()->getUserCurrency(),
// All amounts sent to Stripe must be in integers, representing the lowest currency unit (cents)
'unit_amount_decimal' => number_format($order->order_total, 2, '.', '') * 100,
'product_data' => [
'name' => 'Test',
],
],
'quantity' => 1,
],
],
'cancel_url' => $cancelUrl.'?redirect='.array_get($data, 'cancelPage'),
'success_url' => $successUrl,
'mode' => 'payment',
'metadata' => [
'order_id' => $order->order_id,
],
];

$this->fireSystemEvent('payregister.stripecheckout.extendFields', [&$fields, $order, $data]);

return $fields;
}

public function processWebhookUrl()
{
if (strtolower(request()->method()) !== 'post')
return response('Request method must be POST', 400);

$payload = json_decode(request()->getContent(), true);
if (!isset($payload['type']) || !strlen($eventType = $payload['type']))
return response('Missing webhook event name', 400);

$eventName = Str::studly(str_replace('.', '_', $eventType));
$methodName = 'webhookHandle'.$eventName;

if (method_exists($this, $methodName))
$this->$methodName($payload);

Event::fire('payregister.stripecheckout.webhook.handle'.$eventName, [$payload]);

return response('Webhook Handled');
}

protected function webhookHandleCheckoutSessionCompleted($payload)
{
if ($order = Orders_model::find($payload['data']['object']['metadata']['order_id'])) {
if (!$order->isPaymentProcessed()) {
if ($payload['data']['object']['status'] === 'requires_capture') {
$order->logPaymentAttempt('Payment authorized', 1, [], $payload['data']['object']);
} else {
$order->logPaymentAttempt('Payment successful', 1, [], $payload['data']['object'], true);
}

$order->updateOrderStatus($this->model->order_status, ['notify' => false]);
$order->markAsPaymentProcessed();
}
}
}
}
118 changes: 118 additions & 0 deletions payments/stripecheckout/fields.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?php

return [
'fields' => [
'setup' => [
'type' => 'partial',
'path' => '$/igniter/payregister/payments/stripecheckout/info',
],
'transaction_mode' => [
'label' => 'lang:igniter.payregister::default.stripecheckout.label_transaction_mode',
'type' => 'radiotoggle',
'default' => 'test',
'span' => 'left',
'options' => [
'live' => 'lang:igniter.payregister::default.stripecheckout.text_live',
'test' => 'lang:igniter.payregister::default.stripecheckout.text_test',
],
],
'transaction_type' => [
'label' => 'lang:igniter.payregister::default.stripecheckout.label_transaction_type',
'type' => 'radiotoggle',
'default' => 'auth_capture',
'span' => 'right',
'options' => [
'auth_capture' => 'lang:igniter.payregister::default.stripecheckout.text_auth_capture',
'auth_only' => 'lang:igniter.payregister::default.stripecheckout.text_auth_only',
],
],
'live_secret_key' => [
'label' => 'lang:igniter.payregister::default.stripecheckout.label_live_secret_key',
'type' => 'text',
'span' => 'left',
'trigger' => [
'action' => 'show',
'field' => 'transaction_mode',
'condition' => 'value[live]',
],
],
'live_publishable_key' => [
'label' => 'lang:igniter.payregister::default.stripecheckout.label_live_publishable_key',
'type' => 'text',
'span' => 'right',
'trigger' => [
'action' => 'show',
'field' => 'transaction_mode',
'condition' => 'value[live]',
],
],
'test_secret_key' => [
'label' => 'lang:igniter.payregister::default.stripecheckout.label_test_secret_key',
'type' => 'text',
'span' => 'left',
'trigger' => [
'action' => 'show',
'field' => 'transaction_mode',
'condition' => 'value[test]',
],
],
'test_publishable_key' => [
'label' => 'lang:igniter.payregister::default.stripecheckout.label_test_publishable_key',
'type' => 'text',
'span' => 'right',
'trigger' => [
'action' => 'show',
'field' => 'transaction_mode',
'condition' => 'value[test]',
],
],
'locale_code' => [
'label' => 'lang:igniter.payregister::default.stripecheckout.label_locale_code',
'type' => 'text',
'span' => 'left',
],
'order_fee_type' => [
'label' => 'lang:igniter.payregister::default.label_order_fee_type',
'type' => 'radiotoggle',
'span' => 'right',
'cssClass' => 'flex-width',
'default' => 1,
'options' => [
1 => 'lang:admin::lang.menus.text_fixed_amount',
2 => 'lang:admin::lang.menus.text_percentage',
],
],
'order_fee' => [
'label' => 'lang:igniter.payregister::default.label_order_fee',
'type' => 'currency',
'span' => 'right',
'cssClass' => 'flex-width',
'default' => 0,
'comment' => 'lang:igniter.payregister::default.help_order_fee',
],
'order_total' => [
'label' => 'lang:igniter.payregister::default.label_order_total',
'type' => 'currency',
'span' => 'left',
'comment' => 'lang:igniter.payregister::default.help_order_total',
],
'order_status' => [
'label' => 'lang:igniter.payregister::default.label_order_status',
'type' => 'select',
'options' => [\Admin\Models\Statuses_model::class, 'getDropdownOptionsForOrder'],
'span' => 'right',
'comment' => 'lang:igniter.payregister::default.help_order_status',
],
],
'rules' => [
['transaction_mode', 'lang:igniter.payregister::default.stripecheckout.label_transaction_mode', 'string'],
['live_secret_key', 'lang:igniter.payregister::default.stripecheckout.label_live_secret_key', 'string'],
['live_publishable_key', 'lang:igniter.payregister::default.stripecheckout.label_live_publishable_key', 'string'],
['test_secret_key', 'lang:igniter.payregister::default.stripecheckout.label_test_secret_key', 'string'],
['test_publishable_key', 'lang:igniter.payregister::default.stripecheckout.label_test_publishable_key', 'string'],
['order_fee_type', 'lang:igniter.payregister::default.label_order_fee_type', 'integer'],
['order_fee', 'lang:igniter.payregister::default.label_order_fee', 'numeric'],
['order_total', 'lang:igniter.payregister::default.label_order_total', 'numeric'],
['order_status', 'lang:igniter.payregister::default.label_order_status', 'integer'],
],
];
Loading

0 comments on commit e27424d

Please sign in to comment.