@ayushsolanki29/payment-gateway 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitattributes +2 -0
- package/README.md +81 -0
- package/images/flow.svg +2 -0
- package/images/qr.gif +0 -0
- package/images/secured.png +0 -0
- package/images/secured.svg +1 -0
- package/images/ss.png +0 -0
- package/index.js +2 -0
- package/index.php +444 -0
- package/package.json +22 -0
- package/payment-api.php +160 -0
- package/payment-gateway.js +257 -0
- package/styles.css +320 -0
- package/thank-you.html +61 -0
package/payment-api.php
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
header('Content-Type: application/json');
|
|
3
|
+
session_start();
|
|
4
|
+
|
|
5
|
+
// Handle different API actions
|
|
6
|
+
$action = $_GET['action'] ?? '';
|
|
7
|
+
$input = json_decode(file_get_contents('php://input'), true) ?? [];
|
|
8
|
+
$response = ['success' => false, 'message' => 'Invalid action'];
|
|
9
|
+
|
|
10
|
+
// Merchant configuration
|
|
11
|
+
const MERCHANT_UPI_ID = 'merchant@upi';
|
|
12
|
+
const MERCHANT_NAME = 'Example Merchant';
|
|
13
|
+
const MIN_AMOUNT = 100; // ₹1.00 (100 paise)
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
switch ($action) {
|
|
17
|
+
case 'init_transaction':
|
|
18
|
+
$response = handleInitTransaction($input);
|
|
19
|
+
break;
|
|
20
|
+
|
|
21
|
+
case 'verify_contact':
|
|
22
|
+
$response = handleVerifyContact($input);
|
|
23
|
+
break;
|
|
24
|
+
|
|
25
|
+
case 'process_payment':
|
|
26
|
+
$response = handleProcessPayment($input);
|
|
27
|
+
break;
|
|
28
|
+
|
|
29
|
+
case 'check_status':
|
|
30
|
+
$response = handleCheckStatus($input);
|
|
31
|
+
break;
|
|
32
|
+
|
|
33
|
+
default:
|
|
34
|
+
$response = ['success' => false, 'message' => 'Invalid action specified'];
|
|
35
|
+
}
|
|
36
|
+
} catch (Exception $e) {
|
|
37
|
+
$response = ['success' => false, 'message' => $e->getMessage()];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
echo json_encode($response);
|
|
41
|
+
exit;
|
|
42
|
+
|
|
43
|
+
// API Handlers
|
|
44
|
+
function handleInitTransaction($data) {
|
|
45
|
+
$amount = intval($data['amount'] ?? 0);
|
|
46
|
+
|
|
47
|
+
// Validate amount (must be at least ₹1)
|
|
48
|
+
if ($amount < MIN_AMOUNT) {
|
|
49
|
+
return [
|
|
50
|
+
'success' => false,
|
|
51
|
+
'message' => 'Amount must be at least ₹1.00 (100 paise)'
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Generate transaction ID
|
|
56
|
+
$transactionId = 'TXN' . uniqid() . strtoupper(substr(md5(microtime()), 0, 6));
|
|
57
|
+
|
|
58
|
+
// Store transaction in session (replace with database in production)
|
|
59
|
+
$_SESSION['transactions'][$transactionId] = [
|
|
60
|
+
'amount' => $amount,
|
|
61
|
+
'status' => 'initiated',
|
|
62
|
+
'created_at' => time()
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
return ['success' => true, 'transactionId' => $transactionId];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function handleVerifyContact($data) {
|
|
69
|
+
$upiId = trim($data['upiId'] ?? '');
|
|
70
|
+
$phone = trim($data['phone'] ?? '');
|
|
71
|
+
$transactionId = $data['transactionId'] ?? '';
|
|
72
|
+
|
|
73
|
+
// Validate required fields
|
|
74
|
+
if (empty($upiId) || empty($phone) || empty($transactionId)) {
|
|
75
|
+
return ['success' => false, 'message' => 'Missing required fields'];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Validate UPI ID format
|
|
79
|
+
if (!preg_match('/^[\w.-]+@[\w.-]+$/', $upiId)) {
|
|
80
|
+
return ['success' => false, 'message' => 'Invalid UPI ID format'];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Validate phone number
|
|
84
|
+
if (!preg_match('/^[0-9]{10}$/', $phone)) {
|
|
85
|
+
return ['success' => false, 'message' => 'Phone number must be 10 digits'];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Update transaction in session
|
|
89
|
+
if (isset($_SESSION['transactions'][$transactionId])) {
|
|
90
|
+
$_SESSION['transactions'][$transactionId]['upiId'] = $upiId;
|
|
91
|
+
$_SESSION['transactions'][$transactionId]['phone'] = $phone;
|
|
92
|
+
$_SESSION['transactions'][$transactionId]['status'] = 'verified';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Set cookies for future use
|
|
96
|
+
setcookie('upi_id', $upiId, time() + (86400 * 30), "/");
|
|
97
|
+
setcookie('phone_number', $phone, time() + (86400 * 30), "/");
|
|
98
|
+
|
|
99
|
+
return ['success' => true, 'upiId' => $upiId, 'phone' => $phone];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function handleProcessPayment($data) {
|
|
103
|
+
$transactionId = $data['transactionId'] ?? '';
|
|
104
|
+
$amount = intval($data['amount'] ?? 0);
|
|
105
|
+
$upiId = $data['upiId'] ?? '';
|
|
106
|
+
$phone = $data['phone'] ?? '';
|
|
107
|
+
|
|
108
|
+
if (empty($transactionId) || !isset($_SESSION['transactions'][$transactionId])) {
|
|
109
|
+
return ['success' => false, 'message' => 'Invalid transaction'];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Generate UPI payment URL
|
|
113
|
+
$paymentUrl = "upi://pay?pa=" . urlencode(MERCHANT_UPI_ID) .
|
|
114
|
+
"&pn=" . urlencode(MERCHANT_NAME) .
|
|
115
|
+
"&am=" . $amount .
|
|
116
|
+
"&tn=" . $transactionId .
|
|
117
|
+
"&cu=INR";
|
|
118
|
+
|
|
119
|
+
// Update transaction status
|
|
120
|
+
$_SESSION['transactions'][$transactionId]['status'] = 'processing';
|
|
121
|
+
$_SESSION['transactions'][$transactionId]['payment_url'] = $paymentUrl;
|
|
122
|
+
|
|
123
|
+
return [
|
|
124
|
+
'success' => true,
|
|
125
|
+
'upiUrl' => $paymentUrl,
|
|
126
|
+
'merchantUpiId' => MERCHANT_UPI_ID,
|
|
127
|
+
'redirectUrl' => 'thank-you.html'
|
|
128
|
+
];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function handleCheckStatus($data) {
|
|
132
|
+
$transactionId = $data['transactionId'] ?? '';
|
|
133
|
+
|
|
134
|
+
if (empty($transactionId) || !isset($_SESSION['transactions'][$transactionId])) {
|
|
135
|
+
return ['success' => false, 'message' => 'Invalid transaction'];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Simulate payment processing (2-5 seconds)
|
|
139
|
+
$txn = $_SESSION['transactions'][$transactionId];
|
|
140
|
+
$elapsed = time() - $txn['created_at'];
|
|
141
|
+
|
|
142
|
+
if ($elapsed < 2) {
|
|
143
|
+
return ['success' => true, 'paymentSuccess' => false];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Simulate 70% success rate
|
|
147
|
+
$paymentSuccess = (rand(1, 10) <= 7);
|
|
148
|
+
|
|
149
|
+
if ($paymentSuccess) {
|
|
150
|
+
$_SESSION['transactions'][$transactionId]['status'] = 'completed';
|
|
151
|
+
return [
|
|
152
|
+
'success' => true,
|
|
153
|
+
'paymentSuccess' => true,
|
|
154
|
+
'redirectUrl' => 'thank-you.html'
|
|
155
|
+
];
|
|
156
|
+
} else {
|
|
157
|
+
$_SESSION['transactions'][$transactionId]['status'] = 'failed';
|
|
158
|
+
return ['success' => true, 'paymentSuccess' => false];
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
class UPIPaymentGateway {
|
|
2
|
+
constructor(options = {}) {
|
|
3
|
+
this.config = {
|
|
4
|
+
paymentTimeout: 180,
|
|
5
|
+
apiBase: "payment-api.php",
|
|
6
|
+
defaults: {
|
|
7
|
+
amount: 1000, // ₹10.00 (1000 paise)
|
|
8
|
+
currency: "INR",
|
|
9
|
+
merchantName: "Example Merchant",
|
|
10
|
+
merchantUpiId: "merchant@upi",
|
|
11
|
+
themeColor: "#005bf2",
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
this.options = $.extend({}, this.config.defaults, options);
|
|
16
|
+
this.transactionId = null;
|
|
17
|
+
this.timer = null;
|
|
18
|
+
|
|
19
|
+
this.cacheElements();
|
|
20
|
+
this.bindEvents();
|
|
21
|
+
}
|
|
22
|
+
cacheElements() {
|
|
23
|
+
this.$els = {
|
|
24
|
+
modal: $("#paymentModal"),
|
|
25
|
+
trigger: $("#paymentTrigger"),
|
|
26
|
+
closeBtn: $(".close-modal"),
|
|
27
|
+
amount: $("#paymentAmount"),
|
|
28
|
+
upiInput: $("#upiId"),
|
|
29
|
+
phoneInput: $("#phoneNumber"),
|
|
30
|
+
proceedBtn: $("#proceedToPay"),
|
|
31
|
+
qrCode: $("#upiQrCode"),
|
|
32
|
+
merchantUpi: $("#merchantUpiId"),
|
|
33
|
+
amountFinal: $("#displayAmount"),
|
|
34
|
+
timer: $("#paymentTimer"),
|
|
35
|
+
upiBtn: $("#openUpiApp"),
|
|
36
|
+
messageBox: $("#messageBox"),
|
|
37
|
+
messageText: $("#messageText"),
|
|
38
|
+
contactStep: $("#contactStep"),
|
|
39
|
+
paymentStep: $("#paymentStep"),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
bindEvents() {
|
|
44
|
+
this.$els.trigger.on("click", () => this.openModal());
|
|
45
|
+
this.$els.closeBtn.on("click", () => this.closeModal());
|
|
46
|
+
this.$els.proceedBtn.on("click", () => this.verifyAndProceed());
|
|
47
|
+
this.$els.upiBtn.on("click", () => this.openUpiApp());
|
|
48
|
+
|
|
49
|
+
// Tab switching
|
|
50
|
+
$(".option-tab").on("click", (e) => {
|
|
51
|
+
const tab = $(e.currentTarget);
|
|
52
|
+
$(".option-tab, .option-content").removeClass("active");
|
|
53
|
+
tab.addClass("active");
|
|
54
|
+
$(`#${tab.data("tab")}`).addClass("active");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Copy UPI ID
|
|
58
|
+
$(".copy-btn").on("click", (e) => {
|
|
59
|
+
const target = $(e.currentTarget).data("target");
|
|
60
|
+
const text = $(`#${target}`).text();
|
|
61
|
+
navigator.clipboard.writeText(text);
|
|
62
|
+
this.showMessage("UPI ID copied to clipboard!", "success");
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
openModal() {
|
|
67
|
+
this.$els.amount.text(`₹${this.options.amount}`);
|
|
68
|
+
this.$els.modal.css("display", "flex");
|
|
69
|
+
$("body").css("overflow", "hidden");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
closeModal() {
|
|
73
|
+
this.$els.modal.hide();
|
|
74
|
+
$("body").css("overflow", "");
|
|
75
|
+
this.resetFlow();
|
|
76
|
+
clearInterval(this.timer);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
resetFlow() {
|
|
80
|
+
this.$els.contactStep.addClass("active");
|
|
81
|
+
this.$els.paymentStep.removeClass("active");
|
|
82
|
+
this.$els.proceedBtn
|
|
83
|
+
.prop("disabled", false)
|
|
84
|
+
.html(
|
|
85
|
+
'<span>Proceed to Payment</span><i class="fas fa-arrow-right"></i>'
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async verifyAndProceed() {
|
|
90
|
+
const upiId = this.$els.upiInput.val().trim();
|
|
91
|
+
const phone = this.$els.phoneInput.val().trim();
|
|
92
|
+
|
|
93
|
+
if (!this.validateInputs(upiId, phone)) return;
|
|
94
|
+
|
|
95
|
+
this.loading(true);
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
// 1. Initialize transaction
|
|
99
|
+
const init = await this.apiCall("init_transaction", {
|
|
100
|
+
amount: this.options.amount,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
if (!init.success) throw new Error(init.message || "Transaction failed");
|
|
104
|
+
this.transactionId = init.transactionId;
|
|
105
|
+
|
|
106
|
+
// 2. Verify contact details
|
|
107
|
+
const verify = await this.apiCall("verify_contact", {
|
|
108
|
+
upiId,
|
|
109
|
+
phone,
|
|
110
|
+
transactionId: this.transactionId,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
if (!verify.success)
|
|
114
|
+
throw new Error(verify.message || "Verification failed");
|
|
115
|
+
|
|
116
|
+
// 3. Process payment
|
|
117
|
+
const payment = await this.apiCall("process_payment", {
|
|
118
|
+
transactionId: this.transactionId,
|
|
119
|
+
amount: this.options.amount,
|
|
120
|
+
upiId,
|
|
121
|
+
phone,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
if (!payment.success)
|
|
125
|
+
throw new Error(payment.message || "Payment failed");
|
|
126
|
+
|
|
127
|
+
this.showPaymentStep(payment.upiUrl, payment.merchantUpiId);
|
|
128
|
+
} catch (err) {
|
|
129
|
+
this.showMessage(err.message, "error");
|
|
130
|
+
this.loading(false);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
validateInputs(upiId, phone) {
|
|
135
|
+
if (!upiId || !phone) {
|
|
136
|
+
this.showMessage("Please enter both UPI ID and Phone Number", "error");
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
if (!/^[\w.-]+@[\w.-]+$/.test(upiId)) {
|
|
140
|
+
this.showMessage("Invalid UPI ID format", "error");
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
if (!/^[0-9]{10}$/.test(phone)) {
|
|
144
|
+
this.showMessage("Phone number must be 10 digits", "error");
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
showPaymentStep(upiUrl, merchantUpiId) {
|
|
151
|
+
this.$els.contactStep.removeClass("active");
|
|
152
|
+
this.$els.paymentStep.addClass("active");
|
|
153
|
+
|
|
154
|
+
this.$els.qrCode.attr(
|
|
155
|
+
"src",
|
|
156
|
+
`https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=${encodeURIComponent(
|
|
157
|
+
upiUrl
|
|
158
|
+
)}`
|
|
159
|
+
);
|
|
160
|
+
this.$els.merchantUpi.text(merchantUpiId || this.options.merchantUpiId);
|
|
161
|
+
this.$els.amountFinal.text(`₹${this.options.amount}`);
|
|
162
|
+
|
|
163
|
+
this.startTimer();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
startTimer() {
|
|
167
|
+
let seconds = this.config.paymentTimeout;
|
|
168
|
+
this.$els.timer.text(seconds);
|
|
169
|
+
|
|
170
|
+
this.timer = setInterval(() => {
|
|
171
|
+
seconds--;
|
|
172
|
+
this.$els.timer.text(seconds);
|
|
173
|
+
if (seconds <= 0) {
|
|
174
|
+
clearInterval(this.timer);
|
|
175
|
+
this.checkStatus();
|
|
176
|
+
}
|
|
177
|
+
}, 1000);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async checkStatus() {
|
|
181
|
+
try {
|
|
182
|
+
const res = await this.apiCall("check_status", {
|
|
183
|
+
transactionId: this.transactionId,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
if (res.paymentSuccess) {
|
|
187
|
+
this.showMessage("Payment successful!", "success");
|
|
188
|
+
setTimeout(() => {
|
|
189
|
+
window.location.href = res.redirectUrl || "thank-you.html";
|
|
190
|
+
}, 2000);
|
|
191
|
+
} else {
|
|
192
|
+
this.showMessage("Payment not completed. Please try again.", "error");
|
|
193
|
+
this.resetFlow();
|
|
194
|
+
}
|
|
195
|
+
} catch (err) {
|
|
196
|
+
this.showMessage("Error verifying payment", "error");
|
|
197
|
+
this.resetFlow();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
openUpiApp() {
|
|
202
|
+
const url = `upi://pay?pa=${this.options.merchantUpiId}&pn=${this.options.merchantName}&am=${this.options.amount}&tn=${this.transactionId}&cu=INR`;
|
|
203
|
+
window.location.href = url;
|
|
204
|
+
setTimeout(() => {
|
|
205
|
+
this.showMessage(
|
|
206
|
+
"If UPI app didn't open, please copy the UPI ID",
|
|
207
|
+
"warning"
|
|
208
|
+
);
|
|
209
|
+
}, 1000);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
loading(show) {
|
|
213
|
+
this.$els.proceedBtn
|
|
214
|
+
.prop("disabled", show)
|
|
215
|
+
.html(
|
|
216
|
+
show
|
|
217
|
+
? '<i class="fas fa-spinner fa-spin"></i> Processing...'
|
|
218
|
+
: '<span>Proceed to Payment</span><i class="fas fa-arrow-right"></i>'
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
showMessage(msg, type) {
|
|
223
|
+
this.$els.messageText.text(msg);
|
|
224
|
+
this.$els.messageBox
|
|
225
|
+
.removeClass("error success show")
|
|
226
|
+
.addClass(`${type} show`);
|
|
227
|
+
|
|
228
|
+
setTimeout(() => {
|
|
229
|
+
this.$els.messageBox.removeClass("show");
|
|
230
|
+
}, 5000);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async apiCall(action, data) {
|
|
234
|
+
try {
|
|
235
|
+
const res = await $.ajax({
|
|
236
|
+
url: `${this.config.apiBase}?action=${action}`,
|
|
237
|
+
method: "POST",
|
|
238
|
+
dataType: "json",
|
|
239
|
+
data: JSON.stringify(data),
|
|
240
|
+
contentType: "application/json",
|
|
241
|
+
});
|
|
242
|
+
return res;
|
|
243
|
+
} catch (err) {
|
|
244
|
+
console.error("API Error:", err);
|
|
245
|
+
throw new Error("Network error. Please try again.");
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Initialize when DOM is ready
|
|
251
|
+
$(document).ready(() => {
|
|
252
|
+
const paymentGateway = new UPIPaymentGateway({
|
|
253
|
+
amount: 1000, // ₹10.00 (1000 paise)
|
|
254
|
+
merchantName: "Example Merchant",
|
|
255
|
+
merchantUpiId: "merchant@upi",
|
|
256
|
+
});
|
|
257
|
+
});
|