Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -183,13 +183,4 @@ public function registerSettings()
];
}

/**
* registerFormWidgets
*/
public function registerFormWidgets()
{
return [
\Responsiv\Pay\FormWidgets\Discount::class => 'discount'
];
}
}
12 changes: 12 additions & 0 deletions classes/GatewayBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,18 @@ public function payOfflineMessage()
return '';
}

/**
* checkPaymentStatus checks the payment status with the gateway for an
* invoice that has been submitted but not yet confirmed. Gateways can
* implement this to poll for updated payment status (e.g. a pending
* capture that has since completed). Returns true if the payment has
* been confirmed as processed.
*/
public function checkPaymentStatus($invoice): bool
{
return false;
}

//
// Payment Profiles
//
Expand Down
30 changes: 29 additions & 1 deletion components/Payment.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php namespace Responsiv\Pay\Components;

use Log;
use Auth;
use Redirect;
use Cms\Classes\ComponentBase;
Expand All @@ -8,6 +9,7 @@
use Responsiv\Pay\Models\Invoice as InvoiceModel;
use Illuminate\Http\RedirectResponse;
use ApplicationException;
use Exception;

/**
* Payment screen for an existing invoice.
Expand Down Expand Up @@ -59,13 +61,39 @@ public function onRun()
protected function prepareVars()
{
$this->page['invoice'] = $invoice = $this->invoice();
$this->page['paymentMethods'] = $this->listAvailablePaymentMethods();

if ($invoice) {
$this->checkPaymentStatus($invoice);

$this->page->meta_title = $this->page->meta_title
? str_replace('%s', $invoice->getUniqueId(), $this->page->meta_title)
: 'Invoice #'.$invoice->getUniqueId();
}

$this->page['paymentMethods'] = $this->listAvailablePaymentMethods();
}

/**
* checkPaymentStatus polls the payment gateway to check if a submitted
* payment has since been confirmed. Silently fails on any error.
*/
protected function checkPaymentStatus($invoice)
{
if ($invoice->is_paid || !$invoice->is_payment_submitted) {
return;
}

try {
$paymentMethod = $invoice->payment_method;
if ($paymentMethod && ($driver = $paymentMethod->getDriverObject())) {
if ($driver->checkPaymentStatus($invoice)) {
$invoice->refresh();
}
}
}
catch (Exception $ex) {
Log::debug('Payment status check failed: ' . $ex->getMessage());
}
}

