Skip to content

Commit 3cbe0f7

Browse files
first commit
0 parents  commit 3cbe0f7

25 files changed

+1316
-0
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Lịch sử thay đổi
2+
3+
Tất cả thay đổi của `fob-payfs` sẽ được mô tả trong file này
4+
5+
## 1.0.0 - 2025-10-23
6+
7+
- Bản phát hành đầu tiên
8+
- Tích hợp PayFS webhook với xác thực 2 lớp (API Key + HMAC-SHA256)
9+
- Tạo mã QR code tự động qua VietQR API
10+
- Hỗ trợ 30+ ngân hàng Việt Nam
11+
- Tự động quy đổi tiền tệ sang VND
12+
- Kiểm tra trạng thái thanh toán real-time
13+
- Bảo vệ chống xử lý trùng lặp

LICENSE

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
MIT License
2+
3+
Copyright (c) Friends Of Botble
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
22+
23+
---
24+
25+
Bản quyền (c) Friends Of Botble
26+
27+
Giấy phép này được cấp miễn phí cho bất kỳ ai nhận được bản sao của tài liệu này
28+
và các tệp liên quan ("Phần mềm") nhận được để sử dụng, sao chép, sửa đổi,
29+
hợp nhất, xuất bản, phân phối, cấp phép, và/hoặc bán sao chép của Phần mềm,
30+
và cho phép người nhận của Phần mềm thực hiện điều này, dưới điều kiện sau:
31+
32+
Thông báo bản quyền trên và thông cáo này phải được bao gồm trong tất cả
33+
các bản sao hoặc phần quan trọng của Phần mềm.
34+
35+
PHẦN MỀM ĐƯỢC CUNG CẤP "NHƯ VẬY", KHÔNG CÓ BẤT KỲ LOẠI BẢO ĐẢM NÀO, CHỈ RÕ RÀNG
36+
HAY ẨN Ý, BAO GỒM NHƯNG KHÔNG GIỚI HẠN, CÁC BẢO ĐẢM VỀ CHẤT LƯỢNG KINH DOANH,
37+
PHÙ HỢP CHO MỘT MỤC ĐÍCH NHẤT ĐỊNH, VÀ VI PHẠM QUYỀN SỞ HỮU HOẶC SỰ PHẠM TỘI.
38+
TRONG KHÔNG CÓ TRƯỜNG HỢP NÀO TÁC GIẢ HOẶC CHỦ SỞ HỮU BẢN QUYỀN CHỊU TRÁCH
39+
NHIỆM ĐỐI VỚI BẤT KỲ YÊU CẦU, THIỆT HẠI HOẶC PHÁT VÀO, BẤT KỲ TRÁCH NHIỆM PHÁP LÝ,
40+
ĐẶC BIỆT HOẶC TRÁCH NHIỆM NÀO KHÁC, XUẤT PHÁT TỪ HOẶC TRONG CỐT,
41+
HOẶC LIÊN QUAN ĐẾN PHẦN MỀM HOẶC VIỆC SỬ DỤNG HOẶC CÁC GIAO DIỆN VỚI PHẦN MỀM.

