Stripe Tap to Pay Integration
August 7, 2025 · AI Conversations
I have currently Framework 7 and jquery apace cordova powered app backend is codeignter and payment site is stripe
—
### ✅ Overview
**Tap to Pay on Android** turns Android devices into contactless terminals using NFC — no hardware needed. Stripe supports this via the **Stripe Terminal SDK**.
—
### 🧩 Requirements
1. Android device with NFC + GMS (Google Mobile Services).
2. A [Stripe account](https://stripe.com/terminal/tap-to-pay).
3. Cordova Android platform ≥ 10.
4. Access to Stripe's [Tap to Pay on Android](https://stripe.com/docs/terminal/android/tap-to-pay).
—
### 🛠️ Steps to Integrate Stripe Tap to Pay in Cordova App
#### 1. **Enable Tap to Pay in Stripe Dashboard**
– Go to Stripe Dashboard → Terminal → Locations
– Create a **Location** with Tap to Pay enabled.
– Generate **Reader registration code** (used to register the Android device).
#### 2. **Add Android SDK Bridge in Cordova**
Since Stripe Terminal SDK is native Android Java/Kotlin, you'll need to **create a Cordova plugin** that wraps around the SDK.
##### Option A: Use existing plugin (if available)
Currently, **no official Cordova plugin** supports Stripe Terminal Tap to Pay — so we need a custom plugin.
##### Option B: Write a Cordova Plugin Wrapper (recommended)
You can create a plugin like:
cordova create stripe-tap-plugin com.example.stripe TerminalPlugin
Add native Java code in `src/android/TerminalPlugin.java` to:
– Initialize Stripe Terminal
– Discover Tap to Pay reader
– Handle card read events
– Create/collect payment intents
– Confirm payments
> Let me know and I’ll give you the **ready-made plugin boilerplate** if needed.
—
#### 3. **In Your Cordova App (JS side)**
You’ll need to call the native plugin:
document.addEventListener('deviceready', function () {
cordova.plugins.TerminalPlugin.startTapToPay({
locationId: 'loc_xxx',
connectionTokenUrl: 'https://your-backend.com/terminal/connection_token'
}, function(success) {
console.log("Reader ready!", success);
}, function(error) {
console.error("Tap to Pay failed", error);
});
});
—
### 🔐 Backend (CodeIgniter): Provide Connection Token
Your Cordova app will need to fetch a **connection token** from Stripe via your backend.
In **CodeIgniter**:
// controller/Terminal.php
public function connection_token()
{
StripeStripe::setApiKey('sk_test_...');
$connectionToken = StripeTerminalConnectionToken::create();
echo json_encode($connectionToken);
}
—
### 📲 Payment Flow in Cordova App
1. App calls native plugin to initialize Stripe Terminal.
2. Native plugin registers Tap to Pay reader.
3. When a card is tapped, plugin creates a **payment intent**.
4. Cordova app collects confirmation → your backend captures it.
—
### 🔍 Testing
– Use **Stripe test cards with NFC simulation** in Stripe’s test mode.
– Add logs at each stage to help debug.
—
### 📦 Alternative: React Native Plugin?
If you're open to switching UI framework to React Native or Flutter, you can use:
– [Stripe Terminal React Native SDK](https://github.com/stripe/stripe-terminal-js-samples)
But for your current stack (Cordova + Framework7 + jQuery), a **custom plugin** is the way to go.
—
Would you like me to create and share a **custom Cordova plugin boilerplate** for Stripe Tap to Pay integration using Android native code?
—
## 📦 Plugin Boilerplate: `cordova-plugin-stripe-tap-to-pay`
### 1. 📁 Folder Structure
cordova-plugin-stripe-tap-to-pay/
├── plugin.xml
├── src/
│ └── android/
│ └── StripeTapToPayPlugin.java
└── www/
└── StripeTapToPay.js
—
### 2. 📄 `plugin.xml`
<plugin id="cordova-plugin-stripe-tap-to-pay" version="1.0.0" xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android">
<name>StripeTapToPay</name>
<description>Cordova plugin for Stripe Tap to Pay</description>
<platform name="android">
<config-file target="AndroidManifest.xml" parent="application">
<uses-permission android:name="android.permission.NFC"/>
</config-file>
<source-file src="src/android/StripeTapToPayPlugin.java" target-dir="src/com/example/stripe"/>
<framework src="com.stripe:stripe-terminal:2.22.0" />
</platform>
<js-module src="www/StripeTapToPay.js" name="StripeTapToPay">
<clobbers target="cordova.plugins.StripeTapToPay"/>
</js-module>
</plugin>
—
### 3. 📄 `www/StripeTapToPay.js`
var exec = require('cordova/exec');
exports.startTapToPay = function (options, success, error) {
exec(success, error, 'StripeTapToPayPlugin', 'startTapToPay', [options]);
};
—
### 4. 📄 `src/android/StripeTapToPayPlugin.java`
This file should include:
– `StripeTerminal.initTerminal(…)`
– `ConnectionTokenProvider` to call your CodeIgniter backend
– `TapToPayDiscoveryConfiguration` to register reader
– `collectPaymentMethod(…)` and `processPayment(…)`
🛠 **Due to the length**, I’ll provide a **minimal working version** here. Let me know if you'd like it fully wired with `createPaymentIntent`, `collect`, and `process`.
package com.example.stripe;
import org.apache.cordova.*;
import org.json.JSONArray;
import org.json.JSONObject;
import android.util.Log;
import com.stripe.stripeterminal.Terminal;
import com.stripe.stripeterminal.external.callable.ConnectionTokenProvider;
import com.stripe.stripeterminal.external.models.ConnectionTokenException;
import com.stripe.stripeterminal.external.models.ConnectionTokenCallback;
public class StripeTapToPayPlugin extends CordovaPlugin {
private static final String TAG = "StripeTapPlugin";
private CallbackContext callbackContext;
@Override
public boolean execute(String action, JSONArray args, CallbackContext cbContext) {
if ("startTapToPay".equals(action)) {
this.callbackContext = cbContext;
cordova.getActivity().runOnUiThread(() -> {
try {
JSONObject opts = args.getJSONObject(0);
String tokenUrl = opts.getString("connectionTokenUrl");
Terminal.initTerminal(
cordova.getActivity().getApplicationContext(),
new TokenProvider(tokenUrl),
null
);
callbackContext.success("Stripe Terminal initialized");
} catch (Exception e) {
callbackContext.error("Error initializing Stripe Terminal: " + e.getMessage());
}
});
return true;
}
return false;
}
private static class TokenProvider implements ConnectionTokenProvider {
private final String tokenUrl;
public TokenProvider(String url) {
this.tokenUrl = url;
}
@Override
public void fetchConnectionToken(ConnectionTokenCallback callback) {
// Fetch token from backend (you should make HTTP call here)
// For now just simulate failure
callback.onFailure(new ConnectionTokenException("Not implemented HTTP fetch"));
}
}
}
—
### ✅ Next Steps
1. Add real HTTP request code in `TokenProvider.fetchConnectionToken()` using `OkHttp` or similar.
2. Add methods to:
– Discover Tap to Pay reader
– Collect payment method
– Create and confirm payment
3. Use `cordova.plugins.StripeTapToPay.startTapToPay({…})` in your JS code.
4. Deploy backend endpoint in CodeIgniter to serve `/terminal/connection_token`.
—
Would you like me to:
– Add real HTTP request code in the Android plugin?
– Add `collectPaymentMethod` and `processPayment` steps?
– Provide a working sample Cordova app using this plugin?
Let me know how deep you'd like to go.
—
### 📁 Project Structure
/www/
index.html
css/
framework7.bundle.min.css
js/
framework7.bundle.min.js
cordova.js (added by Cordova)
StripeTapToPay.js (plugin wrapper)
app.js
—
### 🔑 1. `index.html`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Tap to Pay Terminal</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="css/framework7.bundle.min.css">
</head>
<body>
<div id="app">
<div class="view view-main">
<div class="page page-home">
<div class="navbar">
<div class="navbar-inner">
<div class="title">Tap‑to‑Pay Terminal</div>
</div>
</div>
<div class="page-content block">
<button id="btn-init" class="button button-fill">Start Tap to Pay</button>
<div id="status" class="block"></div>
</div>
</div>
</div>
</div>
<script src="cordova.js"></script>
<script src="js/framework7.bundle.min.js"></script>
<script src="js/StripeTapToPay.js"></script>
<script src="js/app.js"></script>
</body>
</html>
—
### 🔗 2. `js/StripeTapToPay.js` (Cordova Plugin JS Wrapper)
var exec = require('cordova/exec');
module.exports = {
startTapToPay: function (opts, success, error) {
exec(success, error, 'StripeTapToPayPlugin', 'startTapToPay', [opts]);
},
createPaymentIntent: function (amount, currency, success, error) {
exec(success, error, 'StripeTapToPayPlugin', 'createPaymentIntent', [amount, currency]);
},
collectPayment: function (success, error) {
exec(success, error, 'StripeTapToPayPlugin', 'collectPayment', []);
},
processPayment: function (success, error) {
exec(success, error, 'StripeTapToPayPlugin', 'processPayment', []);
}
};
—
### ⚙️ 3. `js/app.js` (Framework7 + jQuery)
$(document).ready(function () {
var app = new Framework7({ root: '#app' });
$('#btn-init').on('click', function () {
$('#status').text('Initializing...');
var opts = {
locationId: 'YOUR_LOCATION_ID',
connectionTokenUrl: 'https://your-server.com/terminal/connection_token'
};
cordova.plugins.StripeTapToPay.startTapToPay(opts, function (res) {
$('#status').text('Tap to Pay initialized. Tap a card...');
waitForTap();
}, function (err) {
$('#status').text('Init error: ' + err);
});
});
function waitForTap() {
var amount = 1999; // ₹19.99 written in cents
var currency = 'INR';
cordova.plugins.StripeTapToPay.createPaymentIntent(amount, currency, function (intent) {
$('#status').text('PaymentIntent created: ' + intent.id + '. Tap a card now.');
cordova.plugins.StripeTapToPay.collectPayment(function (paymentMethod) {
$('#status').text('Payment collected: ' + paymentMethod.id);
cordova.plugins.StripeTapToPay.processPayment(function (paymentResult) {
$('#status').text('Payment processed 👍: ' + paymentResult.status);
}, function (err) {
$('#status').text('Error processing payment: ' + err);
});
}, function (err) {
$('#status').text('Error collecting payment: ' + err);
});
}, function (err) {
$('#status').text('Error creating PaymentIntent: ' + err);
});
}
});
—
### 🧠 4. Backend Endpoint (`CodeIgniter` Controller)
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
require 'vendor/autoload.php'; // ensure Stripe PHP SDK is loaded
class Terminal extends CI_Controller {
public function connection_token() {
header('Content-Type: application/json');
StripeStripe::setApiKey('sk_test_…');
$conn = StripeTerminalConnectionToken::create();
echo json_encode(['secret' => $conn->secret]);
}
public function create_payment_intent() {
header('Content-Type: application/json');
$amount = $this->input->post('amount');
$currency = $this->input->post('currency');
StripeStripe::setApiKey('sk_test_…');
$pi = StripePaymentIntent::create([
'amount' => intval($amount),
'currency' => $currency,
'payment_method_types' => ['card_present'],
]);
echo json_encode(['id' => $pi->id, 'client_secret' => $pi->client_secret]);
}
}
—
### 🛠️ 5. Native Plugin – `StripeTapToPayPlugin.java`
Expanding from earlier template: ensure you implement methods for actions `createPaymentIntent`, `collectPayment`, `processPayment` that integrate with Stripe Terminal SDK.
Each method should:
– Call Stripe Terminal SDK on UI thread.
– Return results back to Cordova success callback.
—
### ✅ How the Flow Works
| Step | What Happens |
|——|—————|
| Init | JS calls `startTapToPay` → native initialises Stripe Terminal with your location and connection token. |
| Tap | JS creates a `PaymentIntent` via native (using CI endpoint) |
| Collect | Card tap triggers `collectPayment`, sending card data to Stripe SDK. |
| Process | `processPayment` completes the transaction and returns status to UI. |
—
### 🔧 What You Need to Do
1. Replace placeholders:
– `YOUR_LOCATION_ID` in JS.
– Backend secret key, domain, amounts, currency codes.
2. Flesh out native methods in `StripeTapToPayPlugin.java` for a robust payment experience.
3. Deploy backend, build Cordova Android with the plugin, test with **Stripe test NFC cards**.
—
If you’d like, I can:
– Provide the full Java plugin with each native method implemented,
– Offer guidance on error handling & SDK callbacks,
– Or even a complete GitHub repo ready to run.
Let me know how much deeper to go!
—
## ✅ Finalized Flow
1. User enters **amount** and **order ID** in the app.
2. App calls backend to **create PaymentIntent**, passing those.
3. Stripe Tap to Pay flow is triggered.
4. On payment success, app sends update to backend with `order_id` and status.
—
### 💡 1. Modify `index.html` (Add Input Fields)
<div class="block">
<input id="order-id" type="text" placeholder="Enter Order ID" class="input">
<input id="amount" type="number" placeholder="Enter Amount (e.g. 1999)" class="input">
<button id="btn-pay" class="button button-fill">Tap to Pay</button>
<div id="status" class="block"></div>
</div>
—
### 🧠 2. Update `app.js` (JS Logic)
$(document).ready(function () {
var app = new Framework7({ root: '#app' });
$('#btn-pay').on('click', function () {
let orderId = $('#order-id').val().trim();
let amount = $('#amount').val().trim();
if (!orderId || !amount) {
$('#status').text('Order ID and Amount are required!');
return;
}
$('#status').text('Initializing Tap to Pay...');
const opts = {
locationId: 'YOUR_LOCATION_ID',
connectionTokenUrl: 'https://your-server.com/terminal/connection_token'
};
cordova.plugins.StripeTapToPay.startTapToPay(opts, function () {
$('#status').text('Stripe Terminal Ready. Creating PaymentIntent...');
$.post('https://your-server.com/terminal/create_payment_intent', {
amount: amount,
currency: 'INR',
order_id: orderId
}).done(function (response) {
const clientSecret = response.client_secret;
$('#status').text('PaymentIntent created. Tap a card now.');
cordova.plugins.StripeTapToPay.collectPayment(function (paymentMethod) {
$('#status').text('Card collected. Processing payment...');
cordova.plugins.StripeTapToPay.processPayment(function (paymentResult) {
$('#status').text('Payment successful: ' + paymentResult.status);
// Update backend via order_id
$.post('https://your-server.com/terminal/update_order_status', {
order_id: orderId,
status: paymentResult.status,
payment_intent_id: response.id
});
}, function (err) {
$('#status').text('Payment processing failed: ' + err);
});
}, function (err) {
$('#status').text('Payment collection failed: ' + err);
});
}).fail(function () {
$('#status').text('Failed to create PaymentIntent.');
});
}, function (err) {
$('#status').text('Tap to Pay init failed: ' + err);
});
});
});
—
### 🔧 3. Backend CodeIgniter (controller: `Terminal.php`)
#### 🔹 `create_payment_intent`
public function create_payment_intent()
{
header('Content-Type: application/json');
$amount = $this->input->post('amount');
$currency = $this->input->post('currency');
$orderId = $this->input->post('order_id');
StripeStripe::setApiKey('sk_test_...');
$intent = StripePaymentIntent::create([
'amount' => intval($amount),
'currency' => $currency,
'payment_method_types' => ['card_present'],
'metadata' => ['order_id' => $orderId]
]);
echo json_encode([
'id' => $intent->id,
'client_secret' => $intent->client_secret
]);
}
—
#### 🔹 `update_order_status`
public function update_order_status()
{
$orderId = $this->input->post('order_id');
$status = $this->input->post('status');
$paymentIntentId = $this->input->post('payment_intent_id');
// Example update
$this->db->where('order_id', $orderId)->update('orders', [
'payment_status' => $status,
'payment_intent_id' => $paymentIntentId
]);
echo json_encode(['success' => true]);
}
—
### ✅ Summary
| Step | Action |
|——|——–|
| 1 | User enters order ID and amount |
| 2 | App creates PaymentIntent with metadata (order ID) |
| 3 | Tap to Pay → collect & process |
| 4 | On success → send order ID + status to backend |
| 5 | Backend updates DB (`orders` table) |
—
Would you like:
– A downloadable Cordova project folder with this implemented?
– The full native plugin with `createPaymentIntent`, `collect`, and `process` in Java?
Let me know how you'd like to proceed!
"name": "StripeTapToPayPlugin",
"type": "code/java",
"content": "package com.example.stripe;nnimport android.app.Activity;nimport android.util.Log;nnimport com.stripe.stripeterminal.Terminal;nimport com.stripe.stripeterminal.external.callable.*;nimport com.stripe.stripeterminal.external.models.*;nnimport org.apache.cordova.*;nimport org.json.JSONArray;nimport org.json.JSONException;nimport org.json.JSONObject;nnimport java.io.IOException;nimport java.util.concurrent.ExecutorService;nimport java.util.concurrent.Executors;nnimport okhttp3.*;nnpublic class StripeTapToPayPlugin extends CordovaPlugin {n private static final String TAG = "StripeTapPlugin";n private static Terminal terminal;n private static Reader currentReader;n private static PaymentIntent currentPaymentIntent;n private static CallbackContext pluginCallback;n private ExecutorService executor = Executors.newSingleThreadExecutor();nn @Overriden public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {n pluginCallback = callbackContext;nn switch (action) {n case "startTapToPay":n JSONObject opts = args.getJSONObject(0);n initTerminal(opts.getString("connectionTokenUrl"), callbackContext);n return true;nn case "createPaymentIntent":n int amount = args.getInt(0);n String currency = args.getString(1);n createPaymentIntent(amount, currency, callbackContext);n return true;nn case "collectPayment":n collectPayment(callbackContext);n return true;nn case "processPayment":n processPayment(callbackContext);n return true;nn default:n return false;n }n }nn private void initTerminal(String tokenUrl, CallbackContext callbackContext) {n Terminal.initTerminal(n cordova.getActivity().getApplicationContext(),n LogLevel.VERBOSE,n new ConnectionTokenProvider() {n @Overriden public void fetchConnectionToken(ConnectionTokenCallback callback) {n executor.execute(() -> {n OkHttpClient client = new OkHttpClient();n Request request = new Request.Builder().url(tokenUrl).build();n try (Response response = client.newCall(request).execute()) {n if (response.isSuccessful() && response.body() != null) {n JSONObject json = new JSONObject(response.body().string());n callback.onSuccess(json.getString("secret"));n } else {n callback.onFailure(new ConnectionTokenException("Failed to fetch token"));n }n } catch (IOException | JSONException e) {n callback.onFailure(new ConnectionTokenException(e.getMessage()));n }n });n }n },n new TerminalEventListener() {}n );nn terminal = Terminal.getInstance();nn // Discover and connect to Tap to Pay readern DiscoveryConfiguration config = new DiscoveryConfiguration(0, DeviceType.TAP_TO_PAY, DiscoveryMethod.BLUETOOTH_SCAN);n terminal.discoverReaders(config, new DiscoveryListener() {n @Overriden public void onUpdateDiscoveredReaders(java.util.List readers) {n if (!readers.isEmpty()) {n Reader reader = readers.get(0);n terminal.connectReader(reader, new ReaderCallback() {n @Overriden public void onSuccess(Reader connectedReader) {n currentReader = connectedReader;n callbackContext.success("Connected to reader: " + reader.getSerialNumber());n }nn @Overriden public void onFailure(TerminalException e) {n callbackContext.error("Failed to connect reader: " + e.getErrorMessage());n }n });n } else {n callbackContext.error("No Tap to Pay reader found.");n }n }n });n }nn private void createPaymentIntent(int amount, String currency, CallbackContext callbackContext) {n PaymentIntentParameters params = new PaymentIntentParameters.Builder()n .setAmount(amount)n .setCurrency(currency)n .build();nn terminal.createPaymentIntent(params, new PaymentIntentCallback() {n @Overriden public void onSuccess(PaymentIntent intent) {n currentPaymentIntent = intent;n callbackContext.success(intent.getId());n }nn @Overriden public void onFailure(TerminalException e) {n callbackContext.error("Failed to create payment intent: " + e.getErrorMessage());n }n });n }nn private void collectPayment(CallbackContext callbackContext) {n terminal.collectPaymentMethod(currentPaymentIntent, new PaymentIntentCallback() {n @Overriden public void onSuccess(PaymentIntent intent) {n currentPaymentIntent = intent;n callbackContext.success(intent.getId());n }nn @Overriden public void onFailure(TerminalException e) {n callbackContext.error("Failed to collect payment: " + e.getErrorMessage());n }n });n }nn private void processPayment(CallbackContext callbackContext) {n terminal.processPayment(currentPaymentIntent, new PaymentIntentCallback() {n @Overriden public void onSuccess(PaymentIntent intent) {n callbackContext.success(intent.getStatus().toString());n }nn @Overriden public void onFailure(TerminalException e) {n callbackContext.error("Failed to process payment: " + e.getErrorMessage());n }n });n }n} "
}
– `startTapToPay`: Initializes Stripe Terminal and connects to Tap to Pay reader.
– `createPaymentIntent`: Creates a new PaymentIntent.
– `collectPayment`: Starts card collection.
– `processPayment`: Confirms and processes the transaction.
Let me know if you want this wrapped into a downloadable Cordova plugin or need iOS support as well.
—
### 🛠️ Step-by-Step: Create and Use `cordova-plugin-stripe-tap-to-pay`
—
### ✅ 1. Create the Plugin Folder
mkdir cordova-plugin-stripe-tap-to-pay
cd cordova-plugin-stripe-tap-to-pay
—
### ✅ 2. Create Required Files
#### A. `plugin.xml`
Create a `plugin.xml` file with this minimal setup:
<?xml version="1.0" encoding="UTF-8"?>
<plugin id="cordova-plugin-stripe-tap-to-pay" version="1.0.0" xmlns="http://apache.org/cordova/ns/plugins/1.0">
<name>StripeTapToPay</name>
<js-module src="www/StripeTapToPay.js" name="StripeTapToPay">
<clobbers target="cordova.plugins.StripeTapToPay" />
</js-module>
<platform name="android">
<config-file target="AndroidManifest.xml" parent="application">
<uses-permission android:name="android.permission.NFC"/>
<uses-permission android:name="android.permission.INTERNET"/>
</config-file>
<source-file src="src/android/StripeTapToPayPlugin.java" target-dir="src/com/example/stripe" />
<framework src="com.stripe:stripe-terminal:2.22.0" />
<framework src="com.squareup.okhttp3:okhttp:4.9.1" />
<framework src="org.json:json:20210307" />
</platform>
</plugin>
—
#### B. Create Folder Structure and Files
mkdir -p www
mkdir -p src/android/com/example/stripe
touch www/StripeTapToPay.js
touch src/android/com/example/stripe/StripeTapToPayPlugin.java
—
#### C. Paste Content
– Paste your **Java plugin logic** into `src/android/com/example/stripe/StripeTapToPayPlugin.java` (from the file I created for you above).
– Paste this into `www/StripeTapToPay.js`:
var exec = require('cordova/exec');
module.exports = {
startTapToPay: function (opts, success, error) {
exec(success, error, 'StripeTapToPayPlugin', 'startTapToPay', [opts]);
},
createPaymentIntent: function (amount, currency, success, error) {
exec(success, error, 'StripeTapToPayPlugin', 'createPaymentIntent', [amount, currency]);
},
collectPayment: function (success, error) {
exec(success, error, 'StripeTapToPayPlugin', 'collectPayment', []);
},
processPayment: function (success, error) {
exec(success, error, 'StripeTapToPayPlugin', 'processPayment', []);
}
};
—
### ✅ 3. Add Plugin to Cordova Project
From your Cordova app project folder:
cordova plugin add ../cordova-plugin-stripe-tap-to-pay
> Adjust the path depending on where your plugin folder is located.
—
### ✅ 4. Build Android
cordova platform add android
cordova build android
—
### ✅ 5. Test It
Make sure you:
– Have an NFC-enabled Android device.
– Set up your backend endpoints.
– Enable Tap to Pay in your Stripe Dashboard → Terminal → Locations.
—
Would you like me to zip this plugin for download or share a GitHub repo version?
What is this
—
### 🧠 Why It’s Needed
When using **Tap to Pay**, your Android device acts like a hardware terminal. So, Stripe requires you to:
1. Create a **Location** in your Stripe Dashboard.
2. Generate a **Reader Registration Code** for that location.
3. Use that code in your app (via the Stripe SDK) to **register the Android device** as a reader.
This process securely ties the device to your Stripe account and to a specific physical location.
—
### 🛠 How to Generate It
1. Go to your [Stripe Dashboard → Terminal → Locations](https://dashboard.stripe.com/test/terminal/locations).
2. Select or create a **Location**.
3. Click on **“Register new reader”**.
4. Choose **“Tap to Pay on Android”**.
5. You’ll get a **registration code** like: `rg_AbCd1234…`
6. This code is valid for a short time (typically 5 minutes).
—
### 🔐 How to Use It (in SDK)
In your Cordova plugin (or native Android code), after initializing Stripe Terminal, you would register the reader like:
terminal.registerReader(
new RegisterReaderParameters.Builder()
.setRegistrationCode("rg_AbCd1234...")
.setLabel("My Android Phone")
.setLocation("loc_123456...")
.build(),
new ReaderCallback() {
@Override
public void onSuccess(Reader registeredReader) {
Log.d("Stripe", "Reader registered: " + registeredReader.getSerialNumber());
}
@Override
public void onFailure(TerminalException e) {
Log.e("Stripe", "Failed to register reader: " + e.getErrorMessage());
}
}
);
⚠️ This `registerReader` step should only be run **once per device**, or if you're re-registering it under a new Stripe Location.
—
### ✅ Summary
| Term | Meaning |
|——|———|
| **Registration Code** | Temporary code from Stripe used to register the Android device as a Tap to Pay reader. |
| **When to Use** | During first setup or re-registration of the Android device. |
| **How to Get** | Stripe Dashboard → Terminal → Locations → Register Reader. |
| **How to Use** | Pass it to the Stripe SDK in the `registerReader()` call. |
—
Would you like me to add this step to your Cordova plugin so you can enter the code via your app UI and register the device automatically?
—
### ✅ Updated PHP Code
<?php
$comment_author = get_comment_author();
$comment_author_email = get_comment_author_email();
$user = get_user_by('email', $comment_author_email);
if ($user && !empty($user->first_name)) {
$display_name = esc_html($user->first_name);
} else {
$display_name = get_comment_author_link(); // Fallback to default name/link
}
printf( __( '%s', 'flatsome' ), sprintf( '<cite class="strong fn">%s</cite>', $display_name ) );
?>
—
### 🧠 Explanation
– `get_user_by('email', get_comment_author_email())`: retrieves the user object if the comment was made by a registered user.
– Checks if `first_name` is set.
– If so, displays the **first name** (sanitized).
– If not, falls back to the **comment author link** as usual.
—
Let me know if you want to:
– Show **first name + last initial**, or
– Link the first name to the user profile.
—
### ✅ Goal Recap:
– Keep the layout clean and consistent when no products are found.
– Ensure the `flatsome_blog_posts` or any other shortcode only renders **inside** the “ area.
– Avoid layout breaking due to placement of the `echo do_shortcode(…)`.
—
### 🔧 Fix the Placement and Conditional Logic
Move the **category blog section** and `flatsome_blog_caursel` logic **into the correct column** and **within the no-products condition block** if that's your goal.
—
### ✅ Recommended Updated Structure (Edited Section)
Replace:
} else {
/**
* Hook: woocommerce_no_products_found.
*
* @hooked wc_no_products_found - 10
*/
do_action( 'woocommerce_no_products_found' );
}
?>
<?php
/**
* Hook: flatsome_products_after.
*
* @hooked flatsome_products_footer_content - 10
*/
do_action( 'flatsome_products_after' );
With:
} else {
/**
* Hook: woocommerce_no_products_found.
*
* @hooked wc_no_products_found - 10
*/
do_action( 'woocommerce_no_products_found' );
// Optional: Show blog posts or other content when no products are found
$category = get_queried_object();
if ($category && $category->parent == 0) {
echo '<div>';
echo '<h3 class="blog-bottom-title">' . __('Blog van De Professor','flatsome') . '</h3>';
echo do_shortcode('[flatsome_blog_posts cat_id="1126" blog_info="yes" pagination="No" searchbox="No" category_tabs="No" columns="3" limit="3"]');
echo '</div>';
} elseif ($category && $category->parent > 0) {
echo do_shortcode('[flatsome_blog_caursel active="yes" limit="12"]');
}
}
?>
<?php
do_action( 'flatsome_products_after' );
—
### 🧪 Result
– If products are found → the normal product loop runs.
– If no products are found → your blog or category carousel shortcodes render **cleanly inside the product content area**.
—
Let me know if you'd like to conditionally show different shortcodes based on **search results** vs **empty categories**, or limit to mobile/desktop, etc.
—
### ✅ Goal
– `col large-9` should **only contain** product grid (or empty if products exist).
– Shortcodes like blog posts or carousels should appear **after the grid**, outside the product content column, but **only if no products are found**.
—
### ✅ Updated Code
Find this part:
</div> <!-- closes <div class="col large-9"> -->
And just **after that**, add:
<?php
if ( ! woocommerce_product_loop() ) {
$category = get_queried_object();
echo '<div class="no-products-alt-content">';
if ($category && $category->parent == 0) {
echo '<h3 class="blog-bottom-title">' . __('Blog van De Professor', 'flatsome') . '</h3>';
echo do_shortcode('[flatsome_blog_posts cat_id="1126" blog_info="yes" pagination="No" searchbox="No" category_tabs="No" columns="3" limit="3"]');
}
if ($category && $category->parent > 0) {
echo do_shortcode('[flatsome_blog_caursel active="yes" limit="12"]');
}
echo '</div>';
}
?>
—
### 🧠 Summary
| Section | Content |
|——–|———|
| `.col.large-9` | Only shows product loop (or nothing) |
| **After “** | If no products, shows blog or carousel shortcode |
| Conditional logic | Based on category parent (root or subcategory) |
—
### 🪄 Optional: Add Styling
In your custom CSS:
.no-products-alt-content {
padding: 40px 0;
text-align: center;
}
—
Let me know if you want different shortcodes based on **search vs category**, or to show a custom message like “No products found, but you might like this blog…”
—
### ✅ Updated Code
Replace this block:
<?php
$category = get_queried_object();
if ($category && $category->parent == 0) {
?>
<div>
<h3 class="blog-bottom-title"><?php echo __('Blog van De Professor','flatsome') ?></h3>
<?php echo do_shortcode('[flatsome_blog_posts cat_id="1126" blog_info="yes" pagination="No" searchbox="No" category_tabs="No" columns="3" limit="3"]'); ?>
</div>
<?php } ?>
With this updated version that checks for search:
<?php
if ( ! is_search() ) {
$category = get_queried_object();
if ( $category && $category->parent == 0 ) {
?>
<div>
<h3 class="blog-bottom-title"><?php echo __('Blog van De Professor','flatsome') ?></h3>
<?php echo do_shortcode('[flatsome_blog_posts cat_id="1126" blog_info="yes" pagination="No" searchbox="No" category_tabs="No" columns="3" limit="3"]'); ?>
</div>
<?php
}
}
?>
And update the carousel block too:
<?php
if ( ! is_search() && $category && $category->parent > 0 ) {
echo do_shortcode('[flatsome_blog_caursel active="yes" limit="12"]');
}
?>
—
### ✅ Result
– On **product category pages**, shortcodes render as usual.
– On **search result pages**, both blog blocks are completely **hidden**.
Let me know if you'd like to show **alternate content for search pages** instead.