2 require_once(__DIR__
. "/../globals.php");
4 use OpenEMR\Core\Header
;
6 $total = $_GET['total'] ??
null;
12 <meta charset
="utf-8" />
13 <title
><?php
echo xlt("POS Payments") ?
></title
>
14 <meta name
="description" content
="In-person payment on Stripe" />
15 <meta name
="viewport" content
="width=device-width, initial-scale=1" />
16 <?php Header
::setupHeader(['opener']); ?
>
17 <script src
="https://js.stripe.com/terminal/v1/"></script
>
19 let amount
= <?php
echo js_escape(str_replace('.', '', $total)); ?
>;
20 // run anonymous function to get invoice data for metadata.
21 const encDates
= (() => {
24 opener
.$
('#table_display tbody tr').each(function () {
25 if (this
.className
== 'table-active') {
29 return false; // breaks on max 5 encounters
31 invDates +
= 'item' + ++i +
': ';
33 $
(this
).find('td').each(function() {
35 invDates +
= this
.innerText +
' ';
42 const terminal
= StripeTerminal
.create({
43 onFetchConnectionToken
: fetchConnectionToken
,
44 onUnexpectedReaderDisconnect
: unexpectedDisconnect
,
47 function unexpectedDisconnect() {
48 alert("Disconnected from reader");
49 console
.log("Disconnected from reader");
52 function fetchConnectionToken() {
53 // The SDK manages the ConnectionToken's lifecycle.
54 return fetch('./front_payment_cc.php?mode=terminal_token', {method
: "POST"}).then(function (response
) {
55 return response
.json();
56 }).then(function (data
) {
61 let discoveredReaders
;
63 // Handler for a "Discover readers" button
64 function discoverReaderHandler() {
65 const config
= {simulated
: false};
66 terminal
.discoverReaders(config
).then(function (discoverResult
) {
67 if (discoverResult
.error
) {
68 alert(discoverResult
.error
.message
);
69 console
.log('Failed to discover: ', discoverResult
.error
);
70 } else if (discoverResult
.discoveredReaders
.length
=== 0) {
71 alert(xl('No available readers.'));
73 discoveredReaders
= discoverResult
.discoveredReaders
;
74 log('Terminal Discover Reader', discoveredReaders
);
75 connectReaderHandler(discoveredReaders
);
80 // Handler for a "Connect Reader" button
81 function connectReaderHandler(discoveredReaders
) {
82 // Just select the first reader here.
83 if (!discoveredReaders
) {
84 alert(xl("Error No selected Readers"));
87 const selectedReader
= discoveredReaders
[0];
88 terminal
.connectReader(selectedReader
).then(function (connectResult
) {
89 if (connectResult
.error
) {
90 alert(connectResult
.error
.message
);
91 console
.log('Failed to connect: ', connectResult
.error
);
93 document
.getElementById("collect-button").classList
.remove("d-none");
94 console
.log('Connected to reader: ', connectResult
.reader
.label
);
95 log('Connect to Reader', connectResult
)
100 function fetchPaymentIntentClientSecret(amount
) {
101 const bodyContent
= JSON
.stringify({
105 return fetch('./front_payment_cc.php?mode=terminal_create', {
108 'Content-Type': 'application/json'
111 }).then(function (response
) {
112 return response
.json();
113 }).then(function (data
) {
114 return data
.client_secret
;
119 let isChargePending
= false;
121 function collectPayment(amount
) {
123 fetchPaymentIntentClientSecret(amount
).then(function (client_secret
) {
124 terminal
.collectPaymentMethod(client_secret
).then(function (result
) {
126 alert(result
.error
.message
);
128 log('Collect Payment Method', result
.paymentIntent
);
129 terminal
.processPayment(result
.paymentIntent
).then(function (result
) {
131 console
.log(result
.error
);
132 alert(result
.error
.message
);
133 } else if (result
.paymentIntent
) {
134 paymentIntentId
= result
.paymentIntent
.id
;
135 log('Process Payment', result
.paymentIntent
);
136 isChargePending
= true;
137 document
.getElementById("collect-button").classList
.add("d-none");
138 document
.getElementById("capture-button").classList
.remove("d-none");
139 document
.getElementById("refund-button").classList
.remove("d-none");
150 function capture(paymentIntentId
) {
151 return fetch('./front_payment_cc.php?mode=terminal_capture', {
154 'Content-Type': 'application/json'
156 body
: JSON
.stringify({"id": paymentIntentId
})
157 }).then(function (response
) {
158 return response
.json();
159 }).then(function (data
) {
161 log('Capture Payment Error', data
.error
);
162 console
.log(data
.error
);
166 opener
.document
.getElementById("check_number").value
= data
.id
;
167 opener
.$
("[name='form_save']").click();
172 function cancel(paymentIntentId
) {
173 return fetch('./front_payment_cc.php?mode=cancel_intent', {
176 'Content-Type': 'application/json'
178 body
: JSON
.stringify({"id": paymentIntentId
})
179 }).then(function (response
) {
180 return response
.json();
181 }).then(function (data
) {
183 log('Cancel Payment Error', data
.error
);
184 console
.log(data
.error
);
188 isChargePending
= false;
189 document
.getElementById("refund-button").classList
.add("d-none");
190 document
.getElementById("collect-button").classList
.remove("d-none");
191 document
.getElementById("capture-button").classList
.add("d-none");
192 log('Cancel Payment', data
.status
);
197 const collectButton
= document
.getElementById('collect-button');
198 collectButton
.addEventListener('click', async (event
) => {
199 collectPayment(amount
);
202 const captureButton
= document
.getElementById('capture-button');
203 captureButton
.addEventListener('click', async (event
) => {
204 capture(paymentIntentId
);
207 const cancelIntentButton
= document
.getElementById('refund-button');
208 cancelIntentButton
.addEventListener('click', async (event
) => {
209 cancel(paymentIntentId
);
212 const cancelButton
= parent
.document
.getElementById('closeBtn');
213 cancelButton
.addEventListener('click', async (event
) => {
214 if (isChargePending
) {
215 if (confirm(xl("There is a charge transaction that has not been captured." +
"\n" +
xl("Are you sure?")))) {
223 discoverReaderHandler();
226 function log(method
, message
) {
227 let logs
= document
.getElementById("logs");
228 let title
= document
.createElement("div");
229 let log
= document
.createElement("div");
230 let lineCol
= document
.createElement("div");
231 let logCol
= document
.createElement("div");
232 title
.classList
.add('row');
233 title
.classList
.add('log-title');
234 title
.textContent
= method
;
235 log
.classList
.add('row');
236 log
.classList
.add('log');
237 let hr
= document
.createElement("hr");
238 let pre
= document
.createElement("pre");
239 let code
= document
.createElement("code");
240 code
.textContent
= formatJson(JSON
.stringify(message
, undefined
, 2));
248 function stringLengthOfInt(number
) {
249 return number
.toString().length
;
252 function padSpaces(lineNumber
, fixedWidth
) {
253 // Always indent by 2 and then maybe more, based on the width of the line
255 return " ".repeat(2 + fixedWidth
- stringLengthOfInt(lineNumber
));
258 function formatJson(message
) {
259 let lines
= message
.split('\n');
261 let lineNumberFixedWidth
= stringLengthOfInt(lines
.length
);
262 for (let i
= 1; i
<= lines
.length
; i +
= 1) {
263 line
= i +
padSpaces(i
, lineNumberFixedWidth
) + lines
[i
- 1];
264 json
= json + line +
'\n';
271 <div
class="container-fluid ">
273 <div
class="col-sm-6 offset-sm-3">
274 <h4
><span
class="m-1"><?php
echo xlt("Paying Amount") ?
></span
><i
>$
</i
><span
><?php
echo text($total); ?
></span
></h4
>
277 <div
class="row m-1">
278 <button id
="collect-button" class="btn btn-primary btn-transmit m-1 d-none"><?php
echo xlt("Collect Payment")?
></button
>
279 <button id
="capture-button" class="btn btn-primary btn-transmit m-1 d-none"><?php
echo xlt("Post Payment")?
></button
>
280 <button id
="refund-button" class="btn btn-primary btn-transmit m-1 d-none"><?php
echo xlt("Cancel Payment")?
></button
>
283 <div
class="row ml-2"><h5
><?php
echo xlt("Transaction Progress") ?
></h5
></div
>
284 <div
class="col-sm-12 p-2 bg-secondary" id
="logs"><i
class="fa fa-spinner fa-spin fa-2x"></i
></div
>