/**
Expand Down
5 changes: 4 additions & 1 deletion components/invoice/default.htm
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@ <h2>Invoice {{ invoice.invoice_number }}</h2>

<!-- Status -->
<div class="text-right">
{% if invoice.isPaid %}
{% if invoice.is_paid %}
<h4>This invoice has been paid!</h4>
<p>Thank you for your business.</p>
{% elseif invoice.is_payment_submitted %}
<h4>Your payment is being processed</h4>
<p>Your payment has been submitted and is awaiting confirmation. No further action is needed.</p>
{% else %}
<h4>This invoice has not been paid</h4>
<a class="btn btn-success btn-lg" href="{{ invoice.getReceiptUrl() }}">
Expand Down
8 changes: 7 additions & 1 deletion components/payment/default.htm
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{% put scripts %}
{% for method in paymentMethods %}
{{ method.renderPaymentScripts(invoice.currency ?invoice.currency.code : 'USD')|raw }}
{{ method.renderPaymentScripts()|raw }}
{% endfor %}
{% endput %}
{% if invoice %}
Expand All @@ -11,6 +11,12 @@ <h4 class="text-success">
This invoice has been paid. Thank-you!
</h4>
</div>
{% elseif invoice.is_payment_submitted %}
<div class="payment-invoice-pending">
<h4 class="text-info">
Your payment has been submitted and is being processed. Thank-you!
</h4>
</div>
{% else %}

<!-- Invoice header -->
Expand Down
3 changes: 1 addition & 2 deletions controllers/Invoices.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use Responsiv\Pay\Models\Tax;
use Backend\Classes\Controller;
use Responsiv\Pay\Models\Invoice;
use Responsiv\Currency\Models\Currency as CurrencyModel;

/**
* Invoices Back-end Controller
Expand Down Expand Up @@ -75,7 +74,7 @@ public function preview($recordId = null, $context = null)
{
try {
$invoice = Invoice::find($recordId);
$this->vars['currency'] = CurrencyModel::findByCode($invoice->currency);
$this->vars['currency'] = $invoice->getCurrencyObject();
}
catch (Exception $ex) {
$this->handleError($ex);
Expand Down
2 changes: 2 additions & 0 deletions controllers/invoices/HasInvoiceStatus.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ protected function getInvoiceStatusPopupTitle()
return "Add Payment";
case 'approved':
return "Approve Invoice";
case 'refunded':
return "Refund Invoice";
case 'void':
return "Void Invoice";
default:
Expand Down
2 changes: 1 addition & 1 deletion controllers/invoices/_preview_toolbar.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
->outline() ?>
<?php elseif ($statusCode === 'paid'): ?>
<?= Ui::popupButton("Mark Refunded", 'onLoadChangeInvoiceStatusForm')
->ajaxData(['invoice_id' => $formModel->id, 'status_preset' => 'void'])
->ajaxData(['invoice_id' => $formModel->id, 'status_preset' => 'refunded'])
->size(500)
->icon('icon-ban')
->outline() ?>
Expand Down
10 changes: 6 additions & 4 deletions controllers/invoices/_scoreboard_edit.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,21 @@
</div>
<?php endif ?>

<?php $currencyObj = $formModel->getCurrencyObject(); ?>

<div class="scoreboard-item title-value">
<h4><?= __("Subtotal") ?></h4>
<p><?= Currency::format($formModel->subtotal ?: 0) ?></p>
<p><?= $currencyObj?->formatCurrency($formModel->subtotal ?: 0) ?: Currency::format($formModel->subtotal ?: 0) ?></p>
<p class="description">
<?= __("Discounts") ?>: <?= Currency::format($formModel->discount ?: 0) ?>
<?= __("Discounts") ?>: <?= $currencyObj?->formatCurrency($formModel->discount ?: 0) ?: Currency::format($formModel->discount ?: 0) ?>
</p>
</div>

<div class="scoreboard-item title-value">
<h4><?= __("Total") ?></h4>
<p><?= Currency::format($formModel->total) ?></p>
<p><?= $currencyObj?->formatCurrency($formModel->total) ?: Currency::format($formModel->total) ?></p>
<p class="description">
<?= __("Tax") ?>: <?= Currency::format($formModel->tax) ?>
<?= __("Tax") ?>: <?= $currencyObj?->formatCurrency($formModel->tax) ?: Currency::format($formModel->tax) ?>
</p>
</div>
</div>
4 changes: 2 additions & 2 deletions controllers/invoices/_scoreboard_preview.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@

<div class="scoreboard-item title-value">
<h4><?= __("Total") ?></h4>
<p><?= $formModel->currency?->formatCurrency($formModel->total) ?: Currency::format($formModel->total) ?></p>
<p><?= $formModel->getCurrencyObject()?->formatCurrency($formModel->total) ?: Currency::format($formModel->total) ?></p>
<p class="description">
<?= __("Tax") ?>: <?= $formModel->currency?->formatCurrency($formModel->tax) ?: Currency::format($formModel->tax) ?>
<?= __("Tax") ?>: <?= $formModel->getCurrencyObject()?->formatCurrency($formModel->tax) ?: Currency::format($formModel->tax) ?>
</p>
</div>
40 changes: 38 additions & 2 deletions docs/building-payment-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,42 @@ Using the redirection method, when the user clicks the Pay button, they are redi

### Client-side Method

Using the client-side method, the payment form is handled using custom JavaScript. When the user clicks Pay, the request is submitted back to the server using a locally registered API endpoint, which communicates to the payment gateway via a REST API or equivalent. The response given with JSON And is processed by the JavaScript code. If everything is successful the `onPay` AJAX handler is called to redirect to the confirmation page.
Using the client-side method, the payment form is handled using custom JavaScript. When the user clicks Pay, the request is submitted back to the server using a locally registered API endpoint, which communicates to the payment gateway via a REST API or equivalent. The response given with JSON and is processed by the JavaScript code. If everything is successful the `onPay` AJAX handler is called to redirect to the confirmation page.

> **Note**: View the `Responsiv\Pay\PaymentTypes\PayPalPayment` class for an example of a redirection payment type.
> **Note**: View the `Responsiv\Pay\PaymentTypes\PayPalPayment` class for an example of a client-side payment type.

## Handling Pending Payments

Some payment gateways don't confirm transactions immediately. For example, PayPal may return a `PENDING` capture status when the seller's account requires manual review. In these cases, you should update the invoice status to `approved` to indicate the customer has submitted payment, while waiting for final confirmation via a webhook.

```php
use Responsiv\Pay\Models\InvoiceStatus;

if ($captureStatus === 'COMPLETED') {
$invoice->markAsPaymentProcessed();
}
elseif ($captureStatus === 'PENDING') {
$invoice->updateInvoiceStatus(InvoiceStatus::STATUS_APPROVED);
}
```

When the invoice status is `approved`, the payment page will display a "payment is being processed" message instead of showing payment methods again, preventing the customer from paying twice.

## Checking Payment Status

Gateways can implement the `checkPaymentStatus` method to poll the payment provider when the customer revisits the payment page. This allows pending payments to be confirmed without waiting for a webhook. The method is called automatically by the `Payment` component when an invoice has been submitted but not yet confirmed.

```php
public function checkPaymentStatus($invoice): bool
{
// Query your payment gateway API to check the current status
// If the payment is now confirmed:
$invoice->markAsPaymentProcessed();
return true;

// If still pending or unable to check:
return false;
}
```

The method should return `true` if the payment was confirmed, or `false` otherwise. Any exceptions thrown will be caught silently by the component, so the payment page will still render normally if the check fails.
151 changes: 0 additions & 151 deletions formwidgets/Discount.php

This file was deleted.

Loading