Language guides
WordPress without a plugin
Sell from any WordPress or WooCommerce site using core functions: a payment shortcode, a return check and a signed REST webhook.
11 min read
What you will build
No plugin is required. Two snippets — one in your child theme's functions.php and one MU plugin — give you a payment button, a confirmation page and a webhook endpoint at /wp-json/takaflow/v1/webhook.
Use it on a custom page, or hook the same calls into WooCommerce so orders complete automatically.
- WordPress 6.x with HTTPS and the REST API enabled.
- An API key and webhook secret from Developers in your workspace.
- Edit access to wp-config.php and wp-content/mu-plugins/.
1. Store your keys
// add above "That's all, stop editing"
define("TAKAFLOW_BASE", "https://your-domain.com");
define("TAKAFLOW_API_KEY", "tf_live_xxxxxxxxxxxxxxxx");
define("TAKAFLOW_WEBHOOK_SECRET", "whsec_xxxxxxxxxxxxxxxx");2. A payment button shortcode
This creates a charge and renders a button. Drop [takaflow_pay amount="1250" label="Pay now"] on any page or post. Store the invoice number against the order so the return page and webhook can find it.
function takaflow_pay_button($atts) {
$atts = shortcode_atts(["amount" => "0", "label" => "Pay now"], $atts);
$order_id = "WP-" . time();
$res = wp_remote_post(TAKAFLOW_BASE . "/api/charge", [
"timeout" => 20,
"headers" => [
"RT-TAKAFLOW-API-KEY" => TAKAFLOW_API_KEY,
"Content-Type" => "application/json",
],
"body" => wp_json_encode([
"full_name" => wp_get_current_user()->display_name,
"email" => wp_get_current_user()->user_email,
"amount" => (float) $atts["amount"],
"description" => "WordPress order " . $order_id,
"metadata" => ["order_id" => $order_id],
"redirect_url" => home_url("/payment-received/"),
]),
]);
if (is_wp_error($res)) return "Payment is unavailable right now.";
$charge = json_decode(wp_remote_retrieve_body($res), true);
if (empty($charge["payment_url"])) return "Payment is unavailable right now.";
update_option("takaflow_" . $charge["invoice_number"], $order_id);
return '<a class="button" href="' . esc_url($charge["payment_url"]) . '">'
. esc_html($atts["label"]) . " " . esc_html($charge["amount"]) . " BDT</a>";
}
add_shortcode("takaflow_pay", "takaflow_pay_button");3. Confirm on the return page
Create a page at /payment-received/ and use a block template or a snippet that reads invoice_id from the query string, verifies it, and only then shows the receipt.
$invoice = sanitize_text_field($_GET["invoice_id"] ?? "");
if ($invoice === "") {
echo "Missing invoice reference.";
return;
}
$res = wp_remote_post(TAKAFLOW_BASE . "/api/verify-payment", [
"timeout" => 20,
"headers" => [
"RT-TAKAFLOW-API-KEY" => TAKAFLOW_API_KEY,
"Content-Type" => "application/json",
],
"body" => wp_json_encode(["invoice_id" => $invoice]),
]);
$state = json_decode(wp_remote_retrieve_body($res), true);
if (($state["payment_status"] ?? "") === "COMPLETED") {
$order_id = $state["metadata"]["order_id"] ?? "";
update_option("takaflow_order_" . $order_id, "paid");
echo "Payment received. Thank you!";
} else {
echo "We are still verifying this payment.";
}4. Webhook endpoint
Drop this file in wp-content/mu-plugins/takaflow-webhook.php. It registers a REST route and verifies the signature on the raw request body before marking anything paid.
<?php
add_action("rest_api_init", function () {
register_rest_route("takaflow/v1", "/webhook", [
"methods" => "POST",
"callback" => "__return_null",
"permission_callback" => "__return_true",
]);
});
add_filter("rest_pre_dispatch", function ($result, $server, $request) {
if ($request->get_route() !== "/takaflow/v1/webhook") return $result;
$raw = $request->get_body();
$expected = "sha256=" . hash_hmac("sha256", $raw, TAKAFLOW_WEBHOOK_SECRET);
$received = $request->get_header("x-takaflow-signature") ?? "";
if (!hash_equals($expected, $received)) {
return new WP_Error("takaflow_signature", "Invalid signature", ["status" => 401]);
}
$event = json_decode($raw, true);
if (($event["event"] ?? "") === "payment.completed") {
$order_id = $event["metadata"]["order_id"] ?? "";
update_option("takaflow_order_" . $order_id, "paid");
// send email, reduce stock, grant access ...
}
return new WP_REST_Response(["ok" => true], 200);
}, 10, 3);The webhook URL is https://your-site.com/wp-json/takaflow/v1/webhook. If your host blocks the REST API, add the route through a normal admin-post.php action instead, but keep the same signature check.
5. WooCommerce notes
- Create the charge on woocommerce_checkout_order_processed and save invoice_number in the order meta.
- On payment.completed, load the order with wc_get_order(metadata.order_id) and call $order->payment_complete($trx_id).
- Keep WooCommerce's own gateway list untouched; the hosted checkout replaces the payment step.
- Cache-bust the payment button page if you use a full-page cache — each click must create a fresh charge.
6. Go-live checklist
- Constants live in wp-config.php, never in the theme repository.
- Webhook URL answers 200 to a test delivery in Developers → Webhooks.
- Return page verifies with the API instead of trusting the query string.
- Order updates are idempotent on X-TakaFlow-Delivery.
- A small live payment from a second phone completes the order end to end.