You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
288 lines
12 KiB
288 lines
12 KiB
<?php |
|
require_once(__DIR__ . "/../globals.php"); |
|
|
|
use OpenEMR\Core\Header; |
|
|
|
$total = $_GET['total'] ?? null; |
|
?> |
|
|
|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="utf-8" /> |
|
<title><?php echo xlt("POS Payments") ?></title> |
|
<meta name="description" content="In-person payment on Stripe" /> |
|
<meta name="viewport" content="width=device-width, initial-scale=1" /> |
|
<?php Header::setupHeader(['opener']); ?> |
|
<script src="https://js.stripe.com/terminal/v1/"></script> |
|
<script> |
|
let amount = <?php echo js_escape(str_replace('.', '', $total)); ?>; |
|
// run anonymous function to get invoice data for metadata. |
|
const encDates = (() => { |
|
let i = 0, c; |
|
let invDates = ''; |
|
opener.$('#table_display tbody tr').each(function () { |
|
if (this.className == 'table-active') { |
|
return false; |
|
} |
|
if(i > 4) { |
|
return false; // breaks on max 5 encounters |
|
} |
|
invDates += 'item' + ++i + ': '; |
|
c = 0; |
|
$(this).find('td').each(function() { |
|
if (++c < 3) { |
|
invDates += this.innerText + ' '; |
|
} |
|
}); |
|
}); |
|
return invDates; |
|
})(); |
|
|
|
const terminal = StripeTerminal.create({ |
|
onFetchConnectionToken: fetchConnectionToken, |
|
onUnexpectedReaderDisconnect: unexpectedDisconnect, |
|
}); |
|
|
|
function unexpectedDisconnect() { |
|
alert("Disconnected from reader"); |
|
console.log("Disconnected from reader"); |
|
} |
|
|
|
function fetchConnectionToken() { |
|
// The SDK manages the ConnectionToken's lifecycle. |
|
return fetch('./front_payment_cc.php?mode=terminal_token', {method: "POST"}).then(function (response) { |
|
return response.json(); |
|
}).then(function (data) { |
|
return data.secret; |
|
}); |
|
} |
|
|
|
let discoveredReaders; |
|
let customerId; |
|
// Handler for a "Discover readers" button |
|
function discoverReaderHandler() { |
|
const config = {simulated: false}; |
|
terminal.discoverReaders(config).then(function (discoverResult) { |
|
if (discoverResult.error) { |
|
alert(discoverResult.error.message); |
|
console.log('Failed to discover: ', discoverResult.error); |
|
} else if (discoverResult.discoveredReaders.length === 0) { |
|
alert(xl('No available readers.')); |
|
} else { |
|
discoveredReaders = discoverResult.discoveredReaders; |
|
log('Terminal Discover Reader', discoveredReaders); |
|
connectReaderHandler(discoveredReaders); |
|
} |
|
}); |
|
} |
|
|
|
// Handler for a "Connect Reader" button |
|
function connectReaderHandler(discoveredReaders) { |
|
// Just select the first reader here. |
|
if (!discoveredReaders) { |
|
alert(xl("Error No selected Readers")); |
|
return false; |
|
} |
|
const selectedReader = discoveredReaders[0]; |
|
terminal.connectReader(selectedReader).then(function (connectResult) { |
|
if (connectResult.error) { |
|
alert(connectResult.error.message); |
|
console.log('Failed to connect: ', connectResult.error); |
|
} else { |
|
document.getElementById("collect-button").classList.remove("d-none"); |
|
console.log('Connected to reader: ', connectResult.reader.label); |
|
log('Connect to Reader', connectResult) |
|
} |
|
}); |
|
} |
|
|
|
function fetchPaymentIntentClientSecret(amount) { |
|
const bodyContent = JSON.stringify({ |
|
amount: amount, |
|
encs: encDates |
|
}); |
|
return fetch('./front_payment_cc.php?mode=terminal_create', { |
|
method: "POST", |
|
headers: { |
|
'Content-Type': 'application/json' |
|
}, |
|
body: bodyContent |
|
}).then(function (response) { |
|
return response.json(); |
|
}).then(function (data) { |
|
return data.client_secret; |
|
}); |
|
} |
|
|
|
let paymentIntentId; |
|
let isChargePending = false; |
|
|
|
function collectPayment(amount) { |
|
try { |
|
fetchPaymentIntentClientSecret(amount).then(function (client_secret) { |
|
terminal.collectPaymentMethod(client_secret).then(function (result) { |
|
if (result.error) { |
|
alert(result.error.message); |
|
} else { |
|
log('Collect Payment Method', result.paymentIntent); |
|
terminal.processPayment(result.paymentIntent).then(function (result) { |
|
if (result.error) { |
|
console.log(result.error); |
|
alert(result.error.message); |
|
} else if (result.paymentIntent) { |
|
paymentIntentId = result.paymentIntent.id; |
|
log('Process Payment', result.paymentIntent); |
|
isChargePending = true; |
|
document.getElementById("collect-button").classList.add("d-none"); |
|
document.getElementById("capture-button").classList.remove("d-none"); |
|
document.getElementById("refund-button").classList.remove("d-none"); |
|
} |
|
}); |
|
} |
|
}); |
|
}); |
|
} catch (e) { |
|
alert(e.message); |
|
} |
|
} |
|
|
|
function capture(paymentIntentId) { |
|
return fetch('./front_payment_cc.php?mode=terminal_capture', { |
|
method: "POST", |
|
headers: { |
|
'Content-Type': 'application/json' |
|
}, |
|
body: JSON.stringify({"id": paymentIntentId}) |
|
}).then(function (response) { |
|
return response.json(); |
|
}).then(function (data) { |
|
if (data.error) { |
|
log('Capture Payment Error', data.error); |
|
console.log(data.error); |
|
alert(data.error); |
|
return false; |
|
} |
|
opener.document.getElementById("check_number").value = data.id; |
|
opener.$("[name='form_save']").click(); |
|
dlgclose(); |
|
}); |
|
} |
|
|
|
function cancel(paymentIntentId) { |
|
return fetch('./front_payment_cc.php?mode=cancel_intent', { |
|
method: "POST", |
|
headers: { |
|
'Content-Type': 'application/json' |
|
}, |
|
body: JSON.stringify({"id": paymentIntentId}) |
|
}).then(function (response) { |
|
return response.json(); |
|
}).then(function (data) { |
|
if (data.error) { |
|
log('Cancel Payment Error', data.error); |
|
console.log(data.error); |
|
alert(data.error); |
|
return false; |
|
} |
|
isChargePending = false; |
|
document.getElementById("refund-button").classList.add("d-none"); |
|
document.getElementById("collect-button").classList.remove("d-none"); |
|
document.getElementById("capture-button").classList.add("d-none"); |
|
log('Cancel Payment', data.status); |
|
}); |
|
} |
|
|
|
$(function () { |
|
const collectButton = document.getElementById('collect-button'); |
|
collectButton.addEventListener('click', async (event) => { |
|
collectPayment(amount); |
|
}); |
|
|
|
const captureButton = document.getElementById('capture-button'); |
|
captureButton.addEventListener('click', async (event) => { |
|
capture(paymentIntentId); |
|
}); |
|
|
|
const cancelIntentButton = document.getElementById('refund-button'); |
|
cancelIntentButton.addEventListener('click', async (event) => { |
|
cancel(paymentIntentId); |
|
}); |
|
|
|
const cancelButton = parent.document.getElementById('closeBtn'); |
|
cancelButton.addEventListener('click', async (event) => { |
|
if (isChargePending) { |
|
if (confirm(xl("There is a charge transaction that has not been captured." + "\n" + xl("Are you sure?")))) { |
|
dlgclose(); |
|
} |
|
return false; |
|
} |
|
dlgclose(); |
|
}); |
|
// get/init reader |
|
discoverReaderHandler(); |
|
}); |
|
|
|
function log(method, message) { |
|
let logs = document.getElementById("logs"); |
|
let title = document.createElement("div"); |
|
let log = document.createElement("div"); |
|
let lineCol = document.createElement("div"); |
|
let logCol = document.createElement("div"); |
|
title.classList.add('row'); |
|
title.classList.add('log-title'); |
|
title.textContent = method; |
|
log.classList.add('row'); |
|
log.classList.add('log'); |
|
let hr = document.createElement("hr"); |
|
let pre = document.createElement("pre"); |
|
let code = document.createElement("code"); |
|
code.textContent = formatJson(JSON.stringify(message, undefined, 2)); |
|
pre.append(code); |
|
log.append(pre); |
|
logs.prepend(hr); |
|
logs.prepend(log); |
|
logs.prepend(title); |
|
} |
|
|
|
function stringLengthOfInt(number) { |
|
return number.toString().length; |
|
} |
|
|
|
function padSpaces(lineNumber, fixedWidth) { |
|
// Always indent by 2 and then maybe more, based on the width of the line |
|
// number. |
|
return " ".repeat(2 + fixedWidth - stringLengthOfInt(lineNumber)); |
|
} |
|
|
|
function formatJson(message) { |
|
let lines = message.split('\n'); |
|
let json = ""; |
|
let lineNumberFixedWidth = stringLengthOfInt(lines.length); |
|
for (let i = 1; i <= lines.length; i += 1) { |
|
line = i + padSpaces(i, lineNumberFixedWidth) + lines[i - 1]; |
|
json = json + line + '\n'; |
|
} |
|
return json |
|
} |
|
</script> |
|
</head> |
|
<body> |
|
<div class="container-fluid "> |
|
<div class="row"> |
|
<div class="col-sm-6 offset-sm-3"> |
|
<h4><span class="m-1"><?php echo xlt("Paying Amount") ?></span><i>$</i><span><?php echo text($total); ?></span></h4> |
|
</div> |
|
</div> |
|
<div class="row m-1"> |
|
<button id="collect-button" class="btn btn-primary btn-transmit m-1 d-none"><?php echo xlt("Collect Payment")?></button> |
|
<button id="capture-button" class="btn btn-primary btn-transmit m-1 d-none"><?php echo xlt("Post Payment")?></button> |
|
<button id="refund-button" class="btn btn-primary btn-transmit m-1 d-none"><?php echo xlt("Cancel Payment")?></button> |
|
</div> |
|
<hr /> |
|
<div class="row ml-2"><h5><?php echo xlt("Transaction Progress") ?></h5></div> |
|
<div class="col-sm-12 p-2 bg-secondary" id="logs"><i class="fa fa-spinner fa-spin fa-2x"></i></div> |
|
|
|
</div> |
|
</body> |
|
</html>
|
|
|