Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ jobs:
- run: php tests/test-shortcode-assets.php
- run: php tests/test-webhook-signature.php
- run: php tests/test-verification-authorization.php
- run: php tests/test-checkout-retry.php
- run: php tests/test-redirect-fallback.php
- run: php tests/test-iframe-permissions.php
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Identity verification for WordPress & WooCommerce using the [Didit SDK](https://
| **Shortcode** | `[didit_verify]` — drop a verification button on any page |
| **WooCommerce** | Require identity verification at checkout |
| **Two modes** | **UniLink** (no backend, paste a URL) or **API** (unique sessions per user) |
| **Display** | Modal (popup overlay) or Embedded (inline) |
| **Display** | Modal (popup overlay), Embedded (inline) or Redirect (hosted page) |
| **Customizable** | Button colors, text, padding, radius — all configurable with live preview. Checkout section title and messages editable too |
| **Translatable** | All customer-facing text is translatable; German (`de_DE`, `de_DE_formal`) included |
| **Product scope** | Require verification for all products, only selected products, or all except selected |
Expand Down Expand Up @@ -82,7 +82,7 @@ Configure how the verification UI appears:

| Setting | Description |
|---------|-------------|
| **Display Mode** | Modal (popup overlay) or Embedded (inline where shortcode is placed) |
| **Display Mode** | Modal (popup overlay), Embedded (inline where shortcode is placed) or Redirect (hosted verification page) |
| **Close Button** | Show or hide the X button on the modal |
| **Exit Confirmation** | "Are you sure?" dialog when closing the modal |
| **Auto-close** | Automatically close the modal when verification completes |
Expand Down Expand Up @@ -126,6 +126,14 @@ This renders a verification button styled with your Button Appearance settings.
[didit_verify mode="embedded"]
```

**Redirect instead of embedding:**

```
[didit_verify mode="redirect"]
```

Redirect opens the hosted verification page in the browser. Use it when the camera cannot start inside a frame — the liveness step on some Android Chrome devices, for example — and shoppers come back to the page where they started. On return the plugin confirms the result on the server and keeps the checkout pointing at the same session.

### 7. Status & content gating

**Show verification status** anywhere:
Expand Down Expand Up @@ -276,7 +284,7 @@ All data is sanitized server-side before being sent to the Didit API.
|-----------|---------|-------------|
| `text` | Admin setting | Button label |
| `success_text` | Admin setting | Label after verification |
| `mode` | Admin setting | `modal` or `embedded` — override display mode |
| `mode` | Admin setting | `modal`, `embedded` or `redirect` — override display mode |

### `[didit_status]`

Expand Down
79 changes: 76 additions & 3 deletions assets/js/didit-verify.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

var activeButton = null;
var pendingResult = null;
var RESUME_KEY = "diditPendingSessionId";

function feedback(btn, message, isError) {
var node = btn.parentNode.querySelector('.didit-feedback');
Expand All @@ -33,6 +34,61 @@
}
}

function rememberSession(sessionId) {
try { window.sessionStorage.setItem(RESUME_KEY, sessionId); } catch (e) { /* storage may be blocked */ }
}

function forgetSession() {
try { window.sessionStorage.removeItem(RESUME_KEY); } catch (e) { /* storage may be blocked */ }
if (window.history && window.history.replaceState) {
try {
var current = new URL(window.location.href);
if (current.searchParams.has("verificationSessionId")) {
window.history.replaceState({}, "", pageUrl());
}
} catch (e) { /* older browser, the parameter is harmless */ }
}
}

// This page without the parameters Didit appends on the way back.
function pageUrl() {
try {
var current = new URL(window.location.href);
if (current.searchParams.has("verificationSessionId")) {
current.searchParams.delete("verificationSessionId");
current.searchParams.delete("status");
}
return current.toString();
} catch (e) {
return window.location.href;
}
}

function returnedSessionId() {
var id = "";
try {
id = new URL(window.location.href).searchParams.get("verificationSessionId") || "";
} catch (e) { id = ""; }
if (!id) {
try { id = window.sessionStorage.getItem(RESUME_KEY) || ""; } catch (e) { id = ""; }
}
return /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(id) ? id : "";
}

// The shopper comes back from the hosted page: confirm what Didit decided and keep
// the checkout pointing at the same session, so the order carries the result.
function resumeReturnedSession() {
var sessionId = returnedSessionId();
if (!sessionId) return;
var btn = document.querySelector(".didit-verify-btn");
if (!btn || btn.classList.contains("didit-verified")) {
forgetSession();
return;
}
activeButton = btn;
confirmResult({ type: "completed", session: { sessionId: sessionId } }, btn).then(forgetSession, forgetSession);
}

function confirmResult(result, btn) {
btn.textContent = i18n.confirming || 'Confirming verification…';
btn.disabled = true;
Expand Down Expand Up @@ -64,7 +120,7 @@
pendingResult = null;
setCheckoutSession(data.sessionId);
feedback(btn, i18n.approved || 'Identity verified. You can continue.', false);
} else if (data.status === 'Declined' || data.status === 'Expired' || data.status === 'KYC Expired') {
} else if (data.status === 'Declined' || data.status === 'Abandoned' || data.status === 'Expired' || data.status === 'KYC Expired') {
pendingResult = null;
resetBtn(btn);
feedback(btn, data.status === 'Declined' ? (i18n.declined || 'Verification declined. Please try again.') : (i18n.expired || 'Verification expired. Please start again.'), true);
Expand Down Expand Up @@ -190,6 +246,9 @@
requestBody.order_id = btn.dataset.orderId;
requestBody.order_key = btn.dataset.orderKey || "";
}
if (btn.dataset.display === "redirect") {
requestBody.return_url = pageUrl();
}

fetch(cfg.restUrl, {
method: "POST",
Expand All @@ -209,7 +268,7 @@
})
.then(function (data) {
if (!data.url) throw new Error(i18n.noUrl || "No verification URL returned");
startSdk(data.url, btn);
startSdk(data.url, btn, data.sessionId);
})
.catch(function (err) {
feedback(btn, err.message, true);
Expand All @@ -223,7 +282,15 @@
btn.disabled = false;
}

function startSdk(url, btn) {
function startSdk(url, btn, sessionId) {
// The server marks every redirect button, so a shortcode's own mode always wins.
if (btn.dataset.display === "redirect") {
// The hosted page is the only place a blocked camera can recover the flow.
if (sessionId) rememberSession(sessionId);
window.location.assign(url);
return;
}

var config = {
showCloseButton: cfg.showCloseButton,
showExitConfirmation: cfg.showExitConfirmation,
Expand All @@ -243,4 +310,10 @@
DiditSdk.shared.startVerification({ url: url, configuration: config });
btn.textContent = btn.dataset.text || "Verify Identity";
}

if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", resumeReturnedSession);
} else {
resumeReturnedSession();
}
})();
Loading
Loading