{"id":4,"date":"2026-04-06T02:32:53","date_gmt":"2026-04-06T02:32:53","guid":{"rendered":"http:\/\/localhost:8080\/?page_id=4"},"modified":"2026-04-06T02:32:53","modified_gmt":"2026-04-06T02:32:53","slug":"qris-test","status":"publish","type":"page","link":"https:\/\/akirapay.org\/test-wordpress\/qris-test\/","title":{"rendered":"QRIS Payment Test"},"content":{"rendered":"        <style>\n            .qris-container {\n                max-width: 800px;\n                margin: 0 auto;\n                padding: 30px;\n                background: #1a1a2e;\n                color: #e0e0e0;\n                border-radius: 12px;\n                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n            }\n            .qris-container h2 {\n                color: #00d4aa;\n                margin-top: 0;\n                font-size: 24px;\n                border-bottom: 2px solid #16213e;\n                padding-bottom: 15px;\n            }\n            .qris-order-table {\n                width: 100%;\n                border-collapse: collapse;\n                margin-bottom: 25px;\n                background: #16213e;\n                border-radius: 8px;\n                overflow: hidden;\n            }\n            .qris-order-table td {\n                padding: 12px 16px;\n                border-bottom: 1px solid #1a1a2e;\n            }\n            .qris-order-table td:first-child {\n                font-weight: 600;\n                color: #8892b0;\n                width: 140px;\n            }\n            .qris-order-table td:last-child {\n                color: #ccd6f6;\n            }\n            .qris-amount {\n                font-size: 28px;\n                font-weight: 700;\n                color: #00d4aa !important;\n            }\n            #qris-payment-result {\n                min-height: 100px;\n                background: #0f0f23;\n                border: 2px dashed #16213e;\n                border-radius: 8px;\n                padding: 20px;\n                margin: 20px 0;\n                text-align: center;\n                color: #8892b0;\n            }\n            .qris-btn-pay {\n                display: inline-block;\n                padding: 14px 40px;\n                background: linear-gradient(135deg, #00d4aa, #00b894);\n                color: #1a1a2e;\n                font-size: 18px;\n                font-weight: 700;\n                border: none;\n                border-radius: 8px;\n                cursor: pointer;\n                transition: all 0.3s;\n                text-transform: uppercase;\n                letter-spacing: 1px;\n            }\n            .qris-btn-pay:hover {\n                background: linear-gradient(135deg, #00e6b8, #00cca3);\n                transform: translateY(-2px);\n                box-shadow: 0 4px 15px rgba(0, 212, 170, 0.4);\n            }\n            .qris-btn-pay:disabled {\n                opacity: 0.5;\n                cursor: not-allowed;\n                transform: none;\n            }\n            .qris-test-panel {\n                margin-top: 30px;\n                background: #0f0f23;\n                border-radius: 8px;\n                border: 1px solid #16213e;\n                overflow: hidden;\n            }\n            .qris-test-panel h3 {\n                background: #16213e;\n                margin: 0;\n                padding: 12px 16px;\n                font-size: 14px;\n                color: #00d4aa;\n                cursor: pointer;\n            }\n            .qris-test-log {\n                padding: 16px;\n                max-height: 400px;\n                overflow-y: auto;\n                font-family: 'SF Mono', 'Fira Code', monospace;\n                font-size: 12px;\n                line-height: 1.6;\n            }\n            .qris-test-log .pass { color: #00d4aa; }\n            .qris-test-log .fail { color: #ff6b6b; }\n            .qris-test-log .info { color: #74b9ff; }\n            .qris-test-log .warn { color: #feca57; }\n            .qris-status-bar {\n                display: flex;\n                gap: 20px;\n                padding: 12px 16px;\n                background: #16213e;\n                border-top: 1px solid #1a1a2e;\n                font-size: 13px;\n                font-weight: 600;\n            }\n        <\/style>\n\n        <div class=\"qris-container\">\n            <h2>QRIS Payment Test<\/h2>\n\n            <table class=\"qris-order-table\">\n                <tr>\n                    <td>Invoice<\/td>\n                    <td><code style=\"background:#0f0f23;padding:4px 8px;border-radius:4px;color:#feca57;\">INV-20260406-001<\/code><\/td>\n                <\/tr>\n                <tr>\n                    <td>Amount<\/td>\n                    <td class=\"qris-amount\">Rp 25.000<\/td>\n                <\/tr>\n                <tr>\n                    <td>Payor Name<\/td>\n                    <td>Budi Santoso<\/td>\n                <\/tr>\n                <tr>\n                    <td>Payor Email<\/td>\n                    <td>budi@example.com<\/td>\n                <\/tr>\n                <tr>\n                    <td>Notes<\/td>\n                    <td>WordPress QRIS Test Payment<\/td>\n                <\/tr>\n                <tr>\n                    <td>Status<\/td>\n                    <td><span id=\"qris-order-status\" style=\"background:#fff3cd;color:#856404;padding:4px 10px;border-radius:4px;font-weight:600;\">PENDING<\/span><\/td>\n                <\/tr>\n            <\/table>\n\n            <div id=\"qris-payment-result\">\n                <p>QR Code will appear here after clicking \"Bayar QRIS\"<\/p>\n            <\/div>\n\n            <div style=\"text-align:center; margin:20px 0;\">\n                <button id=\"btn-bayar-qris\" class=\"qris-btn-pay\" onclick=\"initQrisPayment()\">Bayar QRIS<\/button>\n            <\/div>\n\n            <!-- Test Panel -->\n            <div class=\"qris-test-panel\">\n                <h3 onclick=\"this.nextElementSibling.style.display = this.nextElementSibling.style.display === 'none' ? 'block' : 'none'\">\n                    Unit Test Results (click to toggle)\n                <\/h3>\n                <div id=\"qris-test-log\" class=\"qris-test-log\"><\/div>\n                <div id=\"qris-test-stats\" class=\"qris-status-bar\">\n                    <span class=\"pass\">Passed: <span id=\"test-pass-count\">0<\/span><\/span>\n                    <span class=\"fail\">Failed: <span id=\"test-fail-count\">0<\/span><\/span>\n                    <span class=\"info\">Total: <span id=\"test-total-count\">0<\/span><\/span>\n                <\/div>\n            <\/div>\n        <\/div>\n\n        <script>\n        (function() {\n            var ajaxUrl = 'https:\/\/akirapay.org\/test-wordpress\/wp-admin\/admin-ajax.php';\n            var nonce   = '1188525ab3';\n            var config  = {\n                amount:       25000,\n                invoice:      'INV-20260406-001',\n                notes:        'WordPress QRIS Test Payment',\n                payor_name:   'Budi Santoso',\n                payor_email:  'budi@example.com',\n                expirationInterval: 300,\n                resultContainerId: 'qris-payment-result'\n            };\n\n            \/\/ Test runner\n            var testLog = document.getElementById('qris-test-log');\n            var passCount = 0, failCount = 0, totalCount = 0;\n\n            function log(msg, cls) {\n                cls = cls || 'info';\n                var line = document.createElement('div');\n                line.className = cls;\n                var ts = new Date().toLocaleTimeString();\n                line.textContent = '[' + ts + '] ' + (cls === 'pass' ? 'PASS' : cls === 'fail' ? 'FAIL' : cls === 'warn' ? 'WARN' : 'INFO') + ' ' + msg;\n                testLog.appendChild(line);\n                testLog.scrollTop = testLog.scrollHeight;\n            }\n\n            function assert(condition, label) {\n                totalCount++;\n                if (condition) {\n                    passCount++;\n                    log(label, 'pass');\n                } else {\n                    failCount++;\n                    log(label, 'fail');\n                }\n                updateStats();\n            }\n\n            function updateStats() {\n                document.getElementById('test-pass-count').textContent = passCount;\n                document.getElementById('test-fail-count').textContent = failCount;\n                document.getElementById('test-total-count').textContent = totalCount;\n            }\n\n            \/\/ ---- Unit Tests ----\n            function runTests() {\n                log('Starting QRIS SDK Unit Tests...', 'info');\n                log('='.repeat(50), 'info');\n\n                \/\/ Test 1: SDK loaded\n                assert(typeof window.QrisSDK !== 'undefined' || typeof window.QrisPaymentSDK !== 'undefined', 'SDK script is loaded globally');\n\n                var SDKClass = window.QrisSDK || window.QrisPaymentSDK;\n\n                \/\/ Test 2: SDK is a constructor\n                assert(typeof SDKClass === 'function', 'SDK is a constructor function');\n\n                \/\/ Test 3: Instantiate with valid config\n                var testInstance = null;\n                try {\n                    testInstance = new SDKClass({\n                        amount: 10000,\n                        invoice: 'TEST-UNIT-001',\n                        notes: 'unit test',\n                        payor_name: 'Unit Tester',\n                        payor_email: 'unit@test.com',\n                        resultContainerId: 'qris-payment-result'\n                    });\n                    assert(testInstance !== null, 'Constructor accepts valid config without throwing');\n                } catch (e) {\n                    assert(false, 'Constructor accepts valid config without throwing (' + e.message + ')');\n                }\n\n                \/\/ Test 4: Instance has expected methods\n                if (testInstance) {\n                    assert(typeof testInstance.openPayment === 'function', 'Instance has openPayment() method');\n                }\n\n                \/\/ Test 5: Config properties are stored\n                if (testInstance) {\n                    var hasConfig = testInstance.amount === 10000 ||\n                                   (testInstance.config && testInstance.config.amount === 10000) ||\n                                   (testInstance.options && testInstance.options.amount === 10000);\n                    assert(hasConfig, 'Amount config is stored correctly');\n                }\n\n                \/\/ Test 6: Constructor with missing invoice\n                try {\n                    var badInstance = new SDKClass({\n                        amount: 5000,\n                        payor_name: 'No Invoice'\n                    });\n                    \/\/ If no error, it might still be valid\n                    log('Constructor did not throw on missing invoice (may validate lazily)', 'warn');\n                } catch (e) {\n                    assert(true, 'Constructor validates required invoice field');\n                }\n\n                \/\/ Test 7: Zero amount handling\n                try {\n                    var zeroInstance = new SDKClass({\n                        amount: 0,\n                        invoice: 'TEST-ZERO',\n                        resultContainerId: 'qris-payment-result'\n                    });\n                    log('Zero amount accepted (may validate on render)', 'warn');\n                } catch (e) {\n                    assert(true, 'Constructor rejects zero amount');\n                }\n\n                \/\/ Test 8: Negative amount handling\n                try {\n                    var negInstance = new SDKClass({\n                        amount: -1000,\n                        invoice: 'TEST-NEG',\n                        resultContainerId: 'qris-payment-result'\n                    });\n                    log('Negative amount accepted (may validate on render)', 'warn');\n                } catch (e) {\n                    assert(true, 'Constructor rejects negative amount');\n                }\n\n                \/\/ Test 9: String amount handling\n                try {\n                    var strInstance = new SDKClass({\n                        amount: '25000',\n                        invoice: 'TEST-STR',\n                        resultContainerId: 'qris-payment-result'\n                    });\n                    assert(true, 'Constructor handles string amount gracefully');\n                } catch (e) {\n                    log('String amount rejected: ' + e.message, 'warn');\n                }\n\n                \/\/ Test 10: Container element exists\n                var container = document.getElementById('qris-payment-result');\n                assert(container !== null, 'Result container element exists in DOM');\n\n                \/\/ Test 11: AJAX endpoint reachable\n                var xhr = new XMLHttpRequest();\n                xhr.open('POST', ajaxUrl, true);\n                xhr.setRequestHeader('Content-Type', 'application\/x-www-form-urlencoded');\n                xhr.onreadystatechange = function() {\n                    if (xhr.readyState === 4) {\n                        assert(xhr.status === 200 || xhr.status === 400, 'AJAX endpoint responds (status: ' + xhr.status + ')');\n                    }\n                };\n                xhr.send('action=qris_health_check&_ajax_nonce=' + nonce);\n\n                log('='.repeat(50), 'info');\n                log('Unit tests complete. Passed: ' + passCount + ' \/ ' + totalCount, passCount === totalCount ? 'pass' : 'warn');\n            }\n\n            \/\/ ---- Payment Init ----\n            window.initQrisPayment = function() {\n                var btn = document.getElementById('btn-bayar-qris');\n                btn.disabled = true;\n                btn.textContent = 'Loading...';\n\n                var SDKClass = window.QrisSDK || window.QrisPaymentSDK;\n                if (!SDKClass) {\n                    alert('QRIS SDK not loaded!');\n                    btn.disabled = false;\n                    btn.textContent = 'Bayar QRIS';\n                    return;\n                }\n\n                try {\n                    var sdk = new SDKClass({\n                        amount:             config.amount,\n                        invoice:            config.invoice,\n                        notes:              config.notes,\n                        payor_name:         config.payor_name,\n                        payor_email:        config.payor_email,\n                        expirationInterval: config.expirationInterval,\n                        resultContainerId:  config.resultContainerId,\n                        onSuccess: function(data) {\n                            log('Payment SUCCESS callback fired', 'pass');\n                            log('Transaction data: ' + JSON.stringify(data), 'info');\n                            document.getElementById('qris-order-status').textContent = 'PAID';\n                            document.getElementById('qris-order-status').style.background = '#d4edda';\n                            document.getElementById('qris-order-status').style.color = '#155724';\n\n                            \/\/ AJAX update\n                            var xhr = new XMLHttpRequest();\n                            xhr.open('POST', ajaxUrl, true);\n                            xhr.setRequestHeader('Content-Type', 'application\/x-www-form-urlencoded');\n                            xhr.onreadystatechange = function() {\n                                if (xhr.readyState === 4) {\n                                    log('Status update response: ' + xhr.responseText, xhr.status === 200 ? 'pass' : 'fail');\n                                }\n                            };\n                            var txId = (data && (data.transaction_id || data.transactionId || data.id)) || '';\n                            xhr.send('action=qris_mark_paid&invoice=' + encodeURIComponent(config.invoice) + '&transaction_id=' + encodeURIComponent(txId) + '&_ajax_nonce=' + nonce);\n                        },\n                        onFailed: function(error) {\n                            log('Payment FAILED callback fired: ' + JSON.stringify(error), 'fail');\n                            document.getElementById('qris-order-status').textContent = 'FAILED';\n                            document.getElementById('qris-order-status').style.background = '#f8d7da';\n                            document.getElementById('qris-order-status').style.color = '#721c24';\n                        }\n                    });\n\n                    \/\/ Open payment popup \u2014 SDK shows QR in a popup window\n                    sdk.openPayment();\n\n                    btn.textContent = 'Payment Popup Opened';\n                    log('QRIS SDK initialized \u2014 payment popup opened', 'pass');\n                } catch (e) {\n                    log('SDK init error: ' + e.message, 'fail');\n                    btn.disabled = false;\n                    btn.textContent = 'Bayar QRIS';\n                }\n            };\n\n            \/\/ Wait for SDK script to load then run tests\n            function waitForSDK(attempts) {\n                if (attempts <= 0) {\n                    log('SDK script did not load after timeout', 'fail');\n                    totalCount++;\n                    failCount++;\n                    updateStats();\n                    return;\n                }\n                if (typeof window.QrisSDK !== 'undefined' || typeof window.QrisPaymentSDK !== 'undefined') {\n                    runTests();\n                } else {\n                    setTimeout(function() { waitForSDK(attempts - 1); }, 500);\n                }\n            }\n\n            \/\/ Start tests after DOM ready\n            if (document.readyState === 'loading') {\n                document.addEventListener('DOMContentLoaded', function() { waitForSDK(20); });\n            } else {\n                waitForSDK(20);\n            }\n        })();\n        <\/script>\n        \n","protected":false},"excerpt":{"rendered":"","protected":false},"author":0,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-4","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/akirapay.org\/test-wordpress\/wp-json\/wp\/v2\/pages\/4","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/akirapay.org\/test-wordpress\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/akirapay.org\/test-wordpress\/wp-json\/wp\/v2\/types\/page"}],"replies":[{"embeddable":true,"href":"https:\/\/akirapay.org\/test-wordpress\/wp-json\/wp\/v2\/comments?post=4"}],"version-history":[{"count":0,"href":"https:\/\/akirapay.org\/test-wordpress\/wp-json\/wp\/v2\/pages\/4\/revisions"}],"wp:attachment":[{"href":"https:\/\/akirapay.org\/test-wordpress\/wp-json\/wp\/v2\/media?parent=4"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}