README.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# PayFS
2+
3+
Plugin này cho phép bạn tích hợp PayFS để tự động xác thực thanh toán qua phương thức chuyển khoản ngân hàng.
4+
5+
## Yêu cầu tối thiểu
6+
7+
- Botble core 7.0.5 hoặc cao hơn.
8+
- **QUAN TRỌNG 1: Plugin hỗ trợ tự động quy đổi từ các loại tiền tệ khác sang VND. Tuy nhiên, khuyến nghị sử dụng tiền Việt (Đồng, VND) làm mặc định khi sử dụng phương thức thanh toán PayFS để đảm bảo chính xác nhất.**
9+
- **QUAN TRỌNG 2: Yêu cầu bắt buộc cần phải có tiền tố mã đơn hàng để sử dụng được tính năng này. Vui lòng điền đúng theo mô tả bên dưới của biểu mẫu khi bạn thiết lập phương thức.**
10+
11+
## Cài đặt
12+
13+
### Cài đặt thông qua bảng quản trị
14+
15+
Vào **Bảng quản trị (Admin)** và chọn **Plugins**. Bấm vào nút "Thêm mới (Add new)", tìm kiếm plugin **PayFS** và sau đó bấm vào "Cài đặt (Install)".
16+
17+
### Cài đặt thủ công
18+
19+
1. Bạn có thể tải về các bản phát hành tại đây hoặc trên [Botble Marketplace](https://marketplace.botble.com/products/friendsofbotble/fob-payfs).
20+
2. Giải nén file nén vào thư mục `platform/plugins`.
21+
3. Vào **Bảng quản trị (Admin)**, chọn **Plugins**, và bấm vào nút **Kích hoạt (Activate)**.
22+
23+
## Cách sử dụng
24+
25+
1. Vào **Bảng quản trị (Admin)**, chọn **Thanh toán (Payments)**, và bấm vào **Phương thức thanh toán (Payment Methods)**.
26+
2. Kích hoạt **PayFS** bằng cách điền đầy đủ thông tin vào biểu mẫu:
27+
- Chọn ngân hàng
28+
- Nhập số tài khoản
29+
- Nhập tên chủ tài khoản
30+
- Nhập tiền tố mã thanh toán (ví dụ: SDH)
31+
- Nhập API Key từ PayFS dashboard
32+
- Nhập Webhook Secret (tùy chọn, khuyến nghị sử dụng để bảo mật tốt hơn)
33+
3. Sao chép "Webhook URL" hiển thị trong biểu mẫu.
34+
4. Truy cập vào tài khoản [PayFS Dashboard](https://payfs.vn) của bạn.
35+
5. Vào phần **Webhook Settings** và tạo webhook mới:
36+
- Dán URL webhook đã sao chép
37+
- Cấu hình xác thực webhook:
38+
- **Xác thực cơ bản**: Sử dụng API Key (X-Client-API-Key)
39+
- **Xác thực nâng cao** (khuyến nghị): Thêm Webhook Secret để xác thực HMAC-SHA256
40+
6. Lưu cấu hình và tiến hành sử dụng như bình thường.
41+
7. Khách hàng sẽ nhận được mã QR code và thông tin chuyển khoản khi thanh toán.
42+
8. Hệ thống tự động xác nhận thanh toán khi nhận được webhook từ PayFS.
43+
44+
## Tính năng
45+
46+
- ✅ Tích hợp PayFS webhook với xác thực 2 lớp (API Key + HMAC signature)
47+
- ✅ Tạo mã QR code tự động cho chuyển khoản ngân hàng (VietQR)
48+
- ✅ Hỗ trợ 30+ ngân hàng Việt Nam
49+
- ✅ Tự động quy đổi tiền tệ sang VND
50+
- ✅ Kiểm tra trạng thái thanh toán real-time
51+
- ✅ Bảo vệ chống xử lý trùng lặp (idempotency)
52+
- ✅ Ghi log chi tiết để debug
53+
54+
## Lịch sử thay đổi
55+
56+
Vui lòng xem [LỊCH SỬ THAY ĐỔI](CHANGELOG.md) để biết chi tiết.
57+
58+
## Bảo mật
59+
60+
Nếu bạn phát hiện bất kỳ vấn đề liên quan đến bảo mật nào, vui lòng gửi email tới friendsofbotble@gmail.com thay vì sử dụng issues.
61+
62+
## Credits
63+
64+
- [Friends Of Botble](https://github.com/FriendsOfBotble)
65+
- [All Contributors](../../contributors)
66+
67+
## Giấy phép
68+
69+
MIT License (MIT). Vui lòng xem chi tiết trong phần [thông tin giấy phép](LICENSE).

helpers/constants.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
if (! defined('PAYFS_PAYMENT_METHOD_NAME')) {
4+
define('PAYFS_PAYMENT_METHOD_NAME', 'payfs');
5+
}

plugin.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"id": "friendsofbotble/fob-payfs",
3+
"name": "PayFS for Botble",
4+
"namespace": "FriendsOfBotble\\PayFS\\",
5+
"provider": "FriendsOfBotble\\PayFS\\Providers\\PayFSServiceProvider",
6+
"author": "Friends Of Botble",
7+
"url": "https://friendsofbotble.com",
8+
"version": "1.0.0",
9+
"description": "Tích hợp PayFS vào Botble CMS",
10+
"minimum_core_version": "7.0.5"
11+
}

public/images/loading.gif

70.5 KB
Loading

public/images/payfs.png

212 KB
Loading
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
<style>
2+
.payfs.fob-container {
3+
margin-top: 2rem;
4+
}
5+
6+
.payfs .fob-qr-code {
7+
text-align: center;
8+
margin-bottom: 40px;
9+
}
10+
11+
.payfs .fob-qr-code img {
12+
width: 250px;
13+
height: auto;
14+
margin: 0;
15+
padding: 0;
16+
}
17+
18+
.payfs .fob-qr-code figcaption {
19+
margin-top: 10px;
20+
font-size: 14px;
21+
color: #666;
22+
}
23+
24+
.payfs .fob-qr-intro {
25+
margin-bottom: 10px;
26+
font-size: 16px;
27+
}
28+
29+
.payfs .transaction-status-done {
30+
background-color: var(--bs-tertiary-bg);
31+
border: none;
32+
color: var(--primary-color);
33+
}
34+
35+
.payfs .transaction-status-done .icon {
36+
width: 40px;
37+
height: 40px;
38+
}
39+
</style>
40+
41+
<div id="fob-payfs-bank" class="payfs fob-container">
42+
@if ($payment->status != \Botble\Payment\Enums\PaymentStatusEnum::COMPLETED)
43+
<div id="payfs-bank-info">
44+
<div class="fob-qr-intro">
45+
Cách 1: Mở app ngân hàng/ Ví để <strong>quét mã QR</strong>
46+
</div>
47+
<div class="fob-qr-code">
48+
<figure>
49+
<img src="{{ $imageUrl }}" alt="QR Code">
50+
</figure>
51+
</div>
52+
53+
<div class="fob-qr-intro">
54+
Cách 2: Chuyển khoản <strong>thủ công</strong> theo thông tin
55+
</div>
56+
<div class="fob-qr-information">
57+
<table class="table table-hover table-striped">
58+
<tr>
59+
<td>Tên Ngân Hàng</td>
60+
<td>
61+
<strong>{{ $bank }}</strong>
62+
</td>
63+
<td></td>
64+
</tr>
65+
<tr>
66+
<td>Chủ Tài Khoản</td>
67+
<td>
68+
<strong>{{ $bankAccountHolder }}</strong>
69+
</td>
70+
<td></td>
71+
</tr>
72+
<tr>
73+
<td>Số Tài Khoản</td>
74+
<td>
75+
<strong>{{ $bankAccountNumber }}</strong>
76+
</td>
77+
<td class="text-end" style="width: 80px;">
78+
<a href="javascript:void(0);" rel="nooper" class="ms-2" type="button" data-clipboard="{{ $bankAccountNumber }}" data-bb-toggle="copy">
79+
<x-core::icon name="ti ti-clipboard" />
80+
</a>
81+
</td>
82+
</tr>
83+
<tr>
84+
<td>Nội Dung Chuyển Khoản</td>
85+
<td>
86+
<strong>{{ $chargeId }}</strong>
87+
</td>
88+
<td class="text-end" style="width: 80px;">
89+
<a href="javascript:void(0);" rel="nooper" class="ms-2" type="button" data-clipboard="{{ $chargeId }}" data-bb-toggle="copy">
90+
<x-core::icon name="ti ti-clipboard" />
91+
</a>
92+
</td>
93+
</tr>
94+
95+
<tr>
96+
<td>Số Tiền Giao Dịch</td>
97+
<td>
98+
<strong>{{ $formattedOrderAmount = number_format($orderAmount, 0, ',', '.') . '' }}</strong>
99+
@if (isset($originalCurrency) && $originalCurrency !== 'VND')
100+
<br>
101+
<small class="text-muted">(≈ {{ number_format($originalAmount, 2) }} {{ $originalCurrency }})</small>
102+
@endif
103+
</td>
104+
<td class="text-end" style="width: 80px;">
105+
<a href="javascript:void(0);" rel="nooper" class="ms-2" type="button" data-clipboard="{{ $orderAmount }}" data-bb-toggle="copy">
106+
<x-core::icon name="ti ti-clipboard" />
107+
</a>
108+
</td>
109+
</tr>
110+
</table>
111+
112+
<div class="alert alert-warning">
113+
<p>Vui lòng giữ nguyên nội dung chuyển khoản <strong class="text-danger">{{ $chargeId }}</strong> và nhập đúng số tiền <strong class="text-danger">{{ $formattedOrderAmount }}</strong> để được xác nhận thanh toán trực tuyến.</p>
114+
@if (isset($originalCurrency) && $originalCurrency !== 'VND')
115+
<p class="mt-2 mb-0"><em>Số tiền đã được quy đổi từ {{ $originalCurrency }} sang VND theo tỷ giá hiện tại.</em></p>
116+
@endif
117+
</div>
118+
119+
<div class="transaction-status text-center" data-bb-toggle="payfs-transaction-status" data-url="{{ route('payfs.transactions.check') }}" data-charge-id="{{ $chargeId }}">
120+
Trạng thái chờ thanh toán <img src="{{ url('vendor/core/plugins/fob-payfs/images/loading.gif') }}" width="20" height="20" alt="Loading">
121+
</div>
122+
</div>
123+
</div>
124+
@endif
125+
126+
<div @style(['display: none' => $payment->status != \Botble\Payment\Enums\PaymentStatusEnum::COMPLETED])
127+
id="payfs-transaction-status-done">
128+
<div class="transaction-status-done card text-center pb-3 pt-2">
129+
<div class="p-4">
130+
<div class="mb-2">
131+
<x-core::icon name="ti ti-circle-check"/>
132+
</div>
133+
<h4>Thanh toán thành công</h4>
134+
</div>
135+
</div>
136+
</div>
137+
</div>
138+
139+
<script>
140+
document.addEventListener('DOMContentLoaded', function () {
141+
const copyButtons = document.querySelectorAll('[data-bb-toggle="copy"]');
142+
143+
copyButtons.forEach((button) => {
144+
button.addEventListener('click', function (event) {
145+
event.preventDefault();
146+
event.stopPropagation();
147+
const textToCopy = this.getAttribute('data-clipboard');
148+
fobCopyToClipboard(textToCopy);
149+
})
150+
})
151+
152+
})
153+
154+
let interval = null
155+
156+
$(document).ready(function() {
157+
const paymentStatus = $('[data-bb-toggle="payfs-transaction-status"]')
158+
159+
if (paymentStatus.length) {
160+
interval = setInterval(() => fetchPaymentStatus(paymentStatus), 3000)
161+
}
162+
})
163+
164+
function fetchPaymentStatus(elm) {
165+
$.ajax({
166+
url: elm.data('url'),
167+
method: 'POST',
168+
data: {
169+
charge_id: elm.data('charge-id')
170+
},
171+
success: ({ data }) => {
172+
if (data.status.value === 'completed') {
173+
$('#payfs-transaction-status-done').show()
174+
$('#payfs-bank-info').remove()
175+
176+
let paymentStatusElement = $(document).find('span[data-bb-target="ecommerce-order-payment-status"]');
177+
178+
if (paymentStatusElement.length && data.status_html) {
179+
paymentStatusElement.html(data.status_html);
180+
}
181+
182+
clearInterval(interval)
183+
}
184+
}
185+
})
186+
}
187+
188+
async function fobCopyToClipboard(textToCopy) {
189+
if (navigator.clipboard && window.isSecureContext) {
190+
await navigator.clipboard.writeText(textToCopy);
191+
} else {
192+
fobUnsecuredCopyToClipboard(textToCopy);
193+
}
194+
195+
MainCheckout.showSuccess('Sao chép thành công!');
196+
}
197+
198+
function fobUnsecuredCopyToClipboard(textToCopy) {
199+
const textArea = document.createElement('textarea');
200+
textArea.value = textToCopy;
201+
textArea.style.position = 'absolute';
202+
textArea.style.left = '-999999px';
203+
document.body.append(textArea);
204+
textArea.focus();
205+
textArea.select();
206+
207+
try {
208+
document.execCommand('copy');
209+
} catch (error) {
210+
console.error('Unable to copy to clipboard', error);
211+
}
212+
213+
document.body.removeChild(textArea);
214+
}
215+
</script>

resources/views/detail.blade.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<h4 class="my-4">Thông tin dữ liệu nhận từ PayFS (Webhook)</h4>
2+
3+
<pre><code>{{ BaseHelper::jsonEncodePrettify($payment->metadata) }}</code></pre>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@if (setting('payment_payfs_status') == 1)
2+
<x-plugins-payment::payment-method
3+
:name="PAYFS_PAYMENT_METHOD_NAME"
4+
paymentName="PayFS"
5+
/>
6+
@endif

0 commit comments

Comments
 (0)