Shoppinggate adds its own APIs on top of the standard Mini App ones — orders, payment, refunds, support tickets and the app language. A page inside a web-view calls them with ft.callNativeAPI, through the JSSDK.
Registering a name in SGConf.js is what creates ft.<name> on the mini-app runtime, for a native page to call. Your page reaches the host through ft.callNativeAPI instead, which does not read that table.
orderPayment, getUserOrders, createTicket, …getUserInfo, getSetting, authorize, showModal, … called by your shellSo a wrap that takes payment and opens tickets ships SGConf.js with an empty extApi array and leaves it that way — see the wrapper.
ft.callNativeAPI comes from the JSSDK, so your page needs it before it can reach any host function. One file, no build step.
<!-- Load it in the TOP-LEVEL document of your page. --> <script src="https://shoppinggate-public.s3.ap-south-1.amazonaws.com/webview-sdk/sg-jssdk-1.4.20.js"></script>
It must load in the top-level document, and ft.callNativeAPI needs 1.4.1 or later — both covered in Overview & SDK.
Every API your Mini App can use is listed in the Shoppinggate developer portal. Public APIs are available to every Mini App. Private APIs must be requested first and approved by an admin before your Mini App can call them.
In the developer portal, open:
Mini-Program → your Mini App → Information Details → App Binding
→ API Configuration
API Configuration shows two lists:
Each row has the same columns:
ft.callNativeAPIuser api, order apis, tickets apisPublic or Private
Click Details on the API in the Restricted API list. In API use application, write why your Mini App needs it, then click Application use.

Once an admin approves the request, the API moves to the Public API list. Its Usable Range still says Private — that is expected. It is now available to your Mini App, and your page can call it.

