Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e87e5e124 | ||
|
|
8d27778ced | ||
|
|
7aa226468f | ||
|
|
f47aaccb43 | ||
|
|
9677c828a4 | ||
|
|
5d7a2d4d1b | ||
|
|
422296840d | ||
|
|
3d5c6ccfb2 | ||
|
|
9b81bc6e2a | ||
|
|
0aeba289b9 | ||
|
|
9633b63b1f | ||
|
|
552a5de37a | ||
|
|
bdeb8cc931 | ||
|
|
b77bcd8740 |
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -24,7 +24,7 @@ jobs:
|
||||
with:
|
||||
type: 'zip'
|
||||
filename: 'btcpay.ocmod.zip'
|
||||
exclusions: '*.git* *.github* composer.*'
|
||||
exclusions: '*.git* *.github* composer.* README.md'
|
||||
- name: Upload artifact to release page.
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
|
||||
19
README.md
Normal file
19
README.md
Normal file
@ -0,0 +1,19 @@
|
||||
# BTCPay for OpenCart extension
|
||||
|
||||
This free extension can be used with [OpenCart](https://www.opencart.com) version 3 or 4. You can also find this extension on the OpenCart [marketplace](https://www.opencart.com/index.php?route=marketplace/extension/info&extension_id=44269).
|
||||
|
||||
## Installation
|
||||
|
||||
You can find a detailed installation guide on how to setup the extension on our [OpenCart documentation](https://docs.btcpayserver.org/OpenCart/).
|
||||
|
||||
## Feedback
|
||||
|
||||
If you have issues or feature requests feel free to open an [issue](https://github.com/btcpayserver/opencart/issues) or join us on our [chat](https://chat.btcpayserver.org) or [telegram channel](https://t.me/btcpayserver).
|
||||
|
||||
Please let us always know your OpenCart, BTCPay extension, PHP versions. You can find them on the extension configuration page.
|
||||
|
||||
## Development
|
||||
|
||||
OpenCart 4 development happens on the `master` branch and OpenCart 3 is maintained on the `3.x` branch but is mostly in maintenance mode.
|
||||
|
||||
|
||||
280
admin/controller/payment/btcpay.php
Normal file
280
admin/controller/payment/btcpay.php
Normal file
@ -0,0 +1,280 @@
|
||||
<?php
|
||||
namespace Opencart\Admin\Controller\Extension\Btcpay\Payment;
|
||||
|
||||
use BTCPayServer\Client\Store;
|
||||
use BTCPayServer\Client\Webhook;
|
||||
|
||||
require_once DIR_EXTENSION . 'btcpay/system/library/btcpay/autoload.php';
|
||||
require_once DIR_EXTENSION . 'btcpay/system/library/btcpay/version.php';
|
||||
|
||||
class Btcpay extends \Opencart\System\Engine\Controller {
|
||||
private $error = [];
|
||||
|
||||
public function index(): void {
|
||||
$this->load->language('extension/btcpay/payment/btcpay');
|
||||
$this->document->setTitle($this->language->get('heading_title'));
|
||||
|
||||
$this->load->model('setting/setting');
|
||||
$this->load->model('localisation/order_status');
|
||||
$this->load->model('localisation/geo_zone');
|
||||
|
||||
$data['save'] = $this->url->link('extension/btcpay/payment/btcpay|save', 'user_token=' . $this->session->data['user_token']);
|
||||
$data['back'] = $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=payment');
|
||||
$data['cancel'] = $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=payment', true);
|
||||
$data['order_statuses'] = $this->model_localisation_order_status->getOrderStatuses();
|
||||
|
||||
$data['geo_zones'] = $this->model_localisation_geo_zone->getGeoZones();
|
||||
|
||||
if (isset($this->error['warning'])) {
|
||||
$data['error_warning'] = $this->error['warning'];
|
||||
} else {
|
||||
$data['error_warning'] = '';
|
||||
}
|
||||
|
||||
if (isset($this->session->data['success'])) {
|
||||
$data['success'] = $this->session->data['success'];
|
||||
unset($this->session->data['success']);
|
||||
} else {
|
||||
$data['success'] = '';
|
||||
}
|
||||
|
||||
$data['breadcrumbs'] = [];
|
||||
$data['breadcrumbs'][] = array(
|
||||
'text' => $this->language->get('text_home'),
|
||||
'href' => $this->url->link('common/dashboard', 'user_token=' . $this->session->data['user_token'], true)
|
||||
);
|
||||
$data['breadcrumbs'][] = array(
|
||||
'text' => $this->language->get('text_extension'),
|
||||
'href' => $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=payment', true)
|
||||
);
|
||||
$data['breadcrumbs'][] = array(
|
||||
'text' => $this->language->get('heading_title'),
|
||||
'href' => $this->url->link('extension/btcpay/payment/btcpay', 'user_token=' . $this->session->data['user_token'], true)
|
||||
);
|
||||
|
||||
$fields = [
|
||||
'payment_btcpay_status',
|
||||
'payment_btcpay_url',
|
||||
'payment_btcpay_api_auth_token',
|
||||
'payment_btcpay_btcpay_storeid',
|
||||
'payment_btcpay_webhook',
|
||||
'payment_btcpay_webhook_delete',
|
||||
'payment_btcpay_modal_mode',
|
||||
'payment_btcpay_new_status_id',
|
||||
'payment_btcpay_paid_status_id',
|
||||
'payment_btcpay_settled_status_id',
|
||||
'payment_btcpay_settled_paidover_status_id',
|
||||
'payment_btcpay_invalid_status_id',
|
||||
'payment_btcpay_expired_status_id',
|
||||
'payment_btcpay_expired_partialpayment_status_id',
|
||||
'payment_btcpay_expired_paidlate_status_id',
|
||||
'payment_btcpay_refunded_status_id',
|
||||
'payment_btcpay_total',
|
||||
'payment_btcpay_geo_zone_id',
|
||||
'payment_btcpay_debug_mode',
|
||||
];
|
||||
|
||||
// Process our fields to be sure they are displayed.
|
||||
foreach ($fields as $field) {
|
||||
if (isset($this->request->post[$field])) {
|
||||
$data[$field] = $this->request->post[$field];
|
||||
} else {
|
||||
$data[$field] = $this->config->get($field);
|
||||
}
|
||||
}
|
||||
|
||||
$data['payment_btcpay_sort_order'] = isset($this->request->post['payment_btcpay_sort_order']) ?
|
||||
$this->request->post['payment_btcpay_sort_order'] : $this->config->get('payment_btcpay_sort_order');
|
||||
|
||||
$data['header'] = $this->load->controller('common/header');
|
||||
$data['column_left'] = $this->load->controller('common/column_left');
|
||||
$data['footer'] = $this->load->controller('common/footer');
|
||||
|
||||
$this->response->setOutput($this->load->view('extension/btcpay/payment/btcpay', $data));
|
||||
}
|
||||
|
||||
protected function validate($messages): array {
|
||||
|
||||
$this->load->language('extension/btcpay/payment/btcpay');
|
||||
if (!$this->user->hasPermission('modify', 'extension/btcpay/payment/btcpay')) {
|
||||
$messages['error'] = $this->language->get('error_permission');
|
||||
}
|
||||
|
||||
if (!class_exists('BTCPayServer\Client\Health')) {
|
||||
$messages['error'] = $this->language->get('error_composer');
|
||||
}
|
||||
|
||||
if (!isset($messages['error'])) {
|
||||
$host = $this->request->post['payment_btcpay_url'];
|
||||
$apiKey = $this->request->post['payment_btcpay_api_auth_token'];
|
||||
$storeId = $this->request->post['payment_btcpay_btcpay_storeid'];
|
||||
|
||||
try {
|
||||
$client = new Store($host, $apiKey);
|
||||
$store = $client->getStore($storeId);
|
||||
if (empty($store->getId())) {
|
||||
$messages['error'] = $this->language->get('error_store_not_found');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$messages['error'] = $this->language->get('error_connect_to_btcpay');
|
||||
$this->log->write($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return $messages;
|
||||
}
|
||||
|
||||
public function save(): void {
|
||||
$this->load->language('extension/btcpay/payment/btcpay');
|
||||
$this->load->model('setting/setting');
|
||||
|
||||
$json = [];
|
||||
$redirect = false;
|
||||
|
||||
$json = $this->validate($json);
|
||||
|
||||
if (empty($json['error'])) {
|
||||
$host = $this->request->post['payment_btcpay_url'];
|
||||
$apiKey = $this->request->post['payment_btcpay_api_auth_token'];
|
||||
$storeId = $this->request->post['payment_btcpay_btcpay_storeid'];
|
||||
|
||||
// On saving we create a webhook if there is none yet.
|
||||
if ($this->webhookExists() === false) {
|
||||
if ($whData = $this->webhookSetup($host, $apiKey, $storeId)) {
|
||||
$this->request->post['payment_btcpay_webhook'] = $whData;
|
||||
$json['success'] = $this->language->get('notice_success_create_webhook');
|
||||
$redirect = true;
|
||||
} else {
|
||||
$json['error'] = $this->language->get('error_creating_webhook');
|
||||
}
|
||||
} else {
|
||||
// Check if the user wants to delete an existing webhook.
|
||||
if (isset($this->request->post['payment_btcpay_webhook_delete']) &&
|
||||
$this->request->post['payment_btcpay_webhook_delete'] === '1'
|
||||
) {
|
||||
// Try to delete the webhook on the provided host.
|
||||
$this->webhookDelete($host, $apiKey, $storeId);
|
||||
unset($this->request->post['payment_btcpay_webhook']);
|
||||
unset($this->request->post['payment_btcpay_webhook_delete']);
|
||||
$json['success'] = $this->language->get('notice_success_delete_webhook');
|
||||
$redirect = true;
|
||||
} else {
|
||||
// Need to convert existing webhook values back to array for storage.
|
||||
if (isset($this->request->post['payment_btcpay_webhook'])) {
|
||||
$whString = $this->request->post['payment_btcpay_webhook'];
|
||||
$whString = str_replace(['ID: ', 'SECRET: ', 'URL: '], '', $whString);
|
||||
$whArr = explode(' | ', $whString);
|
||||
if (count($whArr) === 3) {
|
||||
$whData = [
|
||||
'id' => $whArr[0],
|
||||
'secret' => $whArr[1],
|
||||
'url' => $whArr[2]
|
||||
];
|
||||
$this->request->post['payment_btcpay_webhook'] = $whData;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($json['error'])) {
|
||||
if (!empty($json['success'])) {
|
||||
$json['success'] = $this->language->get('notice_success') . ' ' . $json['success'];
|
||||
} else {
|
||||
$json['success'] = $this->language->get('notice_success');
|
||||
}
|
||||
$this->model_setting_setting->editSetting('payment_btcpay', $this->request->post);
|
||||
}
|
||||
|
||||
if ($redirect) {
|
||||
$this->session->data['success'] = $json['success'];
|
||||
unset($json['success']);
|
||||
$json['redirect'] = $this->url->link('extension/btcpay/payment/btcpay', 'user_token=' . $this->session->data['user_token'], true);
|
||||
}
|
||||
|
||||
$this->response->addHeader('Content-Type: application/json');
|
||||
$this->response->setOutput(json_encode($json));
|
||||
}
|
||||
|
||||
public function install(): void {
|
||||
$this->load->model('extension/btcpay/payment/btcpay');
|
||||
$this->model_extension_btcpay_payment_btcpay->install();
|
||||
}
|
||||
|
||||
public function uninstall(): void {
|
||||
$this->load->model('extension/btcpay/payment/btcpay');
|
||||
$this->model_extension_btcpay_payment_btcpay->uninstall();
|
||||
}
|
||||
|
||||
private function webhookExists(): bool {
|
||||
// Check if the config is any value set at all.
|
||||
$data = $this->config->get('payment_btcpay_webhook');
|
||||
if (empty($data) || !is_array($data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// todo: load webhook form BTCPay to check if the callback url domain is the same
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function webhookSetup($host, $apiKey, $storeId): ?array {
|
||||
$whEvents = [
|
||||
'InvoiceReceivedPayment',
|
||||
'InvoicePaymentSettled',
|
||||
'InvoiceProcessing',
|
||||
'InvoiceExpired',
|
||||
'InvoiceSettled',
|
||||
'InvoiceInvalid'
|
||||
];
|
||||
|
||||
try {
|
||||
$whClient = new Webhook( $host, $apiKey );
|
||||
$webhook = $whClient->createWebhook(
|
||||
$storeId,
|
||||
$this->webhookCallbackUrl(),
|
||||
$whEvents,
|
||||
null
|
||||
);
|
||||
|
||||
// Prepare data for settings storage.
|
||||
$whData = [
|
||||
'id' => $webhook->getData()['id'],
|
||||
'secret' => $webhook->getData()['secret'],
|
||||
'url' => $webhook->getData()['url']
|
||||
];
|
||||
|
||||
return $whData;
|
||||
} catch (\Throwable $e) {
|
||||
$this->log->write($e->getMessage());
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
private function webhookDelete($host, $apiKey, $storeId): void {
|
||||
$data = $this->config->get('payment_btcpay_webhook');
|
||||
|
||||
$client = new \BTCPayServer\Client\Webhook($host, $apiKey);
|
||||
try {
|
||||
$client->deleteWebhook($storeId, $data['id']);
|
||||
} catch (\Throwable $e) {
|
||||
$this->log->write('Error deleting webhook: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function webhookCallbackUrl(): string {
|
||||
$url = $this->url->link('extension/btcpay/payment/btcpay|callback', '', true);
|
||||
|
||||
// As we are in admin controller context we need to strip out the admin
|
||||
// path to receive the correct frontend callback url.
|
||||
$adminPathParts = explode('/', DIR_APPLICATION);
|
||||
end($adminPathParts); // Last array item is empty.
|
||||
$adminPath = prev($adminPathParts);
|
||||
if (!empty($adminPath)) {
|
||||
$url = str_replace($adminPath . '/', '', $url);
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,11 @@
|
||||
<?php
|
||||
|
||||
require_once DIR_SYSTEM . 'library/btcpay/version.php';
|
||||
require_once DIR_EXTENSION . '/btcpay/system/library/btcpay/version.php';
|
||||
|
||||
$_['heading_title'] = 'BTCPay Server';
|
||||
$_['text_edit'] = 'Edit BTCPay Settings';
|
||||
$_['text_version_info'] = 'Debug info: OpenCart ' . VERSION . ' with BTCPay Extension ' . BTCPAY_OPENCART_EXTENSION_VERSION . ' on PHP ' . phpversion();
|
||||
$_['text_support_info'] = 'For setup instructions follow our <a href="https://docs.btcpayserver.org/OpenCart" target="_blank" rel="noopener">setup guide</a>. If you run into any problems feel free to <a href="https://github.com/btcpayserver/opencart" target="_blank" rel="noopener">open an issue on Github</a> or come to our <a href="https://chat.btcpayserver.org/" target="_blank" rel="noopener">Mattermost chat</a>.';
|
||||
|
||||
$_['text_extension'] = 'Extensions';
|
||||
|
||||
$_['entry_status'] = 'Payment Method Enabled';
|
||||
@ -16,6 +15,7 @@ $_['entry_btcpay_storeid'] = 'BTCPay Store ID';
|
||||
$_['entry_webhook'] = 'Webhook Data';
|
||||
$_['entry_webhook_secret'] = 'Webhook Secret';
|
||||
$_['entry_webhook_delete'] = 'Delete Webhook';
|
||||
$_['entry_modal_mode'] = 'Modal/iFrame mode';
|
||||
$_['entry_total'] = 'Total';
|
||||
$_['entry_geo_zone'] = 'Geo Zone';
|
||||
$_['entry_sort_order'] = 'Sort Order';
|
||||
@ -34,15 +34,19 @@ $_['entry_debug_mode'] = 'Debug mode';
|
||||
$_['help_btcpay_url'] = 'The public URL of your BTCPay Server instance. e.g. https://demo.mainnet.btcpayserver.org. You need to have a BTCPay Server instance running, see "Requirements" for several options of deployment on our <a href="https://docs.btcpayserver.org/OpenCart" target="_blank" rel="noopener">setup guide</a>.';
|
||||
$_['help_webhook'] = 'The webhook will get created automatically after you entered BTCPay Server URL, API Key and Store ID. If you see this field filled with data (after you saved the form) all went well.';
|
||||
$_['help_webhook_delete'] = 'This is useful if you switch hosts or have problems with webhooks. When checked this will delete the webhook on OpenCart (and BTCPay Server if possible). Make sure to delete the webhook on BTCPay Server Store settings too if not done automatically. <strong>ATTENTION:</strong> You need to edit and <strong>save</strong> this settings page again so a new webhook gets created on BTCPay Server.';
|
||||
$_['help_modal_mode'] = 'If enabled the invoice will be shown in a modal/overlay (iFrame). Default behaviour is that the user will get redirected to BTCPay Server invoice page.';
|
||||
$_['help_total'] = 'The checkout total the order must reach before this payment method becomes active.';
|
||||
$_['help_debug_mode'] = 'If enabled debug output will be saved to the error logs found in System -> Maintenance -> Error logs. Should be disabled after debugging.';
|
||||
|
||||
$_['notice_success'] = 'BTCPay Server Payment details have been successfully updated.';
|
||||
$_['notice_success_webhook_renew'] = 'BTCPay Server Payment details have been successfully updated and successfully deleted webhook data.';
|
||||
$_['notice_success'] = 'BTCPay Server Payment details have been updated.';
|
||||
$_['notice_success_delete_webhook'] = 'Successfully deleted webhook. Please save again to create a new one, make sure that on BTCPay Server it does not exist twice.';
|
||||
$_['notice_success_create_webhook'] = 'Successfully created a webhook on the BTCPay instance.';
|
||||
|
||||
$_['error_permission'] = 'Warning: You do not have permission to modify BTCPay Server!';
|
||||
$_['error_composer'] = 'Unable to load btcpayserver-greenfield-php. Please download a compiled vendor folder or run composer.';
|
||||
$_['error_store_not_found'] = 'Successfully connected to BTCPay Server but no store with that ID found. Make sure you entered the correct store ID on that corresponding BTCPay Server URL.';
|
||||
$_['error_connect_to_btcpay'] = 'Error connecting to BTCPay Server instance. Make sure you provided the correct URL, API key.';
|
||||
$_['error_creating_webhook'] = 'Error creating webhook. Make sure you have a correct store and api key combination with the required permissions. Check OpenCart error logs.';
|
||||
|
||||
$_['text_btcpay'] = '<a href="https://btcpayserver.org/" target="_blank" rel="noopener"><img src="view/image/payment/btcpay.png" alt="BTCPay Server" title="BTCPay Server" style="border: 1px solid #EEEEEE;" /></a>';
|
||||
|
||||
$_['text_btcpay'] = '<a href="https://btcpayserver.org/" target="_blank" rel="noopener"><img src="/extension/btcpay/admin/view/image/payment/btcpay.png" alt="BTCPay Server" title="BTCPay Server" style="border: 1px solid #EEEEEE;" /></a>';
|
||||
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
class ModelExtensionPaymentBTCPay extends Model {
|
||||
public function install() {
|
||||
namespace Opencart\Admin\Model\Extension\Btcpay\Payment;
|
||||
class Btcpay extends \Opencart\System\Engine\Model {
|
||||
public function install(): void {
|
||||
$this->db->query("
|
||||
CREATE TABLE IF NOT EXISTS `" . DB_PREFIX . "btcpay_order` (
|
||||
`btcpay_order_id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||
@ -30,7 +30,7 @@ class ModelExtensionPaymentBTCPay extends Model {
|
||||
$this->model_setting_setting->editSetting('payment_btcpay', $defaults);
|
||||
}
|
||||
|
||||
public function uninstall() {
|
||||
public function uninstall(): void {
|
||||
$this->db->query("DROP TABLE IF EXISTS `" . DB_PREFIX . "btcpay_order`;");
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
301
admin/view/template/payment/btcpay.twig
Normal file
301
admin/view/template/payment/btcpay.twig
Normal file
@ -0,0 +1,301 @@
|
||||
{{ header }}{{ column_left }}
|
||||
<div id="content">
|
||||
<div class="page-header">
|
||||
<div class="container-fluid">
|
||||
<div class="float-end">
|
||||
<button type="submit" form="form_payment" data-bs-toggle="tooltip" title="{{ button_save }}" class="btn btn-primary"><i class="fas fa-save"></i></button>
|
||||
<a href="{{ back }}" data-bs-toggle="tooltip" title="{{ button_back }}" class="btn btn-light"><i class="fas fa-reply"></i></a>
|
||||
</div>
|
||||
<h1>{{ heading_title }}</h1>
|
||||
<ul class="breadcrumb">
|
||||
{% for breadcrumb in breadcrumbs %}
|
||||
<li class="breadcrumb-item"><a href="{{ breadcrumb['href'] }}">{{ breadcrumb['text'] }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container-fluid">
|
||||
{% if error %}
|
||||
<div class="alert alert-danger alert-dismissible"><i class="fas fa-exclamation-circle"></i> {{ error_warning }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if success %}
|
||||
<div class="alert alert-success">
|
||||
{{ success }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="alert alert-info" style="overflow: hidden;">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">{{ text_version_info }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">{{ text_support_info }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header"><i class="fas fa-pencil-alt"></i> {{ text_edit }}</div>
|
||||
<div class="card-body">
|
||||
<form action="{{ save }}" method="post" id="form_payment" data-oc-toggle="ajax">
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="input-status">{{ entry_status }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select name="payment_btcpay_status" id="input-status" class="form-select">
|
||||
{% if payment_btcpay_status %}
|
||||
<option value="1" selected="selected">{{ text_enabled }}</option>
|
||||
<option value="0">{{ text_disabled }}</option>
|
||||
{% else %}
|
||||
<option value="1">{{ text_enabled }}</option>
|
||||
<option value="0" selected="selected">{{ text_disabled }}</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="input-btcpay-url">{{ entry_btcpay_url }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" name="payment_btcpay_url" value="{{ payment_btcpay_url }}" placeholder="{{ entry_btcpay_url }}" id="input-btcpay-url" class="form-control" />
|
||||
<div class="help-block mt-1">
|
||||
{{ help_btcpay_url }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="input-api-auth-token">{{ entry_api_auth_token }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" name="payment_btcpay_api_auth_token" value="{{ payment_btcpay_api_auth_token }}" placeholder="{{ entry_api_auth_token }}" id="input-api-auth-token" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="input-btcpay-storeid">{{ entry_btcpay_storeid }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" name="payment_btcpay_btcpay_storeid" value="{{ payment_btcpay_btcpay_storeid }}" placeholder="{{ entry_btcpay_btcpay_storeid }}" id="input-btcpay-storeid" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="input-webhook">{{ entry_webhook }}</label>
|
||||
<div class="col-sm-10">
|
||||
{% if payment_btcpay_webhook.id %}
|
||||
{% set whData = 'ID: ' ~ payment_btcpay_webhook.id ~ ' | SECRET: ' ~ payment_btcpay_webhook.secret ~ ' | URL: ' ~ payment_btcpay_webhook.url %}
|
||||
{% else %}
|
||||
{% set whData = '-- webhook not configured yet --' %}
|
||||
{% endif %}
|
||||
<input readonly="readonly" type="text" name="payment_btcpay_webhook" value="{{ whData }}" placeholder="{{ entry_webhook }}" id="input-webhook" class="form-control" />
|
||||
<div class="help-block">
|
||||
{{ help_webhook }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="col-sm-2 col-form-label" for="input-webhook-delete">{{ entry_webhook_delete }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="checkbox" name="payment_btcpay_webhook_delete" value="1" id="input-webhook-delete" class="form-check mt-2" {% if payment_btcpay_webhook_delete %} checked="checked" {% endif %} {% if not payment_btcpay_webhook.id %}disabled="disabled"{% endif %}/>
|
||||
<div class="help-block mt-1">
|
||||
{{ help_webhook_delete }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="input-modal-mode">{{ entry_modal_mode }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="checkbox" name="payment_btcpay_modal_mode" value="1" id="input-modal-mode" class="form-check mt-2" {% if payment_btcpay_modal_mode %} checked="checked" {% endif %} />
|
||||
<div class="help-block mt-1">
|
||||
{{ help_modal_mode }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="input-new-status">{{ entry_new_status }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select name="payment_btcpay_new_status_id" id="input-new-status" class="form-select">
|
||||
{% for order_status in order_statuses %}
|
||||
{% if order_status.order_status_id == payment_btcpay_new_status_id %}
|
||||
<option value="{{ order_status.order_status_id }}" selected="selected">{{ order_status.name }}</option>
|
||||
{% else %}
|
||||
<option value="{{ order_status.order_status_id }}">{{ order_status.name }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="input-paid-status">{{ entry_paid_status }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select name="payment_btcpay_paid_status_id" id="input-paid-status" class="form-select">
|
||||
{% for order_status in order_statuses %}
|
||||
{% if order_status.order_status_id == payment_btcpay_paid_status_id %}
|
||||
<option value="{{ order_status.order_status_id }}" selected="selected">{{ order_status.name }}</option>
|
||||
{% else %}
|
||||
<option value="{{ order_status.order_status_id }}">{{ order_status.name }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="input-settled-status">{{ entry_settled_status }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select name="payment_btcpay_settled_status_id" id="input-settled-status" class="form-select">
|
||||
{% for order_status in order_statuses %}
|
||||
{% if order_status.order_status_id == payment_btcpay_settled_status_id %}
|
||||
<option value="{{ order_status.order_status_id }}" selected="selected">{{ order_status.name }}</option>
|
||||
{% else %}
|
||||
<option value="{{ order_status.order_status_id }}">{{ order_status.name }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="input-settled-paidover-status">{{ entry_settled_paidover_status }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select name="payment_btcpay_settled_paidover_status_id" id="input-settled-paidover-status" class="form-select">
|
||||
{% for order_status in order_statuses %}
|
||||
{% if order_status.order_status_id == payment_btcpay_settled_paidover_status_id %}
|
||||
<option value="{{ order_status.order_status_id }}" selected="selected">{{ order_status.name }}</option>
|
||||
{% else %}
|
||||
<option value="{{ order_status.order_status_id }}">{{ order_status.name }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="input-invalid-status">{{ entry_invalid_status }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select name="payment_btcpay_invalid_status_id" id="input-invalid-status" class="form-select">
|
||||
{% for order_status in order_statuses %}
|
||||
{% if order_status.order_status_id == payment_btcpay_invalid_status_id %}
|
||||
<option value="{{ order_status.order_status_id }}" selected="selected">{{ order_status.name }}</option>
|
||||
{% else %}
|
||||
<option value="{{ order_status.order_status_id }}">{{ order_status.name }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="input-expired-status">{{ entry_expired_status }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select name="payment_btcpay_expired_status_id" id="input-expired-status" class="form-select">
|
||||
{% for order_status in order_statuses %}
|
||||
{% if order_status.order_status_id == payment_btcpay_expired_status_id %}
|
||||
<option value="{{ order_status.order_status_id }}" selected="selected">{{ order_status.name }}</option>
|
||||
{% else %}
|
||||
<option value="{{ order_status.order_status_id }}">{{ order_status.name }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="input-expired-partialpayment-status">{{ entry_expired_partialpayment_status }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select name="payment_btcpay_expired_partialpayment_status_id" id="input-expired-partialpayment-status" class="form-select">
|
||||
{% for order_status in order_statuses %}
|
||||
{% if order_status.order_status_id == payment_btcpay_expired_partialpayment_status_id %}
|
||||
<option value="{{ order_status.order_status_id }}" selected="selected">{{ order_status.name }}</option>
|
||||
{% else %}
|
||||
<option value="{{ order_status.order_status_id }}">{{ order_status.name }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="input-expired-paidlate-status">{{ entry_expired_paidlate_status }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select name="payment_btcpay_expired_paidlate_status_id" id="input-expired-paidlate-status" class="form-select">
|
||||
{% for order_status in order_statuses %}
|
||||
{% if order_status.order_status_id == payment_btcpay_expired_partialpayment_status_id %}
|
||||
<option value="{{ order_status.order_status_id }}" selected="selected">{{ order_status.name }}</option>
|
||||
{% else %}
|
||||
<option value="{{ order_status.order_status_id }}">{{ order_status.name }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="input-refunded-status">{{ entry_refunded_status }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select name="payment_btcpay_refunded_status_id" id="input-refunded-status" class="form-select">
|
||||
{% for order_status in order_statuses %}
|
||||
{% if order_status.order_status_id == payment_btcpay_refunded_status_id %}
|
||||
<option value="{{ order_status.order_status_id }}" selected="selected">{{ order_status.name }}</option>
|
||||
{% else %}
|
||||
<option value="{{ order_status.order_status_id }}">{{ order_status.name }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="input-total"><span data-toggle="tooltip" title="{{ help_total }}">{{ entry_total }}</span></label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" name="payment_btcpay_total" value="{{ payment_btcpay_total }}" placeholder="{{ entry_total }}" id="input-total" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="input-geo-zone">{{ entry_geo_zone }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select name="payment_btcpay_geo_zone_id" id="input-geo-zone" class="form-select">
|
||||
<option value="0">{{ text_all_zones }}</option>
|
||||
{% for geo_zone in geo_zones %}
|
||||
{% if geo_zone.geo_zone_id == payment_btcpay_geo_zone_id %}
|
||||
<option value="{{ geo_zone.geo_zone_id }}" selected="selected">{{ geo_zone.name }}</option>
|
||||
{% else %}
|
||||
<option value="{{ geo_zone.geo_zone_id }}">{{ geo_zone.name }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="input-sort-order">{{ entry_sort_order }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" name="payment_btcpay_sort_order" value="{{ payment_btcpay_sort_order }}" placeholder="{{ entry_sort_order }}" id="input-sort-order" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="input-debug-mode">{{ entry_debug_mode }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="checkbox" name="payment_btcpay_debug_mode" value="1" id="input-debug-mode" class="form-check" {% if payment_btcpay_debug_mode %} checked="checked" {% endif %} />
|
||||
<div class="help-block mt-1">
|
||||
{{ help_debug_mode }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ footer }}
|
||||
@ -1,118 +1,131 @@
|
||||
<?php
|
||||
|
||||
namespace Opencart\Catalog\Controller\Extension\Btcpay\Payment;
|
||||
use BTCPayServer\Client\Invoice;
|
||||
use BTCPayServer\Client\InvoiceCheckoutOptions;
|
||||
use BTCPayServer\Client\Webhook;
|
||||
use BTCPayServer\Util\PreciseNumber;
|
||||
|
||||
require DIR_SYSTEM . 'library/btcpay/autoload.php';
|
||||
require DIR_SYSTEM . 'library/btcpay/version.php';
|
||||
require_once DIR_EXTENSION . 'btcpay/system/library/btcpay/autoload.php';
|
||||
require_once DIR_EXTENSION . 'btcpay/system/library/btcpay/version.php';
|
||||
|
||||
class ControllerExtensionPaymentBTCPay extends Controller
|
||||
class Btcpay extends \Opencart\System\Engine\Controller
|
||||
{
|
||||
|
||||
public function index()
|
||||
public function index(): string
|
||||
{
|
||||
$this->load->language('extension/payment/btcpay');
|
||||
$this->load->language('extension/btcpay/payment/btcpay');
|
||||
$this->load->model('checkout/order');
|
||||
|
||||
$useModal = $this->config->get('payment_btcpay_modal_mode');
|
||||
|
||||
$data['button_confirm'] = $this->language->get('button_confirm');
|
||||
$data['action'] = $this->url->link(
|
||||
'extension/payment/btcpay/checkout',
|
||||
'extension/btcpay/payment/btcpay|checkout',
|
||||
'',
|
||||
true
|
||||
);
|
||||
|
||||
return $this->load->view('extension/payment/btcpay', $data);
|
||||
if (isset($this->session->data['error_warning'])) {
|
||||
$data['error_warning'] = $this->session->data['error_warning'];
|
||||
unset($this->session->data['error_warning']);
|
||||
} else {
|
||||
$data['error_warning'] = '';
|
||||
}
|
||||
|
||||
if ($useModal) {
|
||||
$host = $this->config->get('payment_btcpay_url');
|
||||
$data['btcpay_host'] = $host;
|
||||
$data['modal_url'] = $host . '/modal/btcpay.js';
|
||||
$data['success_link'] = $this->url->link('checkout/success', '', true);
|
||||
$data['invoice_expired_text'] = $this->language->get('invoice_expired_text');
|
||||
|
||||
return $this->load->view('extension/btcpay/payment/btcpay_modal', $data);
|
||||
} else {
|
||||
// Redirect.
|
||||
return $this->load->view('extension/btcpay/payment/btcpay', $data);
|
||||
}
|
||||
}
|
||||
|
||||
public function checkout()
|
||||
public function checkout(): void
|
||||
{
|
||||
$this->load->model('checkout/order');
|
||||
$this->load->model('extension/payment/btcpay');
|
||||
$this->load->model('extension/btcpay/payment/btcpay');
|
||||
$this->load->language('extension/btcpay/payment/btcpay');
|
||||
|
||||
$debug = $this->config->get('payment_btcpay_debug_mode');
|
||||
$useModal = $this->config->get('payment_btcpay_modal_mode');
|
||||
|
||||
if ($debug) {
|
||||
$this->log->write('Entering checkout() of BTCPay catalog controller.');
|
||||
$this->log->write('Session data:');
|
||||
$this->log->write(print_r($this->session->data, true));
|
||||
}
|
||||
|
||||
$metadata = [];
|
||||
$token = md5(uniqid(rand(), true));
|
||||
|
||||
if (!isset($this->session->data['order_id'])) {
|
||||
$this->log->write('No session data order_id present, aborting.');
|
||||
return;
|
||||
}
|
||||
|
||||
$order_info = $this->model_checkout_order->getOrder(
|
||||
$this->session->data['order_id']
|
||||
);
|
||||
|
||||
// Set included tax amount.
|
||||
//// $metadata['taxIncluded'] = $order->get_cart_tax();
|
||||
|
||||
// POS metadata.
|
||||
////todo: $metadata['posData'] = $this->preparePosMetadata( $order );
|
||||
|
||||
// Checkout options.
|
||||
$checkoutOptions = new InvoiceCheckoutOptions();
|
||||
$redirectUrl = $this->url->link(
|
||||
'extension/payment/btcpay/success',
|
||||
['token' => $token],
|
||||
true
|
||||
);
|
||||
|
||||
$checkoutOptions->setRedirectURL(htmlspecialchars_decode($redirectUrl));
|
||||
if ($debug) {
|
||||
$this->log->write( 'Setting redirect url to: ' . $redirectUrl );
|
||||
if (empty($order_info)) {
|
||||
if ($debug) {
|
||||
$this->log->write('Could not load order passed by session, order id: ' . $this->session->data['order_id']);
|
||||
}
|
||||
$this->session->data['error_warning'] = $this->language->get('session_checkout_order_error');
|
||||
$this->response->redirect(
|
||||
$this->url->link('checkout/checkout', '', true)
|
||||
);
|
||||
}
|
||||
|
||||
// Calculate total and format it properly.
|
||||
$total = number_format(
|
||||
$order_info['total'] * $this->currency->getvalue(
|
||||
$order_info['currency_code']
|
||||
),
|
||||
8,
|
||||
'.',
|
||||
''
|
||||
);
|
||||
$amount = PreciseNumber::parseString(
|
||||
$total
|
||||
); // unlike method signature suggests, it returns string.
|
||||
$invoiceId = '';
|
||||
$checkoutLink = '';
|
||||
|
||||
// API credentials.
|
||||
$apiKey = $this->config->get('payment_btcpay_api_auth_token');
|
||||
$host = $this->config->get('payment_btcpay_url');
|
||||
$storeId = $this->config->get('payment_btcpay_btcpay_storeid');
|
||||
|
||||
// Create the invoice on BTCPay Server.
|
||||
$client = new Invoice($host, $apiKey);
|
||||
try {
|
||||
$invoice = $client->createInvoice(
|
||||
$storeId,
|
||||
$order_info['currency_code'],
|
||||
$amount,
|
||||
$order_info['order_id'],
|
||||
null, // this is null here as we handle it in the metadata.
|
||||
$metadata,
|
||||
$checkoutOptions
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
$this->log->write($e->getMessage());
|
||||
}
|
||||
|
||||
if ($invoice->getData()['id']) {
|
||||
$this->model_extension_payment_btcpay->addOrder([
|
||||
'order_id' => $order_info['order_id'],
|
||||
'token' => $token,
|
||||
'invoice_id' => $invoice->getData(
|
||||
)['id'],
|
||||
]);
|
||||
|
||||
$this->model_checkout_order->addOrderHistory(
|
||||
$order_info['order_id'],
|
||||
$this->config->get('payment_btcpay_order_status_id')
|
||||
);
|
||||
|
||||
$this->response->redirect($invoice->getData()['checkoutLink']);
|
||||
// First, check if we have an existing and not expired wallet and do not create a new one.
|
||||
if ($existingInvoice = $this->orderHasExistingInvoice($order_info)) {
|
||||
$invoiceId = $existingInvoice->getId();
|
||||
$checkoutLink = $existingInvoice->getCheckoutLink();
|
||||
if ($debug) {
|
||||
$this->log->write('Found existing and not yet expired invoice: ' . $invoiceId);
|
||||
}
|
||||
} else {
|
||||
// Create the invoice on BTCPay Server.
|
||||
$token = md5(uniqid(rand(), true));
|
||||
if ($newInvoice = $this->createInvoice($order_info, $token)) {
|
||||
$invoiceId = $newInvoice->getId();
|
||||
$checkoutLink = $newInvoice->getCheckoutLink();
|
||||
|
||||
// Add invoiceId to the btcpay order table.
|
||||
$this->model_extension_btcpay_payment_btcpay->addOrder([
|
||||
'order_id' => $order_info['order_id'],
|
||||
'token' => $token,
|
||||
'invoice_id' => $invoiceId,
|
||||
]);
|
||||
|
||||
$this->model_checkout_order->addHistory(
|
||||
$order_info['order_id'],
|
||||
$this->config->get('payment_btcpay_new_status_id'),
|
||||
'BTCPay invoice id: ' . $newInvoice->getId()
|
||||
);
|
||||
|
||||
/* TODO: wip have BTCPay Server invoice link in customer comments, needs option.
|
||||
// Add user facing comment with a link to BTCPay Server invoice:
|
||||
$this->model_checkout_order->addHistory(
|
||||
$order_info['order_id'],
|
||||
$this->config->get('payment_btcpay_new_status_id'),
|
||||
$this->language->get(
|
||||
'order_payment_link'
|
||||
) . '<a href="' . $newInvoice->getCheckoutLink() . '" target="_blank">' . $newInvoice->getCheckoutLink() . '</a>',
|
||||
true
|
||||
);
|
||||
*/
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($invoiceId)) {
|
||||
$this->log->write(
|
||||
"Order #" . $order_info['order_id'] . " is not valid or something went wrong. Please check BTCPay Server API request logs."
|
||||
);
|
||||
@ -120,27 +133,40 @@ class ControllerExtensionPaymentBTCPay extends Controller
|
||||
$this->url->link('checkout/checkout', '', true)
|
||||
);
|
||||
}
|
||||
|
||||
// Handle invoice in modal or redirect to BTCPay Server.
|
||||
if ($useModal) {
|
||||
// Return JSON data for Javascript to process.
|
||||
$data['invoiceId'] = $invoiceId;
|
||||
$this->response->addHeader('Content-Type: application/json');
|
||||
$this->response->setOutput(json_encode($data));
|
||||
} else {
|
||||
// Redirect to BTCPay Server.
|
||||
$this->response->redirect($checkoutLink);
|
||||
}
|
||||
}
|
||||
|
||||
public function cancel()
|
||||
public function cancel(): void
|
||||
{
|
||||
$this->response->redirect($this->url->link('checkout/cart', ''));
|
||||
}
|
||||
|
||||
public function success()
|
||||
public function success(): void
|
||||
{
|
||||
$this->load->model('checkout/order');
|
||||
$this->load->model('extension/payment/btcpay');
|
||||
$this->load->model('extension/btcpay/payment/btcpay');
|
||||
|
||||
$debug = $this->config->get('payment_btcpay_debug_mode');
|
||||
|
||||
if ($debug) {
|
||||
$this->log->write('Entering success callback / redirect page.');
|
||||
$this->log->write('SESSION data: ');
|
||||
$this->log->write(print_r($this->session->data, true));
|
||||
}
|
||||
|
||||
if ($order_id = $this->session->data['order_id']) {
|
||||
$order = $this->model_extension_payment_btcpay->getOrder(
|
||||
$order_id
|
||||
if (isset($this->session->data['order_id'])) {
|
||||
$order = $this->model_extension_btcpay_payment_btcpay->getOrder(
|
||||
$this->session->data['order_id']
|
||||
);
|
||||
|
||||
// Check if token is present and valid.
|
||||
@ -149,7 +175,7 @@ class ControllerExtensionPaymentBTCPay extends Controller
|
||||
$this->request->get['token']
|
||||
) !== 0) {
|
||||
if ($debug) {
|
||||
$this->log->write('Redirect to success page had no valid token.');
|
||||
$this->log->write('Redirect to home page, had no valid token.');
|
||||
}
|
||||
$this->response->redirect(
|
||||
$this->url->link('common/home', '', true)
|
||||
@ -162,7 +188,7 @@ class ControllerExtensionPaymentBTCPay extends Controller
|
||||
|
||||
} else {
|
||||
if ($debug) {
|
||||
$this->log->write('Redirect to success page valid order id or session expired.');
|
||||
$this->log->write('Redirect to home page, no valid order id or session expired.');
|
||||
}
|
||||
$this->response->redirect(
|
||||
$this->url->link('common/home', '', true)
|
||||
@ -173,7 +199,9 @@ class ControllerExtensionPaymentBTCPay extends Controller
|
||||
public function callback()
|
||||
{
|
||||
$this->load->model('checkout/order');
|
||||
$this->load->model('extension/payment/btcpay');
|
||||
$this->load->model('extension/btcpay/payment/btcpay');
|
||||
|
||||
$json = [];
|
||||
|
||||
$debug = $this->config->get('payment_btcpay_debug_mode');
|
||||
|
||||
@ -202,10 +230,16 @@ class ControllerExtensionPaymentBTCPay extends Controller
|
||||
die($whMessage);
|
||||
}
|
||||
|
||||
$btcpay_order = $this->model_extension_payment_btcpay->getOrderByInvoiceId(
|
||||
$btcpay_order = $this->model_extension_btcpay_payment_btcpay->getOrderByInvoiceId(
|
||||
$data->invoiceId
|
||||
);
|
||||
|
||||
if (empty($btcpay_order)) {
|
||||
$this->log->write('Order not found. Aborting.');
|
||||
http_response_code(200);
|
||||
die();
|
||||
}
|
||||
|
||||
$order_info = $this->model_checkout_order->getOrder(
|
||||
$btcpay_order['order_id']
|
||||
);
|
||||
@ -214,7 +248,7 @@ class ControllerExtensionPaymentBTCPay extends Controller
|
||||
$this->log->write('Order:');
|
||||
$this->log->write($btcpay_order);
|
||||
$this->log->write('Webhook payload: ');
|
||||
$this->log->write($data);
|
||||
$this->log->write(print_r($data, true));
|
||||
}
|
||||
|
||||
if (!empty($order_info) && !empty($btcpay_order)) {
|
||||
@ -236,7 +270,7 @@ class ControllerExtensionPaymentBTCPay extends Controller
|
||||
|
||||
if ($debug) {
|
||||
$this->log->write('Invoice data: ');
|
||||
$this->log->write($invoice);
|
||||
$this->log->write(print_r($invoice, true));
|
||||
}
|
||||
|
||||
$invStatus = $invoice->getStatus();
|
||||
@ -244,7 +278,7 @@ class ControllerExtensionPaymentBTCPay extends Controller
|
||||
|
||||
if ($invoice) {
|
||||
$order_status = NULL;
|
||||
$order_message = 'Event: ' . $data->type;
|
||||
$order_message = 'Event: ' . $data->type . ': ';
|
||||
$notify = false;
|
||||
switch ($data->type) {
|
||||
case "InvoiceReceivedPayment":
|
||||
@ -300,7 +334,7 @@ class ControllerExtensionPaymentBTCPay extends Controller
|
||||
}
|
||||
|
||||
if (!is_null($order_status)) {
|
||||
$this->model_checkout_order->addOrderHistory(
|
||||
$this->model_checkout_order->addHistory(
|
||||
$btcpay_order['order_id'],
|
||||
$this->config->get($order_status),
|
||||
'Payment status update: ' . $order_message,
|
||||
@ -318,16 +352,117 @@ class ControllerExtensionPaymentBTCPay extends Controller
|
||||
}
|
||||
|
||||
$this->response->addHeader('HTTP/1.1 200 OK');
|
||||
$this->response->setOutput(json_encode($json));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check webhook signature to be a valid request.
|
||||
*/
|
||||
public function validWebhookRequest(string $signature, string $requestData): bool {
|
||||
protected function validWebhookRequest(string $signature, string $requestData): bool {
|
||||
if ($whData = $this->config->get('payment_btcpay_webhook')) {
|
||||
return Webhook::isIncomingWebhookRequestValid($requestData, $signature, $whData['secret']);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function createInvoice(array $order_info, string $token): ?\BTCPayServer\Result\Invoice {
|
||||
// API credentials.
|
||||
$apiKey = $this->config->get('payment_btcpay_api_auth_token');
|
||||
$apiHost = $this->config->get('payment_btcpay_url');
|
||||
$apiStoreId = $this->config->get('payment_btcpay_btcpay_storeid');
|
||||
$debug = $this->config->get('payment_btcpay_debug_mode');
|
||||
|
||||
$client = new Invoice($apiHost, $apiKey);
|
||||
|
||||
// Checkout options.
|
||||
$checkoutOptions = new InvoiceCheckoutOptions();
|
||||
$redirectUrl = $this->url->link(
|
||||
'extension/btcpay/payment/btcpay|success',
|
||||
['token' => $token],
|
||||
true
|
||||
);
|
||||
|
||||
$checkoutOptions->setRedirectURL(htmlspecialchars_decode($redirectUrl));
|
||||
if ($debug) {
|
||||
$this->log->write( 'Setting redirect url to: ' . $redirectUrl );
|
||||
}
|
||||
|
||||
// Metadata.
|
||||
$metadata = [];
|
||||
|
||||
$amount = $this->prepareOrderTotal($order_info['total'], $order_info['currency_code']);
|
||||
|
||||
// Create the invoice on BTCPay Server.
|
||||
try {
|
||||
$invoice = $client->createInvoice(
|
||||
$apiStoreId,
|
||||
$order_info['currency_code'],
|
||||
$amount,
|
||||
$order_info['order_id'],
|
||||
null, // this is null here as we handle it in the metadata.
|
||||
$metadata,
|
||||
$checkoutOptions
|
||||
);
|
||||
|
||||
return $invoice;
|
||||
} catch (\Throwable $e) {
|
||||
$this->log->write($e->getMessage());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the order already has an invoice id and it is still not expired.
|
||||
*/
|
||||
protected function orderHasExistingInvoice(array $order_info): ? \BTCPayServer\Result\Invoice {
|
||||
// API credentials.
|
||||
$apiKey = $this->config->get('payment_btcpay_api_auth_token');
|
||||
$apiHost = $this->config->get('payment_btcpay_url');
|
||||
$apiStoreId = $this->config->get('payment_btcpay_btcpay_storeid');
|
||||
$debug = $this->config->get('payment_btcpay_debug_mode');
|
||||
|
||||
$client = new Invoice($apiHost, $apiKey);
|
||||
|
||||
// Calculate order total.
|
||||
$total = $this->prepareOrderTotal($order_info['total'], $order_info['currency_code']);
|
||||
// Round to 2 decimals to avoid mismatch.
|
||||
$totalRounded = round((float) $total->__toString(), 2);
|
||||
|
||||
$btcpay_order = $this->model_extension_btcpay_payment_btcpay->getOrder(
|
||||
$order_info['order_id']
|
||||
);
|
||||
|
||||
if ($debug) {
|
||||
$this->log->write(__FUNCTION__);
|
||||
$this->log->write(print_r($btcpay_order, true));
|
||||
}
|
||||
|
||||
if (!empty($btcpay_order['invoice_id'])) {
|
||||
$existingInvoice = $client->getInvoice($apiStoreId, $btcpay_order['invoice_id']);
|
||||
$invoiceAmount = $existingInvoice->getAmount();
|
||||
|
||||
if ($existingInvoice->isExpired() === false &&
|
||||
$totalRounded === (float) $invoiceAmount->__toString()
|
||||
) {
|
||||
return $existingInvoice;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function prepareOrderTotal($total, $currencyCode): \BTCPayserver\Util\PreciseNumber {
|
||||
// Calculate total and format it properly.
|
||||
$total = number_format(
|
||||
$total * $this->currency->getvalue(
|
||||
$currencyCode
|
||||
),
|
||||
8,
|
||||
'.',
|
||||
''
|
||||
);
|
||||
return PreciseNumber::parseString($total);
|
||||
}
|
||||
|
||||
}
|
||||
9
catalog/language/en-gb/payment/btcpay.php
Normal file
9
catalog/language/en-gb/payment/btcpay.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
$_['text_title'] = 'Bitcoin via BTCPay Server';
|
||||
$_['button_confirm'] = 'Pay with Bitcoin';
|
||||
$_['invoice_expired_text'] = 'The invoice expired. Please try again or choose a different payment method.';
|
||||
$_['invoice_closed_text'] = 'Payment aborted. Please try again or choose a different payment method.';
|
||||
$_['invoice_failed_text'] = 'Payment aborted. Error processing the request. Please contact store owner if the problem persists.';
|
||||
$_['order_payment_link'] = 'You can check your payment status here: ';
|
||||
$_['session_checkout_order_error'] = 'Your cart order id could not be found, please try to log out and in again.';
|
||||
80
catalog/model/payment/btcpay.php
Normal file
80
catalog/model/payment/btcpay.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Opencart\Catalog\Model\Extension\Btcpay\Payment;
|
||||
|
||||
class Btcpay extends \Opencart\System\Engine\Model
|
||||
{
|
||||
|
||||
public function addOrder(array $data): bool
|
||||
{
|
||||
return $this->db->query(
|
||||
"INSERT INTO `" . DB_PREFIX . "btcpay_order` SET `order_id` = '" . (int)$data['order_id'] . "', `token` = '" . $this->db->escape(
|
||||
$data['token']
|
||||
) . "', `invoice_id` = '" . $this->db->escape(
|
||||
$data['invoice_id']
|
||||
) . "'"
|
||||
);
|
||||
}
|
||||
|
||||
public function getOrder(int $order_id): array
|
||||
{
|
||||
$query = $this->db->query(
|
||||
"SELECT * FROM `" . DB_PREFIX . "btcpay_order` WHERE `order_id` = '" . $order_id . "' ORDER BY btcpay_order_id DESC LIMIT 1"
|
||||
);
|
||||
|
||||
return $query->row;
|
||||
}
|
||||
|
||||
public function getOrderByInvoiceId(string $invoice_id): array
|
||||
{
|
||||
$query = $this->db->query(
|
||||
"SELECT * FROM `" . DB_PREFIX . "btcpay_order` WHERE `invoice_id` = '" . $invoice_id . "' LIMIT 1"
|
||||
);
|
||||
|
||||
return $query->row;
|
||||
}
|
||||
|
||||
public function getMethods(array $address = []): array
|
||||
{
|
||||
$this->load->language('extension/btcpay/payment/btcpay');
|
||||
|
||||
$qStr = "SELECT * FROM `" . DB_PREFIX . "zone_to_geo_zone` WHERE `geo_zone_id` = '" . (int)$this->config->get(
|
||||
'payment_btcpay_geo_zone_id'
|
||||
) . "'";
|
||||
|
||||
if (isset($address['country_id'])) {
|
||||
$qStr .= " AND `country_id` = '" . (int)$address['country_id'] ."'";
|
||||
}
|
||||
if (isset($address['zone_id'])) {
|
||||
$qStr .= " AND (`zone_id` = '" . (int)$address['zone_id'] . "' OR `zone_id` = '0')";
|
||||
}
|
||||
|
||||
$query = $this->db->query($qStr);
|
||||
|
||||
if (!$this->config->get('payment_btcpay_geo_zone_id')) {
|
||||
$status = true;
|
||||
} elseif ($query->num_rows) {
|
||||
$status = true;
|
||||
} else {
|
||||
$status = false;
|
||||
}
|
||||
|
||||
$method_data = [];
|
||||
|
||||
if ($status) {
|
||||
$option_data['btcpay'] = [
|
||||
'code' => 'btcpay.btcpay',
|
||||
'name' => $this->language->get('text_title')
|
||||
];
|
||||
|
||||
$method_data = [
|
||||
'code' => 'btcpay',
|
||||
'name' => $this->language->get('text_title'),
|
||||
'option' => $option_data,
|
||||
'sort_order' => $this->config->get('payment_btcpay_sort_order')
|
||||
];
|
||||
}
|
||||
|
||||
return $method_data;
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,9 @@
|
||||
{% if error_warning %}
|
||||
<div class="alert alert-danger">{{ error_warning }}</div>
|
||||
{% endif %}
|
||||
<form action="{{ action }}" method="post">
|
||||
<div class="buttons">
|
||||
<div class="pull-right">
|
||||
<div class="text-end">
|
||||
<input type="submit" value="{{ button_confirm }}" class="btn btn-primary" />
|
||||
</div>
|
||||
</div>
|
||||
57
catalog/view/template/payment/btcpay_modal.twig
Normal file
57
catalog/view/template/payment/btcpay_modal.twig
Normal file
@ -0,0 +1,57 @@
|
||||
<script src="{{ modal_url }}"></script>
|
||||
|
||||
<div class="buttons">
|
||||
<div class="text-end">
|
||||
<button class="btn btn-primary btcpay-modal">{{ button_confirm }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$('button.btcpay-modal').click(function() {
|
||||
$.post("{{ action }}", function (data) {
|
||||
console.log(JSON.stringify(data));
|
||||
if (data.invoiceId !== undefined) {
|
||||
window.btcpay.setApiUrlPrefix('{{ btcpay_host }}');
|
||||
window.btcpay.showInvoice(data.invoiceId);
|
||||
} else {
|
||||
showError('{{ invoice_failed_text }}');
|
||||
}
|
||||
let invoice_paid = false;
|
||||
window.btcpay.onModalReceiveMessage(function (event) {
|
||||
if (isObject(event.data)) {
|
||||
console.log('invoiceId: ' + event.data.invoiceId);
|
||||
console.log('status: ' + event.data.status);
|
||||
if (event.data.status) {
|
||||
switch (event.data.status) {
|
||||
case 'complete':
|
||||
case 'paid':
|
||||
invoice_paid = true;
|
||||
window.location='{{ success_link }}';
|
||||
break;
|
||||
case 'expired':
|
||||
window.btcpay.hideFrame();
|
||||
showError('{{ invoice_expired_text }}');
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else { // handle event.data "loaded" "closed"
|
||||
if (event.data === 'close') {
|
||||
if (invoice_paid === true) {
|
||||
window.location='{{ success_link }}';
|
||||
}
|
||||
showError('{{ invoice_closed_text }}');
|
||||
}
|
||||
}
|
||||
});
|
||||
const isObject = obj => {
|
||||
return Object.prototype.toString.call(obj) === '[object Object]'
|
||||
}
|
||||
}).fail(function() {
|
||||
showError('{{ invoice_failed_text }}');
|
||||
});
|
||||
const showError = err => {
|
||||
const errFail = '<div class="alert alert-danger alert-dismissible"><i class="fa-solid fa-circle-exclamation"></i> ' + err + '<button type="button" class="btn-close" data-bs-dismiss="alert"></button></div>';
|
||||
$('#alert').prepend(errFail);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "btcpayserver/opencart",
|
||||
"description": "BTCPay Server plugin for OpenCart 3",
|
||||
"description": "BTCPay Server plugin for OpenCart 4",
|
||||
"type": "opencart-extension",
|
||||
"require": {
|
||||
"btcpayserver/btcpayserver-greenfield-php": "^1.3"
|
||||
@ -14,6 +14,6 @@
|
||||
],
|
||||
"minimum-stability": "stable",
|
||||
"config": {
|
||||
"vendor-dir": "upload/system/library/btcpay"
|
||||
"vendor-dir": "system/library/btcpay"
|
||||
}
|
||||
}
|
||||
|
||||
14
composer.lock
generated
14
composer.lock
generated
@ -4,20 +4,20 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "0411678fc60196f00a3748362d083e80",
|
||||
"content-hash": "bdf8d088a05991246e9aa2f8ec35d506",
|
||||
"packages": [
|
||||
{
|
||||
"name": "btcpayserver/btcpayserver-greenfield-php",
|
||||
"version": "v1.3.3",
|
||||
"version": "v1.3.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/btcpayserver/btcpayserver-greenfield-php.git",
|
||||
"reference": "aff6ab92151431c2faa63c72805aa60736b0deea"
|
||||
"reference": "cc9ab93a8ecda8a8158b717f11ff37dfa5ab8962"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/btcpayserver/btcpayserver-greenfield-php/zipball/aff6ab92151431c2faa63c72805aa60736b0deea",
|
||||
"reference": "aff6ab92151431c2faa63c72805aa60736b0deea",
|
||||
"url": "https://api.github.com/repos/btcpayserver/btcpayserver-greenfield-php/zipball/cc9ab93a8ecda8a8158b717f11ff37dfa5ab8962",
|
||||
"reference": "cc9ab93a8ecda8a8158b717f11ff37dfa5ab8962",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -55,9 +55,9 @@
|
||||
"description": "BTCPay Server Greenfield API PHP client library.",
|
||||
"support": {
|
||||
"issues": "https://github.com/btcpayserver/btcpayserver-greenfield-php/issues",
|
||||
"source": "https://github.com/btcpayserver/btcpayserver-greenfield-php/tree/v1.3.3"
|
||||
"source": "https://github.com/btcpayserver/btcpayserver-greenfield-php/tree/v1.3.6"
|
||||
},
|
||||
"time": "2022-05-21T15:11:12+00:00"
|
||||
"time": "2022-09-12T21:30:25+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
|
||||
7
install.json
Normal file
7
install.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "BTCPay Server payment gateway",
|
||||
"version": "4.2.2",
|
||||
"license": "MIT",
|
||||
"author": "BTCPay Server",
|
||||
"link": "https://btcpayserver.org"
|
||||
}
|
||||
25
system/library/btcpay/autoload.php
Normal file
25
system/library/btcpay/autoload.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
// autoload.php @generated by Composer
|
||||
|
||||
if (PHP_VERSION_ID < 50600) {
|
||||
if (!headers_sent()) {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
}
|
||||
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
|
||||
if (!ini_get('display_errors')) {
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
fwrite(STDERR, $err);
|
||||
} elseif (!headers_sent()) {
|
||||
echo $err;
|
||||
}
|
||||
}
|
||||
trigger_error(
|
||||
$err,
|
||||
E_USER_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInit0630a220d429326d17ccb4047a5ba4f3::getLoader();
|
||||
@ -40,6 +40,7 @@ class PullPayments
|
||||
$paymentCurrency = 'BTC';
|
||||
$paymentPeriod = null;
|
||||
$boltExpiration = 1;
|
||||
$autoApproveClaims = false;
|
||||
$startsAt = null;
|
||||
$expiresAt = null;
|
||||
$paymentMethods = ['BTC'];
|
||||
@ -54,6 +55,7 @@ class PullPayments
|
||||
$paymentCurrency,
|
||||
$paymentPeriod,
|
||||
$boltExpiration,
|
||||
$autoApproveClaims,
|
||||
$startsAt,
|
||||
$expiresAt,
|
||||
$paymentMethods
|
||||
@ -109,13 +111,29 @@ class PullPayments
|
||||
}
|
||||
}
|
||||
|
||||
public function approvePayout()
|
||||
{
|
||||
$payoutId ='';
|
||||
try {
|
||||
$client = new PullPayment($this->host, $this->apiKey);
|
||||
var_dump($client->approvePayout(
|
||||
$this->storeId,
|
||||
$payoutId,
|
||||
0,
|
||||
null
|
||||
));
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public function getPullPayment()
|
||||
{
|
||||
$pullPaymentId = '';
|
||||
|
||||
try {
|
||||
$client = new PullPayment($this->host, $this->apiKey);
|
||||
var_dump($client->markPayoutAsPaid(
|
||||
var_dump($client->getPullPayment(
|
||||
$this->storeId,
|
||||
$pullPaymentId
|
||||
));
|
||||
@ -131,7 +149,7 @@ class PullPayments
|
||||
|
||||
try {
|
||||
$client = new PullPayment($this->host, $this->apiKey);
|
||||
var_dump($client->markPayoutAsPaid(
|
||||
var_dump($client->getPayouts(
|
||||
$pullPaymentId,
|
||||
$includeCancelled
|
||||
));
|
||||
@ -149,7 +167,7 @@ class PullPayments
|
||||
|
||||
try {
|
||||
$client = new PullPayment($this->host, $this->apiKey);
|
||||
var_dump($client->markPayoutAsPaid(
|
||||
var_dump($client->createPayout(
|
||||
$pullPaymentId,
|
||||
$destination,
|
||||
$amount,
|
||||
@ -167,7 +185,7 @@ class PullPayments
|
||||
|
||||
try {
|
||||
$client = new PullPayment($this->host, $this->apiKey);
|
||||
var_dump($client->markPayoutAsPaid(
|
||||
var_dump($client->getPayout(
|
||||
$pullPaymentId,
|
||||
$payoutId
|
||||
));
|
||||
@ -183,6 +201,7 @@ $pp = new PullPayments();
|
||||
//$pp->archivePullPayment();
|
||||
//$pp->cancelPayout();
|
||||
//$pp->markPayoutAsPaid();
|
||||
//$pp->approvePayout();
|
||||
//$pp->getPullPayment();
|
||||
//$pp->getPayouts();
|
||||
//$pp->createPayout();
|
||||
@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// Include autoload file.
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
// Import Invoice client class.
|
||||
use BTCPayServer\Client\Invoice;
|
||||
use BTCPayServer\Client\Webhook;
|
||||
|
||||
class WebhookExample
|
||||
{
|
||||
public $apiKey;
|
||||
public $host;
|
||||
public $storeId;
|
||||
public $secret;
|
||||
public $webhookId;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// Fill in with your BTCPay Server data.
|
||||
$this->apiKey = '';
|
||||
$this->host = ''; // e.g. https://your.btcpay-server.tld
|
||||
$this->storeId = '';
|
||||
$this->secret = ''; // webhook secret as shown in the webhook UI / returned by createWebhook()
|
||||
$this->webhookId = ''; // only needed for the updateWebhook() example.
|
||||
}
|
||||
|
||||
public function processWebhook()
|
||||
{
|
||||
$myfile = fopen("BTCPay.log", 'ab');
|
||||
$raw_post_data = file_get_contents('php://input');
|
||||
|
||||
$date = date('m/d/Y h:i:s a');
|
||||
|
||||
if (false === $raw_post_data) {
|
||||
fwrite(
|
||||
$myfile,
|
||||
$date . " : Error. Could not read from the php://input stream or invalid BTCPayServer payload received.\n"
|
||||
);
|
||||
fclose($myfile);
|
||||
throw new \RuntimeException(
|
||||
'Could not read from the php://input stream or invalid BTCPayServer payload received.'
|
||||
);
|
||||
}
|
||||
|
||||
$payload = json_decode($raw_post_data, false, 512, JSON_THROW_ON_ERROR);
|
||||
|
||||
if (empty($payload)) {
|
||||
fwrite(
|
||||
$myfile,
|
||||
$date . " : Error. Could not decode the JSON payload from BTCPay.\n"
|
||||
);
|
||||
fclose($myfile);
|
||||
throw new \RuntimeException('Could not decode the JSON payload from BTCPay.');
|
||||
}
|
||||
|
||||
// verify hmac256
|
||||
$headers = getallheaders();
|
||||
$sig = $headers['Btcpay-Sig'];
|
||||
|
||||
$webhookClient = new Webhook($this->host, $this->apiKey);
|
||||
|
||||
if ($webhookClient->isIncomingWebhookRequestValid($raw_post_data, $sig, $this->secret)) {
|
||||
fwrite(
|
||||
$myfile,
|
||||
$date . " : Error. Invalid Signature detected! \n was: " . $sig . " should be: " . hash_hmac(
|
||||
'sha256',
|
||||
$raw_post_data,
|
||||
$this->secret
|
||||
) . "\n"
|
||||
);
|
||||
fclose($myfile);
|
||||
throw new \RuntimeException(
|
||||
'Invalid BTCPayServer payment notification message received - signature did not match.'
|
||||
);
|
||||
}
|
||||
|
||||
if (true === empty($payload->invoiceId)) {
|
||||
fwrite(
|
||||
$myfile,
|
||||
$date . " : Error. Invalid BTCPayServer payment notification message received - did not receive invoice ID.\n"
|
||||
);
|
||||
fclose($myfile);
|
||||
throw new \RuntimeException(
|
||||
'Invalid BTCPayServer payment notification message received - did not receive invoice ID.'
|
||||
);
|
||||
}
|
||||
|
||||
// Load an existing invoice with the provided invoiceId.
|
||||
// Most of the time this is not needed as you can listen to specific webhook events
|
||||
// See: https://docs.btcpayserver.org/API/Greenfield/v1/#tag/Webhooks/paths/InvoiceCreated/post
|
||||
try {
|
||||
$client = new Invoice($this->host, $this->apiKey);
|
||||
$invoice = $client->getInvoice($this->storeId, $payload->invoiceId);
|
||||
} catch (\Throwable $e) {
|
||||
fwrite($myfile, "Error: " . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// optional: check whether your webhook is of the desired type
|
||||
if ($payload->type !== "InvoiceSettled") {
|
||||
throw new \RuntimeException(
|
||||
'Invalid payload message type. Only InvoiceSettled is supported, check the configuration of the webhook.'
|
||||
);
|
||||
}
|
||||
|
||||
$invoicePrice = $invoice->getData()['amount'];
|
||||
$buyerEmail = $invoice->getData()['metadata']['buyerEmail'];
|
||||
|
||||
fwrite(
|
||||
$myfile,
|
||||
$date . " : Payload received for BtcPay invoice " . $payload->invoiceId . " Type: " . $payload->type . " Price: " . $invoicePrice . " E-Mail: " . $buyerEmail . "\n"
|
||||
);
|
||||
fwrite($myfile, "Raw payload: " . $raw_post_data . "\n");
|
||||
|
||||
// your own processing code goes here!
|
||||
|
||||
echo 'OK';
|
||||
}
|
||||
|
||||
public function createWebhook()
|
||||
{
|
||||
$url = 'https://createdurl.test.example.com/webhook';
|
||||
$specificEvents = [
|
||||
'InvoiceExpired',
|
||||
'InvoiceSettled',
|
||||
'InvoiceInvalid'
|
||||
];
|
||||
|
||||
try {
|
||||
$client = new \BTCPayServer\Client\Webhook($this->host, $this->apiKey);
|
||||
var_dump($client->createWebhook($this->storeId, $url, $specificEvents, null));
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public function updateWebhook()
|
||||
{
|
||||
$url = 'https://updatedurl.test.example.com/webhook';
|
||||
$specificEvents = [
|
||||
'InvoiceReceivedPayment',
|
||||
'InvoicePaymentSettled',
|
||||
'InvoiceProcessing',
|
||||
'InvoiceExpired',
|
||||
'InvoiceSettled',
|
||||
'InvoiceInvalid'
|
||||
];
|
||||
|
||||
try {
|
||||
$client = new \BTCPayServer\Client\Webhook($this->host, $this->apiKey);
|
||||
var_dump($client->updateWebhook($this->storeId, $url, $this->webhookId, $specificEvents));
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public function getWebhook()
|
||||
{
|
||||
try {
|
||||
$client = new \BTCPayServer\Client\Webhook($this->host, $this->apiKey);
|
||||
var_dump($client->getWebhook($this->storeId, $this->webhookId));
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$wh = new WebhookExample();
|
||||
//$wh->processWebhook();
|
||||
//$wh->createWebhook();
|
||||
//$wh->getWebhook();
|
||||
//$wh->updateWebhook();
|
||||
@ -43,6 +43,7 @@ class PullPayment extends AbstractClient
|
||||
string $currency,
|
||||
?int $period,
|
||||
?int $BOLT11Expiration,
|
||||
?bool $autoApproveClaims = false,
|
||||
?int $startsAt,
|
||||
?int $expiresAt,
|
||||
array $paymentMethods
|
||||
@ -60,6 +61,7 @@ class PullPayment extends AbstractClient
|
||||
'currency' => $currency,
|
||||
'period' => $period,
|
||||
'BOLT11Expiration' => $BOLT11Expiration,
|
||||
'autoApproveClaims' => $autoApproveClaims,
|
||||
'startsAt' => $startsAt,
|
||||
'expiresAt' => $expiresAt,
|
||||
'paymentMethods' => $paymentMethods
|
||||
@ -156,7 +158,7 @@ class PullPayment extends AbstractClient
|
||||
): bool {
|
||||
$url = $this->getApiUrl() . 'stores/' .
|
||||
urlencode($storeId) . '/' . 'payouts/' .
|
||||
urlencode($payoutId);
|
||||
urlencode($payoutId) . '/mark-paid';
|
||||
|
||||
$headers = $this->getRequestHeaders();
|
||||
$method = 'POST';
|
||||
@ -118,11 +118,17 @@ class Webhook extends AbstractClient
|
||||
}
|
||||
}
|
||||
|
||||
public function createWebhook(string $storeId, string $url, ?array $specificEvents, ?string $secret): \BTCPayServer\Result\WebhookCreated
|
||||
{
|
||||
public function createWebhook(
|
||||
string $storeId,
|
||||
string $url,
|
||||
?array $specificEvents,
|
||||
?string $secret,
|
||||
bool $enabled = true,
|
||||
bool $automaticRedelivery = true
|
||||
): \BTCPayServer\Result\WebhookCreated {
|
||||
$data = [
|
||||
'enabled' => true,
|
||||
'automaticRedelivery' => true,
|
||||
'enabled' => $enabled,
|
||||
'automaticRedelivery' => $automaticRedelivery,
|
||||
'url' => $url
|
||||
];
|
||||
|
||||
@ -158,6 +164,62 @@ class Webhook extends AbstractClient
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing webhook.
|
||||
*
|
||||
* Important: due to a bug in BTCPay Server versions <= 1.6.3.0 you need
|
||||
* to pass the $secret explicitly as it would overwrite your existing secret
|
||||
* otherwise. On newer versions BTCPay Server >= 1.6.4.0, if you do NOT set
|
||||
* a secret it won't change it and everything will continue to work.
|
||||
*
|
||||
* @see https://github.com/btcpayserver/btcpayserver/issues/4010
|
||||
*
|
||||
* @return \BTCPayServer\Result\Webhook
|
||||
* @throws \JsonException
|
||||
*/
|
||||
public function updateWebhook(
|
||||
string $storeId,
|
||||
string $url,
|
||||
string $webhookId,
|
||||
?array $specificEvents,
|
||||
bool $enabled = true,
|
||||
bool $automaticRedelivery = true,
|
||||
?string $secret = null
|
||||
): \BTCPayServer\Result\Webhook {
|
||||
$data = [
|
||||
'enabled' => $enabled,
|
||||
'automaticRedelivery' => $automaticRedelivery,
|
||||
'url' => $url,
|
||||
'secret' => $secret
|
||||
];
|
||||
|
||||
// Specific events or all.
|
||||
if ($specificEvents === null) {
|
||||
$data['authorizedEvents'] = [
|
||||
'everything' => true
|
||||
];
|
||||
} elseif (count($specificEvents) === 0) {
|
||||
throw new \InvalidArgumentException('Argument $specificEvents should be NULL or contains at least 1 item.');
|
||||
} else {
|
||||
$data['authorizedEvents'] = [
|
||||
'everything' => false,
|
||||
'specificEvents' => $specificEvents
|
||||
];
|
||||
}
|
||||
|
||||
$url = $this->getApiUrl() . 'stores/' . urlencode($storeId) . '/webhooks/' . urlencode($webhookId);
|
||||
$headers = $this->getRequestHeaders();
|
||||
$method = 'PUT';
|
||||
$response = $this->getHttpClient()->request($method, $url, $headers, json_encode($data, JSON_THROW_ON_ERROR));
|
||||
|
||||
if ($response->getStatus() === 200) {
|
||||
$data = json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR);
|
||||
return new \BTCPayServer\Result\Webhook($data);
|
||||
} else {
|
||||
throw $this->getExceptionByStatusCode($method, $url, $response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the request your received from a webhook is authentic and can be trusted.
|
||||
* @param string $requestBody Most likely you will use `$requestBody = file_get_contents('php://input');`
|
||||
@ -19,24 +19,24 @@ abstract class AbstractResult implements \ArrayAccess
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function offsetExists($offset)
|
||||
public function offsetExists($offset): bool
|
||||
{
|
||||
$data = $this->getData();
|
||||
return array_key_exists($offset, $data);
|
||||
}
|
||||
|
||||
public function offsetGet($offset)
|
||||
public function offsetGet($offset): mixed
|
||||
{
|
||||
$data = $this->getData();
|
||||
return $data[$offset] ?? null;
|
||||
}
|
||||
|
||||
public function offsetSet($offset, $value)
|
||||
public function offsetSet($offset, $value): void
|
||||
{
|
||||
throw new \RuntimeException('You should not change the data in a result.');
|
||||
}
|
||||
|
||||
public function offsetUnset($offset)
|
||||
public function offsetUnset($offset): void
|
||||
{
|
||||
throw new \RuntimeException('You should not change the data in a result.');
|
||||
}
|
||||
@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace BTCPayServer\Result;
|
||||
|
||||
use BTCPayServer\Util\PreciseNumber;
|
||||
|
||||
class Invoice extends AbstractResult
|
||||
{
|
||||
public const STATUS_NEW = 'New';
|
||||
@ -24,6 +26,51 @@ class Invoice extends AbstractResult
|
||||
|
||||
public const ADDITIONAL_STATUS_PAID_LATE = 'PaidLate';
|
||||
|
||||
public function getId(): string
|
||||
{
|
||||
return $this->getData()['id'];
|
||||
}
|
||||
|
||||
public function getAmount(): PreciseNumber
|
||||
{
|
||||
return PreciseNumber::parseString($this->getData()['amount']);
|
||||
}
|
||||
|
||||
public function getCurrency(): string
|
||||
{
|
||||
return $this->getData()['currency'];
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->getData()['type'];
|
||||
}
|
||||
|
||||
public function getCheckoutLink(): string
|
||||
{
|
||||
return $this->getData()['checkoutLink'];
|
||||
}
|
||||
|
||||
public function getCreatedTime(): int
|
||||
{
|
||||
return $this->getData()['createdTime'];
|
||||
}
|
||||
|
||||
public function getExpirationTime(): int
|
||||
{
|
||||
return $this->getData()['expirationTime'];
|
||||
}
|
||||
|
||||
public function getMonitoringTime(): int
|
||||
{
|
||||
return $this->getData()['monitoringTime'];
|
||||
}
|
||||
|
||||
public function isArchived(): bool
|
||||
{
|
||||
return $this->getData()['archived'];
|
||||
}
|
||||
|
||||
public function isPaid(): bool
|
||||
{
|
||||
$data = $this->getData();
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user