mirror of
https://github.com/anna-sara/lan_kiosk
synced 2026-09-03 10:25:02 +02:00
129 lines
4.4 KiB
PHP
129 lines
4.4 KiB
PHP
<?php
|
|
|
|
/**
|
|
* A stand-in for the Swish Handel API, for trying the payment flow locally
|
|
* before there is an agreement and a certificate.
|
|
*
|
|
* It answers the payment request the way Swish does — with a
|
|
* PaymentRequestToken header — and then calls the callback URL from the request
|
|
* a few seconds later, carrying the callbackIdentifier back as a header just
|
|
* like the real one. Everything downstream is real: the receiver app, the
|
|
* callback verification, the booking.
|
|
*
|
|
* The only thing it cannot do is open the Swish app. The token is made up, so
|
|
* the app would reject it — on a phone the page simply stays in the waiting
|
|
* state until the fake callback lands, which is exactly what we want to see.
|
|
*
|
|
* Start it inside the kiosk container:
|
|
*
|
|
* docker exec -d lan_kiosk-api-1 php -S 127.0.0.1:9099 /var/www/html/dev/fake-swish.php
|
|
*
|
|
* and point the app at it in .env:
|
|
*
|
|
* SWISH_API_URL=http://127.0.0.1:9099
|
|
*
|
|
* Amounts that behave differently, so every screen can be reached:
|
|
*
|
|
* 13 kr → DECLINED, the payer aborted in the app
|
|
* 1 kr → no callback at all, the page keeps waiting until it times out
|
|
* other → PAID after DELAY_SECONDS
|
|
*
|
|
* Delete this directory once Swish Handel is in place.
|
|
*/
|
|
|
|
const DELAY_SECONDS = 4;
|
|
|
|
// Second mode: started in the background by the request below to deliver the
|
|
// callback after the payment request has already been answered.
|
|
if (PHP_SAPI === 'cli') {
|
|
$payload = json_decode(base64_decode($argv[1] ?? ''), true);
|
|
|
|
if (!$payload) {
|
|
exit(1);
|
|
}
|
|
|
|
sleep(DELAY_SECONDS);
|
|
|
|
$body = json_encode($payload['body']);
|
|
|
|
$context = stream_context_create([
|
|
'http' => [
|
|
'method' => 'POST',
|
|
'header' => "Content-Type: application/json\r\n"
|
|
. 'callbackIdentifier: ' . $payload['identifier'] . "\r\n",
|
|
'content' => $body,
|
|
'timeout' => 15,
|
|
'ignore_errors' => true,
|
|
],
|
|
]);
|
|
|
|
$result = @file_get_contents($payload['url'], false, $context);
|
|
|
|
file_put_contents('/tmp/fake-swish.log', sprintf(
|
|
"[%s] callback %s -> %s\n",
|
|
date('H:i:s'),
|
|
$payload['url'],
|
|
$result === false ? 'FAILED' : 'sent'
|
|
), FILE_APPEND);
|
|
|
|
exit(0);
|
|
}
|
|
|
|
// First mode: the payment request itself
|
|
$request = json_decode(file_get_contents('php://input'), true) ?: [];
|
|
$amount = (float) ($request['amount'] ?? 0);
|
|
|
|
file_put_contents('/tmp/fake-swish.log', sprintf(
|
|
"[%s] %s %s %s\n",
|
|
date('H:i:s'),
|
|
$_SERVER['REQUEST_METHOD'],
|
|
$_SERVER['REQUEST_URI'],
|
|
json_encode($request)
|
|
), FILE_APPEND);
|
|
|
|
// Swish rejects a payment request without a payee, and so do we
|
|
if (empty($request['payeeAlias'])) {
|
|
http_response_code(422);
|
|
header('Content-Type: application/json');
|
|
echo json_encode([['errorCode' => 'PA02', 'errorMessage' => 'Payee alias is missing']]);
|
|
return;
|
|
}
|
|
|
|
$instructionUuid = basename($_SERVER['REQUEST_URI']);
|
|
|
|
header('PaymentRequestToken: FAKE' . substr($instructionUuid, 0, 12));
|
|
header('Location: http://127.0.0.1:9099/swish-cpcapi/api/v1/paymentrequests/' . $instructionUuid);
|
|
http_response_code(201);
|
|
|
|
// 1 kr means "the payer never finishes", so no callback is ever sent
|
|
if ((int) $amount === 1) {
|
|
return;
|
|
}
|
|
|
|
$callback = [
|
|
// Already ends in /kiosk — the app puts it there so the receiver knows where to forward
|
|
'url' => $request['callbackUrl'],
|
|
'identifier' => $request['callbackIdentifier'] ?? '',
|
|
'body' => [
|
|
'id' => $instructionUuid,
|
|
'payeePaymentReference' => $request['payeePaymentReference'] ?? '',
|
|
'paymentReference' => strtoupper(bin2hex(random_bytes(16))),
|
|
'payerAlias' => '46701234567',
|
|
'payeeAlias' => $request['payeeAlias'],
|
|
'amount' => $amount,
|
|
'currency' => 'SEK',
|
|
'message' => $request['message'] ?? '',
|
|
'status' => (int) $amount === 13 ? 'DECLINED' : 'PAID',
|
|
'errorCode' => (int) $amount === 13 ? 'BANKIDCL' : null,
|
|
'errorMessage' => '',
|
|
'dateCreated' => date('c'),
|
|
'datePaid' => (int) $amount === 13 ? null : date('c'),
|
|
],
|
|
];
|
|
|
|
// Answer first, deliver the callback afterwards — the same order as the real thing
|
|
exec(sprintf(
|
|
'php %s %s > /dev/null 2>&1 &',
|
|
escapeshellarg(__FILE__),
|
|
escapeshellarg(base64_encode(json_encode($callback)))
|
|
));
|