From 1d15964a52478e4b2a13618a9acbd6ce9428ab73 Mon Sep 17 00:00:00 2001
From: Mahmoud Saad <67297856+iOSMahmoudSaad@users.noreply.github.com>
Date: Tue, 22 Sep 2026 13:04:47 +0300
Subject: [PATCH] add embded view to full doc
Updated import statements and payment result handling in the README. Added details about SDK listener and payment status handling.
---
README.md | 293 ++++++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 272 insertions(+), 21 deletions(-)
diff --git a/README.md b/README.md
index 501ba9a..0b15c70 100644
--- a/README.md
+++ b/README.md
@@ -63,7 +63,10 @@ To get started with the `paymob-reactnative` package, follow these steps:
To begin using the Paymob SDK in your React Native application, start by importing the module in your component:
```javascript
-import Paymob, { PaymentResult, CreditCardType } from 'paymob-reactnative';
+import Paymob, {
+ PaymentStatus,
+ FailureCallBackVersion,
+} from 'paymob-reactnative';
```
### SDK Customization
@@ -88,46 +91,294 @@ These customization options allow you to tailor the Paymob SDK interface to alig
### Adding a Listener
-To handle payment results effectively, you can add a listener that will respond to different transaction statuses. This is crucial for providing feedback to users about their payment transactions:
+To handle payment results, register a listener with `Paymob.setSdkListener`. The listener receives a response object each time the full-screen payment flow started by `Paymob.presentPayVC()` finishes, and it is the single entry point for the success, failure, cancellation, and pending outcomes:
```javascript
-Paymob.setSdkListener((status: PaymentResult) => {
- switch (status) {
- case PaymentResult.SUCCESS:
- // Handle successful payment
+Paymob.setSdkListener((response) => {
+ switch (response.status) {
+ case PaymentStatus.SUCCESS:
+ // Handle successful payment (transaction data is in response.details)
break;
- case PaymentResult.FAIL:
- // Handle failed payment
+ case PaymentStatus.FAIL:
+ // Handle failed payment (the reason is in response.message)
break;
- case PaymentResult.PENDING:
+ case PaymentStatus.CANCELLED:
+ // Handle a payment cancelled by the user
+ break;
+ case PaymentStatus.PENDING:
// Handle pending payment status
break;
}
});
```
-This listener will allow you to implement logic based on the result of the payment process, enhancing the user experience.
+The `response` object has the following fields:
+
+| Field | Type | Description |
+| --- | --- | --- |
+| `status` | `string` | One of `PaymentStatus.SUCCESS` (`'Success'`), `PaymentStatus.FAIL` (`'Fail'`), `PaymentStatus.CANCELLED` (`'Cancelled'`), or `PaymentStatus.PENDING` (`'Pending'`). |
+| `details` | `object` | Transaction data returned by the native SDK for a successful payment. On Android it is also present, as an empty object, for the other statuses; on iOS it is omitted. |
+| `message` | `string` | The failure reason. Only present when `status` is `'Fail'`. |
+
+Keep the following in mind:
+
+- Only one listener is active at a time. Calling `setSdkListener` again replaces the previous listener, and `Paymob.removeSdkListener()` removes it (for example, when your component unmounts).
+- Register the listener before calling `Paymob.presentPayVC()`. On Android, results are only delivered while a listener is registered.
+- The native module delivers results through the `onTransactionStatus` event. `setSdkListener` subscribes to it for you, so you do not need to use `NativeEventEmitter` directly.
+
+### Handling Payment Failures
+
+A failed payment is reported through the same listener, as a response with `status` equal to `PaymentStatus.FAIL`. There is no separate failure API for the full-screen flow.
+
+**Callback parameters**
+
+- `response.status`: `'Fail'` (`PaymentStatus.FAIL`).
+- `response.message`: a string describing why the payment failed, as reported by the native SDK.
+
+**Failure callback version**
+
+You can select the failure callback version used by the native SDK with `Paymob.setFailureCallbackVersion`:
+
+```javascript
+Paymob.setFailureCallbackVersion(FailureCallBackVersion.V2);
+```
+
+- Supported values are `FailureCallBackVersion.V1` and `FailureCallBackVersion.V2`. The strings `'V1'` and `'V2'` are also accepted (case-insensitive); any other value falls back to `V1`.
+- Call it before `Paymob.presentPayVC()`, like the other SDK customizations.
+- It applies to the full-screen flow started by `Paymob.presentPayVC()`. The embedded view does not use it.
+- The React Native bridge always delivers the failure reason to your listener as `response.message`. What differs between `V1` and `V2` is defined by the native SDKs and is not documented in this package.
+
+**Complete example**
+
+```javascript
+import React, { useEffect } from 'react';
+import { Alert, Button, View } from 'react-native';
+import Paymob, {
+ PaymentStatus,
+ FailureCallBackVersion,
+} from 'paymob-reactnative';
+
+const showAlert = (title, message) =>
+ requestAnimationFrame(() => Alert.alert(title, message));
+
+export default function CheckoutScreen({ clientSecret, publicKey }) {
+ useEffect(() => {
+ Paymob.setSdkListener((response) => {
+ switch (response.status) {
+ case PaymentStatus.SUCCESS:
+ console.log('Payment succeeded', response.details);
+ break;
+ case PaymentStatus.FAIL:
+ showAlert('Payment failed', response.message ?? 'Unknown error');
+ break;
+ case PaymentStatus.CANCELLED:
+ showAlert('Payment cancelled');
+ break;
+ case PaymentStatus.PENDING:
+ showAlert('Payment pending');
+ break;
+ }
+ });
+
+ return () => {
+ Paymob.removeSdkListener();
+ };
+ }, []);
+
+ const pay = () => {
+ Paymob.setFailureCallbackVersion(FailureCallBackVersion.V2);
+ Paymob.presentPayVC(clientSecret, publicKey);
+ };
+
+ return (
+
+
+
+ );
+}
+```
+
+### Handling Cancellation
+
+Cancellation is supported and is reported through the same listener, as a response with `status` equal to `PaymentStatus.CANCELLED` (`'Cancelled'`). It is emitted when the native SDK reports that the payment was cancelled.
+
+- **Payload:** only `response.status`. There is no `message`, and `details` is empty (an empty object on Android, omitted on iOS).
+
+```javascript
+Paymob.setSdkListener((response) => {
+ if (response.status === PaymentStatus.CANCELLED) {
+ // The payment was cancelled, for example return the user to the cart
+ }
+});
+```
+
+For the embedded view, cancellation is delivered through the `onCancelled` prop instead. See [Embedded View](#embedded-view).
### Invoking the SDK
After configuring the SDK, you can invoke the Paymob payment interface with the following code:
```javascript
-const savedBankCards = [
- {
- maskedPan: '1234', // The masked card number displayed to the user
- savedCardToken: 'CARD_TOKEN', // The token representing the saved card
- creditCard: CreditCardType.MASTERCARD, // The type of the credit card (e.g., Mastercard)
- },
-];
-
-Paymob.presentPayVC('CLIENT_SECRET', 'PUBLIC_KEY', savedBankCards);
+Paymob.presentPayVC('CLIENT_SECRET', 'PUBLIC_KEY');
```
-**Note:** The `savedBankCards` parameter is optional. If you do not have saved bank cards to provide, you can simply call the `presentPayVC` method without it.
-
This function call opens the Paymob payment interface, allowing users to complete their transactions securely. Make sure to replace `'CLIENT_SECRET'` and `'PUBLIC_KEY'` with your actual credentials.
+## Embedded View
+
+Besides the full-screen flow started by `Paymob.presentPayVC()`, the SDK can render the card checkout inside your own screen through the native `PaymobCheckoutView` component. This keeps the customer inside your app UI while card entry and payment processing happen in an embedded native view.
+
+The embedded view is available on both iOS and Android. It uses the same native SDKs and the same installation steps described above; no additional setup is required.
+
+**Important:** the package does not export a JavaScript wrapper for the embedded view. You obtain the component with `requireNativeComponent('PaymobCheckoutView')` and drive it with two native commands, `configure` and `setPaymentKeys`, exactly as the [example app](example/src/PaymobEmbeddedView.tsx) does. The embedded view is not covered by the package's TypeScript declarations.
+
+### Import and Usage
+
+Register the native component once, at module scope, then render it and keep a ref to it:
+
+```javascript
+import { requireNativeComponent } from 'react-native';
+
+const PaymobCheckoutView = requireNativeComponent('PaymobCheckoutView');
+```
+
+### Props
+
+| Prop | Type | Description |
+| --- | --- | --- |
+| `style` | `StyleProp` | Standard view style. See [Layout and Styling](#layout-and-styling). |
+| `onSuccess` | `(event) => void` | Called when the payment succeeds. `event.nativeEvent` contains the transaction details. |
+| `onFailure` | `(event) => void` | Called when the payment fails. `event.nativeEvent.error` contains the failure reason. |
+| `onCancelled` | `(event) => void` | Called when the payment is cancelled. `event.nativeEvent` is an empty object. |
+| `onPending` | `(event) => void` | Called when the payment is pending. `event.nativeEvent` is an empty object. |
+
+Note that the embedded view reports the failure reason as `event.nativeEvent.error`, whereas the full-screen listener reports it as `response.message`.
+
+### Commands
+
+Commands are sent to the view with `UIManager.dispatchViewManagerCommand(findNodeHandle(ref.current), commandName, args)`. Send each command once, using the command name as a string; this works on both platforms.
+
+| Command | Arguments | Description |
+| --- | --- | --- |
+| `configure` | `[config]` | Applies the embedded view configuration. Send it once after the view has mounted and before setting the payment keys. |
+| `setPaymentKeys` | `[{ publicKey, clientSecret }]` | Provides the payment keys used by the embedded checkout. |
+
+### Configuration
+
+The `configure` command takes a single configuration object. All fields are optional:
+
+| Field | Type | Default | Description |
+| --- | --- | --- | --- |
+| `showAddNewCard` | `boolean` | `true` | Show or hide the "Add new card" option. |
+| `showSaveCard` | `boolean` | `true` | Show or hide the save card option. |
+| `saveCardByDefault` | `boolean` | iOS: `false`, Android: `true` | Pre-check the save card option. The default differs between platforms, so set it explicitly. |
+| `payFromOutside` | `boolean` | `false` | Not currently usable from React Native. See [Limitations](#limitations-of-the-embedded-view). |
+| `uiCustomization` | `string` | none | Optional UI customization, passed to the native SDK as a JSON-encoded string. Omit it to use the default appearance. The supported keys are defined by the native SDKs. |
+
+The full-screen customization functions (`Paymob.setAppName`, `Paymob.setButtonBackgroundColor`, and so on) apply to `Paymob.presentPayVC()` only and do not affect the embedded view.
+
+### Payment Keys
+
+Your backend creates the payment intention with your **secret key** and returns the `publicKey` and `clientSecret` to the app. Never put your secret key inside the app. Once you have the keys, send them to the view:
+
+```javascript
+UIManager.dispatchViewManagerCommand(
+ findNodeHandle(checkoutRef.current),
+ 'setPaymentKeys',
+ [{ publicKey, clientSecret }]
+);
+```
+
+### Layout and Styling
+
+- The height of the embedded view is dynamic. The native view reports its content height to React Native's layout on both platforms, so the view grows and shrinks as its content changes (for example, when the customer opens the card form). Do not set a fixed `height` on it, because that would override the reported height.
+- Give the view the full available width, for example `width: '100%'` (or `alignSelf: 'stretch'`).
+- Because the height can change, place the view inside a `ScrollView` when the screen has other content around it.
+
+### Complete Example
+
+```javascript
+import React, { useEffect, useRef } from 'react';
+import {
+ ScrollView,
+ StyleSheet,
+ Text,
+ UIManager,
+ findNodeHandle,
+ requireNativeComponent,
+} from 'react-native';
+
+const PaymobCheckoutView = requireNativeComponent('PaymobCheckoutView');
+
+const sendCommand = (ref, commandName, args) => {
+ const handle = findNodeHandle(ref.current);
+ if (handle) {
+ UIManager.dispatchViewManagerCommand(handle, commandName, args);
+ }
+};
+
+export default function EmbeddedCheckoutScreen({ publicKey, clientSecret }) {
+ const checkoutRef = useRef(null);
+
+ // 1. Configure the view once it has mounted
+ useEffect(() => {
+ sendCommand(checkoutRef, 'configure', [
+ {
+ showAddNewCard: true,
+ showSaveCard: true,
+ saveCardByDefault: false,
+ payFromOutside: false,
+ },
+ ]);
+ }, []);
+
+ // 2. Provide the keys returned by your backend
+ useEffect(() => {
+ if (publicKey && clientSecret) {
+ sendCommand(checkoutRef, 'setPaymentKeys', [{ publicKey, clientSecret }]);
+ }
+ }, [publicKey, clientSecret]);
+
+ return (
+
+ Checkout
+
+ {
+ console.log('Payment succeeded', event.nativeEvent);
+ }}
+ onFailure={(event) => {
+ console.log('Payment failed', event.nativeEvent.error);
+ }}
+ onCancelled={() => {
+ console.log('Payment cancelled');
+ }}
+ onPending={() => {
+ console.log('Payment pending');
+ }}
+ />
+
+ );
+}
+
+const styles = StyleSheet.create({
+ content: { padding: 16 },
+ title: { fontSize: 18, fontWeight: 'bold', marginBottom: 16 },
+ checkout: { width: '100%' },
+});
+```
+
+### Limitations of the Embedded View
+
+- **No external payment trigger.** The native SDK supports a `payFromOutside` mode, but the React Native bridge does not expose a command to start the payment from your own button. Leave `payFromOutside` as `false` and use the pay button rendered inside the embedded view.
+- **No height callback.** The dynamic height is handled internally; there is no `onHeightChanged` prop.
+- **No single result callback.** Results are delivered through the four `onSuccess`, `onFailure`, `onCancelled`, and `onPending` props.
+- **No dispose method.** No explicit release command is exposed.
+- **Android:** the `configure` command requires the current Activity to be an `androidx.activity.ComponentActivity`, which the default `ReactActivity` is.
+- **Architecture:** the embedded view is implemented as a legacy native view manager, and the example app runs with the New Architecture disabled. Behavior with the New Architecture enabled is not documented here.
+
Here’s the updated explanation with a revised first sentence and the inclusion of the repository cloning step:
## Example App