The portal gives you the name and a one-line description only. The parameters and the response for each API are in the API reference below. The extended APIs at the time of writing:
'en' or 'ar'. The one extended API that takes no miniappId.id to createTicket.One call for all of them: the API name first, the parameters second, and a single callback third. There is no success/fail split — everything the host says arrives in that one callback, which is why the result field in Step 4 is what tells you the outcome.
// Web page — one callback, OUTSIDE the argument object. No success/fail split.
ft.callNativeAPI("orderPayment", {
orderInfo: { /* … */ },
idempotencyKey: "…",
miniappId: MINI_APP_ID,
}, function (res) {
/* both outcomes arrive here */
});miniappId comes fromEvery API except getAppLang takes it, and a placeholder is rejected before anything else about the call is looked at. Read it from ft.getAppletInfo, which the JSSDK exposes to your page.
// Read it at runtime with ft.getAppletInfo, and keep a literal only as the fallback for
// running the page outside a web-view.
var MINI_APP_ID = "<your mini-app id>";
if (typeof ft !== "undefined" && typeof ft.getAppletInfo === "function") {
ft.getAppletInfo({
success: function (res) {
var info = (res && (res.data || res)) || {};
var id = info.appletId || info.appId || info.id;
if (id) MINI_APP_ID = id;
},
fail: function () { /* keep the fallback */ },
});
}Two identifiers are easy to confuse. miniappId on these calls is the mini-app id; the appid in fide.project.config.json belongs to the project.
Extended APIs answer with a result field, not the errMsg string the built-in APIs use:
{ result: 'success', /* ...payload... */ }
{ result: 'failure', message: 'why it failed' }Check result for the outcome and read message when it failed. The payload key differs per API — orders, order, refunds, tickets, issues, refundInfo — listed in the reference below.
result: 'failure'. Check result before reading anything else, and when you log a failure print the host's response as it arrived rather than a field you picked out of it. Worked example in testing.getUserInfo ships with the mini-app runtime rather than being an extended API, so it is not in the portal list above and not something ft.callNativeAPI reaches. Your shell calls it and relays the answer to your page.
Two things shape how you use it, and both differ from every API above:
getLocation is satisfied, so read it once and cache the result — a page that reads it per screen asks repeatedly.The relay, the cache and the invalidation that keeps it honest are in Permissions; the channel itself is in What a page can reach.
Every API with its parameters, an example call and what it returns. The parameters and responses are the same contract the native page documents, rendered from one shared definition; the examples are the web-view call.
Returns the host app's current language — 'en' or 'ar'. The one extended API that takes no miniappId.
None.
ft.callNativeAPI("getAppLang", {}, function (res) {
// The one host function that takes no miniappId.
if (res.result === "success") console.log(res.language); // 'en' | 'ar'
});{ result: 'success', language: 'en' } // or 'ar'
{ result: 'failure', language: null }Creates the order and opens the payment sheet. See the full page: orderPayment.
vendor_order_ref. All fields are on the orderPayment page.ft.getAccountInfoSync(); in a web view, ft.getAppletInfo.ft.callNativeAPI("orderPayment", {
orderInfo: {
vendor_order_ref: "1234567orderid",
items: [{ sku: "d-1", name: "Dates", qty: 1,
unit_price: "98.00", total_amount: "98.00" }],
subtotal: "98.00",
delivery_fee: "12.00",
tax_amount: "16.50",
total_amount: "126.50", // subtotal + delivery_fee + tax_amount must balance
currency: "SAR",
order_type: "delivery",
},
// Stable per order. The same key on a retry is what stops a double charge.
idempotencyKey: "1234567orderid",
miniappId: MINI_APP_ID,
}, function (res) {
if (res.result !== "success") return console.log("declined:", res.message);
console.log(res.status, res.order_id);
});// failure
{ result: 'failure', message }
// success
{ result: 'success', status, order_id, message }status is the outcome of the payment attempt, not an order status:
'already_paid' — paid in an earlier session, nothing left to charge'created' — the order exists, the user backed out without paying⚠️ order_id here is the super app's uuid, not your reference. This is the one API that returns the uuid. Your own reference goes out in orderInfo.vendor_order_ref, and that is what deep links come back on.
Returns one order of the current user.
ft.getAccountInfoSync(); in a web view, ft.getAppletInfo.ft.callNativeAPI("getUserOrder", {
orderId: "1234567orderid",
miniappId: MINI_APP_ID,
}, function (res) {
if (res.result !== "success") return console.log(res.message);
console.log(res.order);
});{ result, order: { /* order */ } }Returns the current user's orders in this Mini App, one page at a time.
1.ft.getAccountInfoSync(); in a web view, ft.getAppletInfo.ft.callNativeAPI("getUserOrders", {
page: 1,
perPage: 10,
miniappId: MINI_APP_ID,
}, function (res) {
if (res.result !== "success") return console.log(res.message);
console.log(res.orders);
});{ result, total, page, per_page, orders: [ /* order */ ] }orders is an order object.Requests a refund for an order — the full amount or part of it.
ft.getAccountInfoSync(); in a web view, ft.getAppletInfo.ft.callNativeAPI("refundOrder", {
orderId: "1234567orderid",
amount: 50.0,
reason: "Item arrived damaged",
miniappId: MINI_APP_ID,
}, function (res) {
if (res.result !== "success") return console.log(res.message);
console.log(res.refund);
});{ result, refundInfo: { /* refund */ } }Returns the refunds of one order.
ft.getAccountInfoSync(); in a web view, ft.getAppletInfo.ft.callNativeAPI("getOrderRefunds", {
orderId: "1234567orderid",
miniappId: MINI_APP_ID,
}, function (res) {
if (res.result !== "success") return console.log(res.message);
console.log(res.refunds);
});{ result, refunds: [ /* refund */ ] }refunds is a refund object.Returns the list of issue types a user can pick when opening a ticket. Pass the chosen id to createTicket.
ft.getAccountInfoSync(); in a web view, ft.getAppletInfo.ft.callNativeAPI("getTicketIssues", {
miniappId: MINI_APP_ID,
}, function (res) {
if (res.result !== "success") return console.log(res.message);
// Each issue has an `id` — that is what createTicket's issueId takes.
console.log(res.issues);
});{ result, issues: [ { id, name, code } ] }code is currently null on every issue. Use id.Opens a support ticket for an order on behalf of the user.
id of an issue from getTicketIssues.ft.chooseImage (res.tempFilePaths[0]), or in a native page ft.chooseMedia (res.tempFiles[0].tempFilePath). See Permissions → pickers.ft.getAccountInfoSync(); in a web view, ft.getAppletInfo.// imageFilePath comes from ft.chooseImage, which your PAGE calls directly —
// no relay needed for this one.
ft.chooseImage({
count: 1,
sizeType: ["compressed"],
sourceType: ["camera", "album"],
success: function (media) {
ft.callNativeAPI("createTicket", {
issueId: 3, // from getTicketIssues
orderId: "1234567orderid",
reason: "The order arrived damaged",
affectedProducts: [],
imageFilePath: media.tempFilePaths[0],
miniappId: MINI_APP_ID,
}, function (res) {
if (res.result !== "success") return console.log(res.message);
console.log(res.ticket);
});
},
});{ result, message, ticket: { /* ticket */ } }Returns the current user's tickets for an order, one page at a time.
ft.getAccountInfoSync(); in a web view, ft.getAppletInfo.1.10.ft.callNativeAPI("getUserTickets", {
orderId: "1234567orderid",
page: 1,
limit: 10,
miniappId: MINI_APP_ID,
}, function (res) {
if (res.result !== "success") return console.log(res.message);
console.log(res.tickets);
});{ result, total, page, limit, tickets: [ /* ticket */ ] }tickets is a ticket object.Returned by getUserOrder (as order) and getUserOrders (in orders).
1234567orderid — not the super app's uuid.'created' means unpaid.orderInfo.items when you created the order with orderPayment — so the shape is yours. For example name, qty, unit_price, sku, booking_date; there can be more fields or fewer.SAR.getUserOrder; a plain string in getUserOrders.Returned by refundOrder (as refundInfo) and getOrderRefunds (in refunds).
{ id, name }.Returned by createTicket (as ticket) and getUserTickets (in tickets).
{ id, name }. Open statuses are open, pending and in_progress.{ name }.getUserTickets.createTicket takes an imageFilePath, and your page can produce one: ft.chooseImage is reachable directly, and res.tempFilePaths[0] is exactly what the field wants. No relay involved — the createTicket example above shows the whole flow.