@grabjs/superapp-sdk 2.0.0-beta.46 → 2.0.0-beta.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/SKILL.md +209 -286
package/package.json
CHANGED
package/skills/SKILL.md
CHANGED
|
@@ -101,17 +101,88 @@ The SDK uses HTTP-style status codes for all responses:
|
|
|
101
101
|
| `500` | Internal Error | Unexpected SDK error |
|
|
102
102
|
| `501` | Not Implemented | Outside Grab SuperApp environment |
|
|
103
103
|
|
|
104
|
-
###
|
|
104
|
+
### Type Guards
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
Type guards narrow the response type so TypeScript knows which fields are available:
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
| Guard | Matches |
|
|
109
|
+
| --------------------------------- | ---------------------------------------- |
|
|
110
|
+
| `isSuccess(r)` | `200`, `204` |
|
|
111
|
+
| `isOk(r)` | `200` |
|
|
112
|
+
| `isNoContent(r)` | `204` |
|
|
113
|
+
| `isRedirection(r)` / `isFound(r)` | `302` |
|
|
114
|
+
| `isClientError(r)` | `400`, `401`, `403`, `404`, `424`, `426` |
|
|
115
|
+
| `isServerError(r)` | `500`, `501` |
|
|
116
|
+
| `isError(r)` | any 4xx or 5xx |
|
|
111
117
|
|
|
112
|
-
|
|
118
|
+
```typescript
|
|
119
|
+
import { isSuccess, isOk, isNoContent, isError } from '@grabjs/superapp-sdk';
|
|
113
120
|
|
|
114
|
-
|
|
121
|
+
if (isSuccess(response)) {
|
|
122
|
+
// narrow further if needed
|
|
123
|
+
if (isOk(response)) console.log(response.result);
|
|
124
|
+
if (isNoContent(response)) console.log('done, no data');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (isError(response)) {
|
|
128
|
+
// response.error: string is guaranteed here
|
|
129
|
+
console.error(response.error);
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Streams
|
|
134
|
+
|
|
135
|
+
Some modules provide streaming methods for real-time data (location updates, media events). Subscribe to receive values over time:
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
import { LocationModule, isSuccess } from '@grabjs/superapp-sdk';
|
|
139
|
+
|
|
140
|
+
const location = new LocationModule();
|
|
141
|
+
|
|
142
|
+
const subscription = location.observeLocationChange().subscribe({
|
|
143
|
+
next: (response) => {
|
|
144
|
+
if (isSuccess(response)) console.log(response.result);
|
|
145
|
+
},
|
|
146
|
+
complete: () => console.log('Stream ended'),
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Always unsubscribe when done to conserve battery and resources
|
|
150
|
+
subscription.unsubscribe();
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
You can also `await` a stream method directly to get its first value.
|
|
154
|
+
|
|
155
|
+
### Scopes and Permissions
|
|
156
|
+
|
|
157
|
+
The SDK categorizes permissions into two distinct types based on their execution context:
|
|
158
|
+
|
|
159
|
+
#### Permission Types
|
|
160
|
+
|
|
161
|
+
- **Backend Scopes** (`openid`, `profile.read`, `phone`)
|
|
162
|
+
- **Purpose**: Access protected resources and user data via your server.
|
|
163
|
+
- **Flow**: Requires a backend token exchange after authorization to retrieve data.
|
|
164
|
+
- **Mobile Scopes** (`mobile.geolocation`, `mobile.checkout`)
|
|
165
|
+
- **Purpose**: Access native device capabilities directly within the MiniApp.
|
|
166
|
+
- **Flow**: Grants in-app permission immediately; no backend exchange is necessary.
|
|
167
|
+
|
|
168
|
+
#### Authorization Patterns
|
|
169
|
+
|
|
170
|
+
When designing your MiniApp, you can choose between two common patterns for requesting scopes:
|
|
171
|
+
|
|
172
|
+
- **Upfront Authorization**
|
|
173
|
+
- Request all required scopes during app initialisation, typically alongside backend sign-in.
|
|
174
|
+
- _Best for_: Core permissions essential for the app to function.
|
|
175
|
+
- **Deferred Authorization**
|
|
176
|
+
- Request scopes only when the user triggers a specific feature that requires them.
|
|
177
|
+
- _Best for_: Optional permissions (e.g., location) to improve user experience and build trust.
|
|
178
|
+
|
|
179
|
+
#### Permission Verification Strategies
|
|
180
|
+
|
|
181
|
+
You can verify permissions either proactively before calling a method, or reactively by handling errors.
|
|
182
|
+
|
|
183
|
+
##### Proactive Checking
|
|
184
|
+
|
|
185
|
+
Proactively verify if the current session has the necessary permissions for a method using `ScopeModule.hasAccessTo()`. This is recommended before calling gated methods, as users can revoke permissions at any time via the Grab app settings.
|
|
115
186
|
|
|
116
187
|
```typescript
|
|
117
188
|
const scope = new ScopeModule();
|
|
@@ -123,6 +194,14 @@ if (isSuccess(hasAccess) && hasAccess.result) {
|
|
|
123
194
|
}
|
|
124
195
|
```
|
|
125
196
|
|
|
197
|
+
##### Reactive Checking (Handling 403 Forbidden)
|
|
198
|
+
|
|
199
|
+
Methods tagged with `@requiredOAuthScope` require specific permissions. If the user hasn't granted the required scope, the method returns `403`. You must request authorization and reload scopes before retrying:
|
|
200
|
+
|
|
201
|
+
1. Call `IdentityModule.authorize()` to request the scope.
|
|
202
|
+
2. Call `ScopeModule.reloadScopes()` to refresh the SDK's internal permission state.
|
|
203
|
+
3. Retry the original method call.
|
|
204
|
+
|
|
126
205
|
```typescript
|
|
127
206
|
import {
|
|
128
207
|
LocationModule,
|
|
@@ -161,99 +240,71 @@ if (isError(response) && response.status_code === 403) {
|
|
|
161
240
|
}
|
|
162
241
|
```
|
|
163
242
|
|
|
164
|
-
### Type Guards
|
|
165
|
-
|
|
166
|
-
Type guards narrow the response type so TypeScript knows which fields are available:
|
|
167
|
-
|
|
168
|
-
| Guard | Matches |
|
|
169
|
-
| --------------------------------- | ---------------------------------------- |
|
|
170
|
-
| `isSuccess(r)` | `200`, `204` |
|
|
171
|
-
| `isOk(r)` | `200` |
|
|
172
|
-
| `isNoContent(r)` | `204` |
|
|
173
|
-
| `isRedirection(r)` / `isFound(r)` | `302` |
|
|
174
|
-
| `isClientError(r)` | `400`, `401`, `403`, `404`, `424`, `426` |
|
|
175
|
-
| `isServerError(r)` | `500`, `501` |
|
|
176
|
-
| `isError(r)` | any 4xx or 5xx |
|
|
177
|
-
|
|
178
|
-
```typescript
|
|
179
|
-
import { isSuccess, isOk, isNoContent, isError } from '@grabjs/superapp-sdk';
|
|
180
|
-
|
|
181
|
-
if (isSuccess(response)) {
|
|
182
|
-
// narrow further if needed
|
|
183
|
-
if (isOk(response)) console.log(response.result);
|
|
184
|
-
if (isNoContent(response)) console.log('done, no data');
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (isError(response)) {
|
|
188
|
-
// response.error: string is guaranteed here
|
|
189
|
-
console.error(response.error);
|
|
190
|
-
}
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
### Streams
|
|
194
|
-
|
|
195
|
-
Some modules provide streaming methods for real-time data (location updates, media events). Subscribe to receive values over time:
|
|
196
|
-
|
|
197
|
-
```typescript
|
|
198
|
-
import { LocationModule, isSuccess } from '@grabjs/superapp-sdk';
|
|
199
|
-
|
|
200
|
-
const location = new LocationModule();
|
|
201
|
-
|
|
202
|
-
const subscription = location.observeLocationChange().subscribe({
|
|
203
|
-
next: (response) => {
|
|
204
|
-
if (isSuccess(response)) console.log(response.result);
|
|
205
|
-
},
|
|
206
|
-
complete: () => console.log('Stream ended'),
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
// Always unsubscribe when done to conserve battery and resources
|
|
210
|
-
subscription.unsubscribe();
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
You can also `await` a stream method directly to get its first value.
|
|
214
|
-
|
|
215
243
|
|
|
216
244
|
## Integration Guide
|
|
217
245
|
|
|
218
246
|
This guide covers the recommended setup for a MiniApp entry point — loading scopes, configuring the container UI, signalling readiness, and handling permissions.
|
|
219
247
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
The [demo](https://github.com/grab/superapp-sdk/tree/master/demo) folder contains two complete MiniApp samples demonstrating these integration patterns in action — one using CDN (vanilla HTML/JS) and one using React. Both implement the same user flow: OAuth authorization, user profile display, deferred location permissions, and checkout payment.
|
|
248
|
+
> **Note:** The [demo](https://github.com/grab/superapp-sdk/tree/master/demo) folder contains two complete MiniApp samples demonstrating these integration patterns in action — one using CDN (vanilla HTML/JS) and one using React. Both implement the same user flow: OAuth authorization, user profile display, deferred location permissions, and checkout payment.
|
|
223
249
|
|
|
224
|
-
###
|
|
250
|
+
### Initialization
|
|
225
251
|
|
|
226
|
-
|
|
252
|
+
Follow these steps when your MiniApp launches to configure the container, authenticate the user, and track the entry event.
|
|
227
253
|
|
|
228
254
|
```typescript
|
|
229
|
-
import {
|
|
255
|
+
import {
|
|
256
|
+
ContainerModule,
|
|
257
|
+
ScopeModule,
|
|
258
|
+
ContainerAnalyticsEventState,
|
|
259
|
+
isSuccess,
|
|
260
|
+
} from '@grabjs/superapp-sdk';
|
|
230
261
|
|
|
231
262
|
const container = new ContainerModule();
|
|
232
263
|
const scope = new ScopeModule();
|
|
233
264
|
|
|
234
265
|
async function init() {
|
|
235
|
-
// 1.
|
|
236
|
-
await
|
|
266
|
+
// 1. Verify the environment
|
|
267
|
+
const connection = await container.isConnected();
|
|
268
|
+
if (!isSuccess(connection) || !connection.result?.connected) {
|
|
269
|
+
// Handle case where app is opened outside Grab SuperApp
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
237
272
|
|
|
238
273
|
// 2. Configure the container UI
|
|
239
274
|
await container.setTitle('My MiniApp');
|
|
240
275
|
await container.setBackgroundColor('#FFFFFF');
|
|
241
276
|
await container.hideBackButton();
|
|
277
|
+
await container.hideRefreshButton();
|
|
242
278
|
|
|
243
279
|
// 3. Dismiss the native loader
|
|
244
280
|
await container.hideLoader();
|
|
281
|
+
|
|
282
|
+
// 4. Track app launch
|
|
283
|
+
await container.sendAnalyticsEvent({
|
|
284
|
+
state: ContainerAnalyticsEventState.HOMEPAGE,
|
|
285
|
+
name: 'DEFAULT',
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// 5. Authenticate the user
|
|
289
|
+
// (Implementation detailed in the Authentication section below)
|
|
290
|
+
await signIn();
|
|
291
|
+
|
|
292
|
+
// 6. Load permission scopes — always do this before making module calls
|
|
293
|
+
await scope.reloadScopes();
|
|
245
294
|
}
|
|
246
295
|
|
|
247
296
|
init();
|
|
248
297
|
```
|
|
249
298
|
|
|
250
|
-
|
|
299
|
+
### Authentication
|
|
251
300
|
|
|
252
|
-
|
|
301
|
+
Trigger `IdentityModule.authorize()` to start the authorization process and request user permissions.
|
|
253
302
|
|
|
254
|
-
|
|
303
|
+
Once the user consents, retrieve the authorization artifacts (which include the `code`, `state`, `nonce`, and PKCE `codeVerifier`) via `IdentityModule.getAuthorizationArtifacts()`.
|
|
255
304
|
|
|
256
|
-
|
|
305
|
+
Forward these artifacts to your backend to exchange the token, validate the `id_token`, fetch user info, and establish the user's session.
|
|
306
|
+
|
|
307
|
+
After the session is established, call `IdentityModule.clearAuthorizationArtifacts()` and `ScopeModule.reloadScopes()` so your MiniApp can begin using the newly granted permissions.
|
|
257
308
|
|
|
258
309
|
```typescript
|
|
259
310
|
import { IdentityModule, ScopeModule, isSuccess, isError } from '@grabjs/superapp-sdk';
|
|
@@ -261,45 +312,79 @@ import { IdentityModule, ScopeModule, isSuccess, isError } from '@grabjs/superap
|
|
|
261
312
|
const identity = new IdentityModule();
|
|
262
313
|
const scope = new ScopeModule();
|
|
263
314
|
|
|
264
|
-
async function
|
|
315
|
+
async function signIn() {
|
|
265
316
|
const response = await identity.authorize({
|
|
266
317
|
clientId: 'your-client-id',
|
|
267
318
|
redirectUri: 'https://your-miniapp.example.com/callback',
|
|
268
|
-
scope: '
|
|
319
|
+
scope: 'openid profile.read phone mobile.storage',
|
|
269
320
|
environment: 'production',
|
|
321
|
+
responseMode: 'in_place',
|
|
270
322
|
});
|
|
271
323
|
|
|
272
324
|
if (isSuccess(response)) {
|
|
273
|
-
|
|
274
|
-
|
|
325
|
+
if (response.status_code === 200) {
|
|
326
|
+
// 1. Retrieve authorization artifacts
|
|
327
|
+
const artifacts = await identity.getAuthorizationArtifacts();
|
|
328
|
+
if (isSuccess(artifacts)) {
|
|
329
|
+
const { codeVerifier, nonce, redirectUri } = artifacts.result;
|
|
330
|
+
const { code } = response.result;
|
|
331
|
+
|
|
332
|
+
// 2. Send the artifacts to your backend for token exchange (see Backend Token Exchange section below)
|
|
333
|
+
// await myBackend.exchangeTokens({ code, codeVerifier, nonce, redirectUri });
|
|
334
|
+
|
|
335
|
+
// 3. Clear artifacts and reload scopes
|
|
336
|
+
await identity.clearAuthorizationArtifacts();
|
|
337
|
+
await scope.reloadScopes();
|
|
338
|
+
}
|
|
339
|
+
} else if (response.status_code === 204) {
|
|
340
|
+
// User cancelled the authorization flow
|
|
341
|
+
await identity.clearAuthorizationArtifacts();
|
|
342
|
+
}
|
|
275
343
|
} else if (isError(response)) {
|
|
276
344
|
console.error('Authorization failed:', response.error);
|
|
345
|
+
await identity.clearAuthorizationArtifacts();
|
|
277
346
|
}
|
|
278
347
|
}
|
|
279
348
|
```
|
|
280
349
|
|
|
281
|
-
### Navigation
|
|
350
|
+
### Container UI & Navigation
|
|
282
351
|
|
|
283
|
-
|
|
352
|
+
Control the native container's appearance and behavior to match your MiniApp's branding and navigation flow.
|
|
284
353
|
|
|
285
|
-
|
|
354
|
+
#### Title and Background
|
|
355
|
+
|
|
356
|
+
Set the title and background color for the native container.
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
await container.setTitle('My MiniApp');
|
|
360
|
+
await container.setBackgroundColor('#FFFFFF');
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
#### Back and Refresh Buttons
|
|
364
|
+
|
|
365
|
+
Hide these buttons when your MiniApp manages its own navigation or requires a focused, non-refreshable view. Restore them when appropriate.
|
|
286
366
|
|
|
287
367
|
```typescript
|
|
368
|
+
// Hide buttons
|
|
288
369
|
await container.hideBackButton();
|
|
370
|
+
await container.hideRefreshButton();
|
|
289
371
|
|
|
290
|
-
//
|
|
372
|
+
// Restore buttons
|
|
291
373
|
await container.showBackButton();
|
|
374
|
+
await container.showRefreshButton();
|
|
292
375
|
```
|
|
293
376
|
|
|
294
377
|
#### Closing the MiniApp
|
|
295
378
|
|
|
379
|
+
Programmatically close the MiniApp and return the user to the Grab SuperApp.
|
|
380
|
+
|
|
296
381
|
```typescript
|
|
297
382
|
await container.close();
|
|
298
383
|
```
|
|
299
384
|
|
|
300
|
-
|
|
385
|
+
### Opening External Links
|
|
301
386
|
|
|
302
|
-
Use `openExternalLink` to open URLs in the system browser instead of navigating away from the WebView
|
|
387
|
+
Use `ContainerModule.openExternalLink()` to open URLs in the system browser instead of navigating away from the MiniApp WebView.
|
|
303
388
|
|
|
304
389
|
```typescript
|
|
305
390
|
const response = await container.openExternalLink('https://example.com');
|
|
@@ -311,185 +396,47 @@ if (isError(response)) {
|
|
|
311
396
|
|
|
312
397
|
### Analytics Event Tracking
|
|
313
398
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
#### Required Events
|
|
317
|
-
|
|
318
|
-
##### Entry Point
|
|
319
|
-
|
|
320
|
-
###### Initiate action
|
|
321
|
-
|
|
322
|
-
Send the initiate event when users click call-to-action buttons to proceed:
|
|
399
|
+
Track user interactions to monitor performance and conversion. Events are categorised by journey stage using `ContainerAnalyticsEventState`.
|
|
323
400
|
|
|
324
401
|
```typescript
|
|
325
|
-
import {
|
|
326
|
-
ContainerModule,
|
|
327
|
-
ContainerAnalyticsEventState,
|
|
328
|
-
isSuccess,
|
|
329
|
-
isError,
|
|
330
|
-
} from '@grabjs/superapp-sdk';
|
|
331
|
-
|
|
332
|
-
// Event names are plain strings — define your own constants
|
|
333
|
-
const EventName = {
|
|
334
|
-
INITIATE: 'INITIATE',
|
|
335
|
-
TRANSACT: 'TRANSACT',
|
|
336
|
-
};
|
|
402
|
+
import { ContainerModule, ContainerAnalyticsEventState, isSuccess } from '@grabjs/superapp-sdk';
|
|
337
403
|
|
|
338
|
-
const
|
|
404
|
+
const container = new ContainerModule();
|
|
339
405
|
|
|
340
|
-
//
|
|
341
|
-
|
|
406
|
+
// 1. System Event (DEFAULT)
|
|
407
|
+
// Send when a user lands on a key page
|
|
408
|
+
await container.sendAnalyticsEvent({
|
|
342
409
|
state: ContainerAnalyticsEventState.HOMEPAGE,
|
|
343
|
-
name:
|
|
410
|
+
name: 'DEFAULT',
|
|
344
411
|
});
|
|
345
412
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
###### Custom interactions
|
|
354
|
-
|
|
355
|
-
Send custom events for additional interactions like banner clicks, category selections, or search queries. Include the `page` parameter and a descriptive event name:
|
|
356
|
-
|
|
357
|
-
```typescript
|
|
358
|
-
import {
|
|
359
|
-
ContainerModule,
|
|
360
|
-
ContainerAnalyticsEventState,
|
|
361
|
-
isSuccess,
|
|
362
|
-
isError,
|
|
363
|
-
} from '@grabjs/superapp-sdk';
|
|
364
|
-
|
|
365
|
-
const containerModule = new ContainerModule();
|
|
413
|
+
// 2. Named Action (INITIATE / TRANSACT)
|
|
414
|
+
// Send when a user performs a primary action
|
|
415
|
+
await container.sendAnalyticsEvent({
|
|
416
|
+
state: ContainerAnalyticsEventState.HOMEPAGE,
|
|
417
|
+
name: 'INITIATE',
|
|
418
|
+
});
|
|
366
419
|
|
|
367
|
-
//
|
|
368
|
-
|
|
420
|
+
// 3. Custom Interaction
|
|
421
|
+
// Send for specific interactions with additional metadata
|
|
422
|
+
await container.sendAnalyticsEvent({
|
|
369
423
|
state: ContainerAnalyticsEventState.CUSTOM,
|
|
370
424
|
name: 'BANNER_CLICK',
|
|
371
425
|
data: {
|
|
372
426
|
page: 'homepage',
|
|
373
427
|
banner_id: 'promo-summer-2024',
|
|
374
|
-
banner_position: 'top',
|
|
375
428
|
},
|
|
376
429
|
});
|
|
377
|
-
|
|
378
|
-
if (isSuccess(response)) {
|
|
379
|
-
// Event sent successfully
|
|
380
|
-
} else if (isError(response)) {
|
|
381
|
-
console.error(`Failed to send event: ${response.error}`);
|
|
382
|
-
}
|
|
383
430
|
```
|
|
384
431
|
|
|
385
|
-
|
|
432
|
+
#### Journey Stages
|
|
386
433
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
ContainerModule,
|
|
394
|
-
ContainerAnalyticsEventState,
|
|
395
|
-
isSuccess,
|
|
396
|
-
isError,
|
|
397
|
-
} from '@grabjs/superapp-sdk';
|
|
398
|
-
|
|
399
|
-
// Event names are plain strings — define your own constants
|
|
400
|
-
const EventName = {
|
|
401
|
-
INITIATE: 'INITIATE',
|
|
402
|
-
TRANSACT: 'TRANSACT',
|
|
403
|
-
};
|
|
404
|
-
|
|
405
|
-
const containerModule = new ContainerModule();
|
|
406
|
-
|
|
407
|
-
// Send when the user clicks the final confirmation button
|
|
408
|
-
const response = await containerModule.sendAnalyticsEvent({
|
|
409
|
-
state: ContainerAnalyticsEventState.CHECKOUT_PAGE,
|
|
410
|
-
name: EventName.TRANSACT,
|
|
411
|
-
data: {
|
|
412
|
-
transaction_amount: 49.99,
|
|
413
|
-
transaction_currency: 'SGD',
|
|
414
|
-
transaction_id: 'TXN-2024-001234',
|
|
415
|
-
payment_method: 'grabpay',
|
|
416
|
-
item_count: 2,
|
|
417
|
-
},
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
if (isSuccess(response)) {
|
|
421
|
-
// Event sent successfully
|
|
422
|
-
} else if (isError(response)) {
|
|
423
|
-
console.error(`Failed to send event: ${response.error}`);
|
|
424
|
-
}
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
###### Custom interactions
|
|
428
|
-
|
|
429
|
-
Send custom events for checkout interactions like promo code applications or payment method selections:
|
|
430
|
-
|
|
431
|
-
```typescript
|
|
432
|
-
import {
|
|
433
|
-
ContainerModule,
|
|
434
|
-
ContainerAnalyticsEventState,
|
|
435
|
-
isSuccess,
|
|
436
|
-
isError,
|
|
437
|
-
} from '@grabjs/superapp-sdk';
|
|
438
|
-
|
|
439
|
-
const containerModule = new ContainerModule();
|
|
440
|
-
|
|
441
|
-
// Send for custom checkout interactions such as promo code applications
|
|
442
|
-
const response = await containerModule.sendAnalyticsEvent({
|
|
443
|
-
state: ContainerAnalyticsEventState.CUSTOM,
|
|
444
|
-
name: 'PROMO_CODE_APPLIED',
|
|
445
|
-
data: {
|
|
446
|
-
page: 'checkout',
|
|
447
|
-
promo_code: 'SAVE20',
|
|
448
|
-
discount_amount: 10.0,
|
|
449
|
-
discount_type: 'percentage',
|
|
450
|
-
},
|
|
451
|
-
});
|
|
452
|
-
|
|
453
|
-
if (isSuccess(response)) {
|
|
454
|
-
// Event sent successfully
|
|
455
|
-
} else if (isError(response)) {
|
|
456
|
-
console.error(`Failed to send event: ${response.error}`);
|
|
457
|
-
}
|
|
458
|
-
```
|
|
459
|
-
|
|
460
|
-
##### Completion Point
|
|
461
|
-
|
|
462
|
-
###### Follow-up actions
|
|
463
|
-
|
|
464
|
-
Send custom events for post-transaction actions like downloading receipts or tracking orders:
|
|
465
|
-
|
|
466
|
-
```typescript
|
|
467
|
-
import {
|
|
468
|
-
ContainerModule,
|
|
469
|
-
ContainerAnalyticsEventState,
|
|
470
|
-
isSuccess,
|
|
471
|
-
isError,
|
|
472
|
-
} from '@grabjs/superapp-sdk';
|
|
473
|
-
|
|
474
|
-
const containerModule = new ContainerModule();
|
|
475
|
-
|
|
476
|
-
// Send for post-transaction actions such as downloading a receipt
|
|
477
|
-
const response = await containerModule.sendAnalyticsEvent({
|
|
478
|
-
state: ContainerAnalyticsEventState.CUSTOM,
|
|
479
|
-
name: 'RECEIPT_DOWNLOAD',
|
|
480
|
-
data: {
|
|
481
|
-
page: 'completion',
|
|
482
|
-
transaction_id: 'TXN-2024-001234',
|
|
483
|
-
format: 'pdf',
|
|
484
|
-
},
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
if (isSuccess(response)) {
|
|
488
|
-
// Event sent successfully
|
|
489
|
-
} else if (isError(response)) {
|
|
490
|
-
console.error(`Failed to send event: ${response.error}`);
|
|
491
|
-
}
|
|
492
|
-
```
|
|
434
|
+
| State | Description |
|
|
435
|
+
| :----------------- | :----------------------------------------------- |
|
|
436
|
+
| `HOMEPAGE` | Entry point or main landing page. |
|
|
437
|
+
| `CHECKOUT_PAGE` | Transaction confirmation or payment selection. |
|
|
438
|
+
| `COMPLETION_POINT` | Post-transaction or success page. |
|
|
439
|
+
| `CUSTOM` | Any other interaction outside the standard flow. |
|
|
493
440
|
|
|
494
441
|
#### Best Practices
|
|
495
442
|
|
|
@@ -502,16 +449,6 @@ if (isSuccess(response)) {
|
|
|
502
449
|
|
|
503
450
|
The checkout flow is a two-step process: your backend first initializes a transaction using your partner credentials, then your frontend triggers the native payment interface using the response from your backend.
|
|
504
451
|
|
|
505
|
-
#### Step 1 — Initialize transaction on your backend
|
|
506
|
-
|
|
507
|
-
Before calling `CheckoutModule.triggerCheckout()`, you must create a transaction on your server by calling the [GrabPay API](https://developer.grab.com/docs/partner-apps/pages/developer-resources/payment/#create-transaction). This requires your `partnerID` and `partnerSecret` to generate an HMAC-SHA256 signature.
|
|
508
|
-
|
|
509
|
-
The API returns a payload containing `partnerTxID`, `request`, and `sessionID` — all three fields are required by the frontend.
|
|
510
|
-
|
|
511
|
-
#### Step 2 — Trigger checkout from your frontend
|
|
512
|
-
|
|
513
|
-
Pass the complete response from the Initialize Transaction API to `CheckoutModule.triggerCheckout()`:
|
|
514
|
-
|
|
515
452
|
```typescript
|
|
516
453
|
import {
|
|
517
454
|
CheckoutModule,
|
|
@@ -526,20 +463,25 @@ const identity = new IdentityModule();
|
|
|
526
463
|
const scope = new ScopeModule();
|
|
527
464
|
|
|
528
465
|
async function processPayment() {
|
|
529
|
-
// 1.
|
|
530
|
-
const
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
466
|
+
// 1. Proactively check for checkout permission
|
|
467
|
+
const hasAccess = await scope.hasAccessTo('CheckoutModule', 'triggerCheckout');
|
|
468
|
+
|
|
469
|
+
if (!isSuccess(hasAccess) || !hasAccess.result) {
|
|
470
|
+
// Request authorization for mobile.checkout
|
|
471
|
+
// Note: mobile.checkout is a mobile scope; no backend exchange is needed for auth.
|
|
472
|
+
const authResponse = await identity.authorize({
|
|
473
|
+
clientId: 'your-client-id',
|
|
474
|
+
redirectUri: window.location.href,
|
|
475
|
+
scope: 'mobile.checkout',
|
|
476
|
+
environment: 'production',
|
|
477
|
+
responseMode: 'in_place',
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
if (isSuccess(authResponse) && authResponse.status_code === 200) {
|
|
481
|
+
await scope.reloadScopes();
|
|
482
|
+
} else {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
543
485
|
}
|
|
544
486
|
|
|
545
487
|
// 2. Fetch the initialized transaction payload from your backend
|
|
@@ -558,33 +500,14 @@ async function processPayment() {
|
|
|
558
500
|
});
|
|
559
501
|
|
|
560
502
|
if (isSuccess(checkoutResult)) {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
if (status === 'success') {
|
|
564
|
-
console.log('Payment successful:', transactionID);
|
|
565
|
-
} else if (status === 'failure') {
|
|
566
|
-
console.error('Payment failed:', errorCode, errorMessage);
|
|
567
|
-
} else if (status === 'pending') {
|
|
568
|
-
console.log('Payment is processing:', transactionID);
|
|
569
|
-
} else if (status === 'userInitiatedCancel') {
|
|
570
|
-
console.log('User cancelled payment');
|
|
571
|
-
}
|
|
503
|
+
console.log(checkoutResult.result);
|
|
572
504
|
} else if (isError(checkoutResult)) {
|
|
573
505
|
console.error('Checkout error:', checkoutResult.error);
|
|
574
506
|
}
|
|
575
507
|
}
|
|
576
508
|
```
|
|
577
509
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
| Status | Description |
|
|
581
|
-
| :-------------------- | :----------------------------------------------------------------------------- |
|
|
582
|
-
| `success` | Payment completed successfully. `transactionID` is provided. |
|
|
583
|
-
| `failure` | Payment failed. `transactionID`, `errorCode`, and `errorMessage` are provided. |
|
|
584
|
-
| `pending` | Payment is still being processed. `transactionID` is provided. |
|
|
585
|
-
| `userInitiatedCancel` | User cancelled the payment. No other fields are present. |
|
|
586
|
-
|
|
587
|
-
For the complete API reference, see [CheckoutModule](https://grab.github.io/superapp-sdk/classes/CheckoutModule.html).
|
|
510
|
+
For the complete API reference, see [GrabPay API](https://developer.grab.com/docs/partner-apps/pages/developer-resources/payment/) and [CheckoutModule](https://grab.github.io/superapp-sdk/classes/CheckoutModule.html).
|
|
588
511
|
|
|
589
512
|
|
|
590
513
|
## API Reference
|