diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 629e145..1e11d65 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -1,5 +1,19 @@ # OST Wallet SDK Changelog +## Version 2.4.1 +* User can authorize external session by scanning QR-Code. To use `Authorize session by scanning QR code`, please update downstream sdk.
+iOS: v2.4.1
+Android: v2.4.1 +* User can pass QR-Code payload to perform QR-Code actions without opening Scanner in OstWalletUI. +This functionality is available for `scanQRCodeToAuthorizeSession`, `scanQRCodeToExecuteTransaction`, `scanQRCodeToAuthorizeDevice`, + +## Version 2.4.0 +* OstRedemption UI component is now available in SDK. To use `OstRedemption`, please update downstream sdk.
+iOS:`v2.4.0`
+Android:`v2.4.0` +* `getRedeemableSkus` and `getRedeemableSkuDetails` apis added in `OstJsonApi`. + + ## Version 2.3.14 * ReadMe updates
* Removed unused imports
diff --git a/README.md b/README.md index 8fedcc5..6dd74ba 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,8 @@ Ost React Native Wallet SDK... - [Implementation](#implementation-1) + [Wallet Settings UI Component](#wallet-settings-ui-component) - [Implementation](#implementation-2) + + [Redemption flow UI Component](#redemption-flow-ui-component) + - [Implementation](#implementation-3) + [OstTransactionHelper - Transaction and Session Integrated Workflow](#osttransactionhelper---transaction-and-session-integrated-workflow) - [Implementation](#implementation-3) * [Intermediate Usage - Ost Wallet SDK UI](#intermediate-usage---ost-wallet-sdk-ui) @@ -240,6 +242,8 @@ Activate User workflow deploys user's wallet on the blockchain and whitelists th #### Implementation Please refer to [Activate User UI Workflow Documentation](./documentation/OstWalletUI.md#activate-user) for implementation details. + + ### 3. Wallet Settings UI Component --- OstWallet Settings is a pre-built UI component available exclusively available in `ost-wallet-sdk-react-native` SDK. @@ -249,7 +253,18 @@ It is a wallet settings page that can be used by end-users to perfrom 12 differe #### Implementation Please refer to [OstWallet Settings Documentation](./documentation/OstWalletSettings.md) for implementation details. -### 4. OstvTransaction Helper - Transaction and Add Session Integrated Workflow + + +### 4. Redemption Flow UI Component +--- +OstRedemption component is a pre-built UI component available exclusively in `ost-wallet-sdk-react-native` SDK. +It consist two pages - one displaying redeemable product list and another displaying product details and redemption options. It can be used by end-users to integrate redemption flow into their app. +> IMPORTANT: This feature requires application to use [React Navigation](https://reactnavigation.org/docs/en/getting-started.html) package. + +#### Implementation +Please reder to [OstRedemption flow Documentation](./documentation/OstRedemptionFlow.md) for implementation details. + +### 5. OstTransaction Helper - Transaction and Add Session Integrated Workflow --- `OstTransactionHelper` is a transaction helper provided by the SDK that creates session keys before performing a transaction if needed. App developers can configure the session creation parameters (session buckets) as per application's need. diff --git a/android/build.gradle b/android/build.gradle index 96474e6..dc676c0 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -18,7 +18,6 @@ allprojects { } apply plugin: 'com.android.library' - android { compileSdkVersion 28 buildToolsVersion '28.0.3' @@ -45,5 +44,5 @@ repositories { dependencies { implementation 'com.facebook.react:react-native:+' - api 'com.ost:ost-wallet-sdk-android:2.3.8' + api 'com.ost:ost-wallet-sdk-android:2.4.1' } diff --git a/android/src/main/java/com/ostwalletrnsdk/OstRNSdkJsonApiModule.java b/android/src/main/java/com/ostwalletrnsdk/OstRNSdkJsonApiModule.java index 49db67e..121be82 100644 --- a/android/src/main/java/com/ostwalletrnsdk/OstRNSdkJsonApiModule.java +++ b/android/src/main/java/com/ostwalletrnsdk/OstRNSdkJsonApiModule.java @@ -150,6 +150,46 @@ public void getCurrentDeviceForUserId( } } + @ReactMethod + public void getRedeemableSkus( + String userId, + ReadableMap requestMap, + Callback successCallback, + Callback errorCallback + ) { + try { + Map requestPayload = new HashMap<>(); + if (null != requestMap) { + requestPayload = requestMap.toHashMap(); + } + OstJsonApi.getRedeemableSkus(userId ,requestPayload, new OstJsonApiCallbackImpl(successCallback, errorCallback)); + } catch (Throwable e) { + errorCallback.invoke(Utils.getError(e, "rn_orsjam_grs_1")); + return; + } + } + + + @ReactMethod + public void getRedeemableSkuDetails( + String userId, + String skuId, + ReadableMap requestMap, + Callback successCallback, + Callback errorCallback + ) { + try { + Map requestPayload = new HashMap<>(); + if (null != requestMap) { + requestPayload = requestMap.toHashMap(); + } + OstJsonApi.getRedeemableSkuDetails(userId , skuId ,requestPayload, new OstJsonApiCallbackImpl(successCallback, errorCallback)); + } catch (Throwable e) { + errorCallback.invoke(Utils.getError(e, "rn_orsjam_grsd_1")); + return; + } + } + private static class OstJsonApiCallbackImpl implements OstJsonApiCallback { diff --git a/android/src/main/java/com/ostwalletrnsdk/Utils.java b/android/src/main/java/com/ostwalletrnsdk/Utils.java index 51f7ff8..710f0d2 100644 --- a/android/src/main/java/com/ostwalletrnsdk/Utils.java +++ b/android/src/main/java/com/ostwalletrnsdk/Utils.java @@ -95,6 +95,8 @@ public static WritableMap convertJsonToMap(JSONObject jsonObject) throws JSONExc map.putDouble(key, (Double) value); } else if (value instanceof String) { map.putString(key, (String) value); + } else if (null == value || "null".equalsIgnoreCase(value.toString())){ + map.putNull(key); } else { map.putString(key, value.toString()); } @@ -119,6 +121,8 @@ public static WritableArray convertJsonToArray(JSONArray jsonArray) throws JSONE array.pushDouble((Double) value); } else if (value instanceof String) { array.pushString((String) value); + } else if (null == value || "null".equalsIgnoreCase(value.toString())) { + array.pushNull(); } else { array.pushString(value.toString()); } diff --git a/android/src/main/java/com/ostwalletrnsdk/ui/OstWalletUiRnSdkModule.java b/android/src/main/java/com/ostwalletrnsdk/ui/OstWalletUiRnSdkModule.java index 0a4f7a9..c134251 100644 --- a/android/src/main/java/com/ostwalletrnsdk/ui/OstWalletUiRnSdkModule.java +++ b/android/src/main/java/com/ostwalletrnsdk/ui/OstWalletUiRnSdkModule.java @@ -165,20 +165,31 @@ public void getAddDeviceQRCode(String userId, String uuid) { } @ReactMethod - public void scanQRCodeToAuthorizeDevice(String userId, String uuid) { + public void scanQRCodeToAuthorizeDevice(String userId, String qrPayload, String uuid) { Activity currentActivity = getCurrentActivity(); OstUICallbackImpl ostUICallback = new OstUICallbackImpl( uuid, this.reactContext, new OstWorkflowContext(OstWorkflowContext.WORKFLOW_TYPE.AUTHORIZE_DEVICE_WITH_QR_CODE)); - String workflowId = OstWalletUI.scanQRCodeToAuthorizeDevice(currentActivity, userId, ostUICallback); + String workflowId = OstWalletUI.scanQRCodeToAuthorizeDevice(currentActivity, qrPayload, userId, ostUICallback); SdkInteract.getInstance().subscribe(workflowId, ostUICallback); } + + @ReactMethod + public void scanQRCodeToAuthorizeSession(String userId, String qrPayload, String uuid) { + Activity currentActivity = getCurrentActivity(); + OstUICallbackImpl ostUICallback = new OstUICallbackImpl( uuid, this.reactContext, + new OstWorkflowContext(OstWorkflowContext.WORKFLOW_TYPE.AUTHORIZE_DEVICE_WITH_QR_CODE)); + String workflowId = OstWalletUI.scanQRCodeToAuthorizeSession(currentActivity, qrPayload, userId, ostUICallback); + SdkInteract.getInstance().subscribe(workflowId, ostUICallback); + } + + @ReactMethod - public void scanQRCodeToExecuteTransaction(String userId, String uuid) { + public void scanQRCodeToExecuteTransaction(String userId, String qrPayload, String uuid) { Activity currentActivity = getCurrentActivity(); OstUICallbackImpl ostUICallback = new OstUICallbackImpl( uuid, this.reactContext, new OstWorkflowContext(OstWorkflowContext.WORKFLOW_TYPE.EXECUTE_TRANSACTION)); - String workflowId = OstWalletUI.scanQRCodeToExecuteTransaction(currentActivity, userId, ostUICallback); + String workflowId = OstWalletUI.scanQRCodeToExecuteTransaction(currentActivity, qrPayload, userId, ostUICallback); SdkInteract.getInstance().subscribe(workflowId, ostUICallback); } diff --git a/assets/down-arrow.png b/assets/down-arrow.png new file mode 100644 index 0000000..8e7202d Binary files /dev/null and b/assets/down-arrow.png differ diff --git a/assets/msg-icon.png b/assets/msg-icon.png new file mode 100644 index 0000000..119aea6 Binary files /dev/null and b/assets/msg-icon.png differ diff --git a/documentation/OstJsonApi.md b/documentation/OstJsonApi.md index 97bbef1..07f5de8 100644 --- a/documentation/OstJsonApi.md +++ b/documentation/OstJsonApi.md @@ -24,6 +24,9 @@ OST JSON APIs are a set of *asynchronous* methods that make API calls to OST Pla - [Usage](#usage-4) - [Sample Response](#sample-response-4) - [Sample Error](#sample-error) + - [Get Redeemable Sku Details](#get-redeemable-sku-details) + - [Usage](#usage-8) + - [Sample Response](#sample-response-8) - [List API](#list-api) - [Get Transactions](#get-transactions) - [Usage](#usage-5) @@ -31,6 +34,9 @@ OST JSON APIs are a set of *asynchronous* methods that make API calls to OST Pla - [Get Devices](#get-devices) - [Usage](#usage-6) - [Sample Response](#sample-response-6) + - [Get Redeemable Skus](#get-redeemable-skus) + - [Usage](#usage-7) + - [Sample Response](#sample-response-7) @@ -342,6 +348,141 @@ The `getPendingRecoveryForUserId` API will respond with `UNPROCESSABLE_ENTITY` A } ``` + +### Get Redeemable Sku Details +API to get redeemable sku details. + + +##### Usage +```javascript +/* + Please update userId as per your needs. + Since this userId does not belong to your economy, you will get an error if you do not change it. +*/ +let userId = "71c59448-ff77-484c-99d8-abea8a419836"; +let skuDetailId = "2"; +let extraParams = {}; + +/** + * Api to get redeemable skus + * @param {String} userId - Ost User id + * @param {String} skuDetailId - Sku detail id got from list of Redeemable skus + * @param {Object} extraParams (@nullable). + * @param {function} Success callback with success data + * @param {function} Failure callback with error and failure response + * @public + */ + + +OstJsonApi.getRedeemableSkuDetails(userId, skuDetailId ,extraParams, (response) => { + console.log(response); + }, (error)=> { + console.log("An error has occurred while fetching redeemable sku details."); + console.log( error ); + }); +``` + + +##### Sample Response +```json +{ + "result_type":"redemption_product", + "redemption_product":{ + "status":"active", + "images":{ + "detail":{ + "original":{ + "size":90821, + "url":"https://dxwfxs8b4lg24.cloudfront.net/ost-platform/rskus/stag-starbucks-d-original.png", + "width":150, + "height":150 + } + }, + "cover":{ + "original":{ + "size":193141, + "url":"https://dxwfxs8b4lg24.cloudfront.net/ost-platform/rskus/stag-starbucks-c-original.png", + "width":320, + "height":320 + } + } + }, + "availability":[ + { + "country_iso_code":"USA", + "country":"USA", + "currency_iso_code":"USD", + "denominations":[ + { + "amount_in_wei":"49938358", + "amount_in_fiat":5 + }, + { + "amount_in_wei":"99876717", + "amount_in_fiat":10 + }, + ... + ] + }, + { + "country_iso_code":"CAN", + "country":"Canada", + "currency_iso_code":"CAD", + "denominations":[ + { + "amount_in_wei":"37547638", + "amount_in_fiat":5 + }, + { + "amount_in_wei":"75095276", + "amount_in_fiat":10 + }, + ... + ] + }, + { + "country_iso_code":"GBR", + "country":"United Kingdom", + "currency_iso_code":"GBP", + "denominations":[ + { + "amount_in_wei":"64855011", + "amount_in_fiat":5 + }, + { + "amount_in_wei":"129710022", + "amount_in_fiat":10 + }, + ... + ] + }, + { + "country_iso_code":"IND", + "country":"India", + "currency_iso_code":"INR", + "denominations":[ + { + "amount_in_wei":"1396", + "amount_in_fiat":0.01 + }, + { + "amount_in_wei":"139609", + "amount_in_fiat":1 + }, + ... + ] + } + ], + "id":"2", + "updated_timestamp":1582024811, + "description":{ + "text":null + }, + "name":"Starbucks" + } +} +``` + ## List API All `List` APIs support pagination. The response of all `List` APIs has an extra attribute `meta`. @@ -546,3 +687,88 @@ OstJsonApi.getDeviceListForUserId(userId, nextPagePayload, "result_type": "devices" } ``` + + +### Get Redeemable Skus +API to get redeemable skus. + + +##### Usage +```javascript +/* + Please update userId as per your needs. + Since this userId does not belong to your economy, you will get an error if you do not change it. +*/ +let userId = "71c59448-ff77-484c-99d8-abea8a419836"; +let nextPagePayload = null; + +/** + * Api to get redeemable skus + * @param {String} userId - Ost User id + * @param {Object} nextPagePayload (@nullable). Pass null to get first page. + * @param {function} Success callback with success data + * @param {function} Failure callback with error and failure response + * @public + */ + + +OstJsonApi.getRedeemableSkus(userId, nextPagePayload , (respones) => { + console.log(response); + // Let's check if more pages of data is available. + if ( response.meta ) { + let nextPagePayloadFromResponse = response.meta.next_page_payload || {}; + if ( Object.keys(nextPagePayloadFromResponse).length > 0 ) { + // Next page is available. + // Update nextPagePayload + nextPagePayload = nextPagePayloadFromResponse; + // To fetch the next page, pass the updated nextPagePayload. + } + } + }, (error)=> { + console.log("An error has occurred while fetching redeemable skus."); + console.log( error ); + }); +``` + + +##### Sample Response +```json +{ + "meta":{ + "next_page_payload":{ + } + }, + "result_type":"redemption_products", + "redemption_products":[ + { + "status":"active", + "updated_timestamp":1582024811, + "id":"2", + "description":{ + "text":null + }, + "images":{ + "detail":{ + "original":{ + "size":90821, + "url":"https://dxwfxs8b4lg24.cloudfront.net/ost-platform/rskus/stag-starbucks-d-original.png", + "width":150, + "height":150 + } + }, + "cover":{ + "original":{ + "size":193141, + "url":"https://dxwfxs8b4lg24.cloudfront.net/ost-platform/rskus/stag-starbucks-c-original.png", + "width":320, + "height":320 + } + } + }, + "name":"Starbucks" + }, + ... + ... + ] +} +``` \ No newline at end of file diff --git a/documentation/OstRedemptionConfig.md b/documentation/OstRedemptionConfig.md new file mode 100644 index 0000000..efd5e6f --- /dev/null +++ b/documentation/OstRedemptionConfig.md @@ -0,0 +1,46 @@ +# OstWallet Redemption Config + +## Introduction + +App developers can configure the text shown on redemption page. + +To configure the content, the sdk needs to be provided with [JSON object](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/JSON). + +## Dictionary Data Structure + +Here is the small sample json representation of the configuration. + +```json +{ + "common": { + "walletIcon": null, + "storeIcon": null + }, + "skuListScreen": { + "navHeader": null, + "header": null, + "description": null + }, + "skuDetailsScreen": { + "navHeader": null + } +} +``` +In the above example: + +* The key `common` includes options to configure the wallet icon in header and the store icon. +* The key `skuListScreen` allows to configure the navigation header and the header and description shown on the redemption store list page. +* The key `skuDetailsScreen` allows to configure the navigation header shown on the redemption store details page. + +## Redemption Config + +The following are the customizable options provided. These can be set in the above config. + +| customizable component | Config Keys | Config Type | +| -------------------------------------- | -------------------------------------------------- | ------------------------------- | +| Wallet icon | - walletIcon | Custom icon Image to be rendered. | +| Store icon | - storeIcon | Custom icon Image to be rendered. | +| Skulist Header | - skuListScreen.header | String | +| Skulist Description | - skuListScreen.description | String | +| SkuDetails Screen Navigation header | - skuDetailsScreen.navHeader | String | +| Skulist Screen Navigation header | - skuListScreen.navHeader | String| diff --git a/documentation/OstRedemptionFlow.md b/documentation/OstRedemptionFlow.md new file mode 100644 index 0000000..1262591 --- /dev/null +++ b/documentation/OstRedemptionFlow.md @@ -0,0 +1,56 @@ +# OstRedemption Flow + +## Introduction + +OstRedemption component is a pre-built UI component available exclusively in `ost-wallet-sdk-react-native` SDK. +It consist two pages - one displaying redeemable product list and another displaying product details and redemption options. It can be used by end-users to integrate redemption flow into their app. +> IMPORTANT: This feature requires application to use [React Navigation](https://reactnavigation.org/docs/en/getting-started.html) package. + +## Usage + +### Create Redemption Flow stack navigation + +```js +import {OstRedeemableSkus, OstRedeemableSkuDetails } from '@ostdotcom/ost-wallet-sdk-react-native'; + +let redemptionStack = createStackNavigator( + { + RedeemableSkusScreen: OstRedeemableSkus, + RedeemableSkuDetails: OstRedeemableSkuDetails + } +); +``` + +### Navigate to settings page +`ostUserId` and `ostWalletUIWorkflowCallback` are mandetory parameters that need to be passed as params to the `RedeemableSkusScreen` screen. +```js +const ostUserId = +const delegate = new OstWalletUIWorkflowCallback(ostUserId, {}) +this.props.navigation.push("RedeemableSkusScreen", {'ostUserId': ostUserId , + 'ostWalletUIWorkflowCallback': delegate, + 'navTitle': 'My Store'}); +``` + +>Note
+> Developer needs to create a class extends from `OstWalletUIWorkflowCallback` and write logic to get passphrase prefix from their application server. +> Please refer [this](OstWalletUI.md#setup-your-passphrase-prefix-delegate) section for documentation. + +## UI Customization + +Developer can customize Redemption flow by updating respective properties mentioned in image. OstTheme config shown [here](./configs/ost-sdk-theme-config.js) + +![copy-framework-file](images/redemptionFlow.png) + +## Redemption Content + +Developer can make various modifications in redemption flow component. To modify contet, [refer here](./OstRedemptionConfig.md). + +```js +import ost_sdk_redemption_config from "../../theme/ostsdk/ost-sdk-redemption-config"; +import { OstRedemableCustomConfig } from '@ostdotcom/ost-wallet-sdk-react-native'; + + OstRedemableCustomConfig.setConfig(ost_sdk_redemption_config); + +``` + + diff --git a/documentation/OstWalletSettingsConfig.md b/documentation/OstWalletSettingsConfig.md index 9059d36..0cde409 100644 --- a/documentation/OstWalletSettingsConfig.md +++ b/documentation/OstWalletSettingsConfig.md @@ -74,4 +74,3 @@ Some workflows requires additional data. It can be passed to workflow by setting | add_session | - spending_limit | String | | | - expiration_time | Number | - diff --git a/documentation/OstWalletUI.md b/documentation/OstWalletUI.md index 6310885..6c5b41d 100644 --- a/documentation/OstWalletUI.md +++ b/documentation/OstWalletUI.md @@ -236,6 +236,9 @@ OstWalletSdkUI.subscribe(workflowId, OstWalletSdkUI.EVENTS.flowInterrupt, (ostWo #### Add Session A session is a period of time during which a sessionKey is authorized to sign transactions under a pre-set limit per transaction on behalf of the user. The device manager, which controls the tokens, authorizes sessions. + +* By Calling function
+ ```js let uiCallback = new UserPassphrasePrefixDelegate() @@ -271,6 +274,80 @@ OstWalletSdkUI.subscribe(workflowId, OstWalletSdkUI.EVENTS.flowInterrupt, (ostWo }); ``` +* By scanning QR-Code
+ +QR Code Sample +``` +as|2.0.0|2a421359d02132e8161cda9518aeaa62647b648e|5369b4d7e0e53e1159d6379b989a8429a7b2dd59|1|1583308559|4d40c46a7302974134a67ce77bdfae0e1f78ee518e87b6cda861ffc5847dfaca11a653651c6cdfadf0224574f6f07e1a78aabacdfed66d8c78e1fb2c9bc750161c +``` + +```js + +let uiCallback = new UserPassphrasePrefixDelegate() + +/** + * Add user session + * @param {String} userId - Ost User id + * @param {OstWalletUIWorkflowCallback} uiCallback - callback implementation instances for application communication + * @public + */ +let workflowId = OstWalletSdkUI.scanQRCodeToAuthorizeSession( + userId, + uiCallback +) + +// Subscribe to events +OstWalletSdkUI.subscribe(workflowId, OstWalletSdkUI.EVENTS.requestAcknowledged, () => { + // Session is being added. +}); + +OstWalletSdkUI.subscribe(workflowId, OstWalletSdkUI.EVENTS.flowComplete, (ostWorkflowContext , ostContextEntity) => { + // Show success message to user. + // Session has been added. +}); + +OstWalletSdkUI.subscribe(workflowId, OstWalletSdkUI.EVENTS.flowInterrupt, (ostWorkflowContext , ostError) => { + // Show error to user. + // An error occoured during the workflow. The Session has NOT been added. +}); +``` + + +* With QR Code Payload
+ +```js + +let uiCallback = new UserPassphrasePrefixDelegate() +let payload = "as|2.0.0|2a421359d02132e8161cda9518aeaa62647b648e|5369b4d7e0e53e1159d6379b989a8429a7b2dd59|1|1583308559|4d40c46a7302974134a67ce77bdfae0e1f78ee518e87b6cda861ffc5847dfaca11a653651c6cdfadf0224574f6f07e1a78aabacdfed66d8c78e1fb2c9bc750161c" +/** + * Authorize browser session with QR code payload + * @param {String} userId - Ost User id + * @param {String} qrPayload - QR-Code payload + * @param {OstWalletUIWorkflowCallback} uiCallback - callback implementation instances for application communication + * @return {*|number} + */ +let workflowId = OstWalletSdkUI.authorizeSessionWithQRPayload( + userId, + payload, + uiCallback +) + +// Subscribe to events +OstWalletSdkUI.subscribe(workflowId, OstWalletSdkUI.EVENTS.requestAcknowledged, () => { + // Session is being added. +}); + +OstWalletSdkUI.subscribe(workflowId, OstWalletSdkUI.EVENTS.flowComplete, (ostWorkflowContext , ostContextEntity) => { + // Show success message to user. + // Session has been added. +}); + +OstWalletSdkUI.subscribe(workflowId, OstWalletSdkUI.EVENTS.flowInterrupt, (ostWorkflowContext , ostError) => { + // Show error to user. + // An error occoured during the workflow. The Session has NOT been added. +}); +``` + #### Get Mnemonic Phrase The mnemonic phrase represents a human-readable way to authorize a new device. This phrase is 12 words long. ```js @@ -553,6 +630,8 @@ QR-Code Sample: } ``` +* By scanning QR-Code
+ ```js let uiCallback = new UserPassphrasePrefixDelegate(); @@ -579,6 +658,37 @@ QR-Code Sample: // An error occoured during the workflow. }); ``` + +* with QR Code payload
+Developer can pass QR code payload to authorize device. QR code scanner view won't open if developer pass payload. + +```js + let uiCallback = new UserPassphrasePrefixDelegate(); + let payload = "{\"dd\":\"AD\",\"ddv\":\"1.1.0\",\"d\":{\"da\":\"0x7701af46018fc57c443b63e839eb24872755a2f8\"}}" + /** + * Authorize device with QR code payload + * @param {String} userId - Ost User id + * @param {String} qrPayload - QR-Code payload + * @param {OstWalletUIWorkflowCallback} uiCallback - callback implementation instances for application communication + * @return {*|number} + */ + let workflowId = OstWalletSdkUI.authorizeDeviceWithQRPayload(userId, payload, uiCallback); + + // Subscribe to events + OstWalletSdkUI.subscribe(workflowId, OstWalletSdkUI.EVENTS.requestAcknowledged, () => { + // Device is being authorized. + }); + + OstWalletSdkUI.subscribe(workflowId, OstWalletSdkUI.EVENTS.flowComplete, (ostWorkflowContext , ostContextEntity) => { + // Show success message to user. + // Device has been authorized. + }); + + OstWalletSdkUI.subscribe(workflowId, OstWalletSdkUI.EVENTS.flowInterrupt, (ostWorkflowContext , ostError) => { + // Show error to user. + // An error occoured during the workflow. + }); +``` ### Execute Transaction * By Scanning QR-Code
@@ -641,6 +751,37 @@ QR-Code Sample: }); ``` +* with QR Code payload
+Developer can pass QR code payload to execute transaction. QR code scanner view won't appear if developer pass payload. + +```js + let uiCallback = new UserPassphrasePrefixDelegate(); + let payload = + /** + * Execute transaction with QR code payload + * @param {String} userId - Ost User id + * @param {String} qrPayload - QR-Code payload + * @param {OstWalletUIWorkflowCallback} uiCallback - callback implementation instances for application communication + * @return {*|number} + */ + let workflowId = OstWalletSdkUI.executeTransactionWithQRPayload(userId, payload, uiCallback); + + // Subscribe to events + OstWalletSdkUI.subscribe(workflowId, OstWalletSdkUI.EVENTS.requestAcknowledged, () => { + // Device is being authorized. + }); + + OstWalletSdkUI.subscribe(workflowId, OstWalletSdkUI.EVENTS.flowComplete, (ostWorkflowContext , ostContextEntity) => { + // Show success message to user. + // Device has been authorized. + }); + + OstWalletSdkUI.subscribe(workflowId, OstWalletSdkUI.EVENTS.flowInterrupt, (ostWorkflowContext , ostError) => { + // Show error to user. + // An error occoured during the workflow. + }); +``` + * By Calling function
Helper method creates session if active sessoin for transction amount is not available. To execute transaction via helper method, [ref here](./OstTransactionHelper.md) diff --git a/documentation/configs/ost-sdk-theme-config.js b/documentation/configs/ost-sdk-theme-config.js index 899e7d4..ce8bc18 100644 --- a/documentation/configs/ost-sdk-theme-config.js +++ b/documentation/configs/ost-sdk-theme-config.js @@ -1,5 +1,3 @@ -import colors from '../styles/Colors' - export default { "nav_bar_logo_image": { @@ -12,6 +10,10 @@ export default { "system_font_weight": "bold", "alignment": "left" }, + "b1":{ + "color":"#ffffff", + "size":15 + }, "c2": { "size": 12, @@ -21,7 +23,7 @@ export default { }, "navigation_bar": { - "tint_color": colors.brightSky + "tint_color": "#61B2C9" }, "navigation_bar_header": { @@ -33,7 +35,8 @@ export default { "tint_color": "#ffffff" }, "back":{ - "tint_color": "#ffffff" + "tint_color": "#ffffff", + "source" : null } }, @@ -53,5 +56,13 @@ export default { "color": "#0F9D58", "system_font_weight": "regular", "alignment": "left", + }, + + "form_field": { + "size":15, + "system_font_weight":"regular", + "color":"#484848", + "border_color": "#484848", + "alignment": "left" } } diff --git a/documentation/images/redemptionFlow.png b/documentation/images/redemptionFlow.png new file mode 100644 index 0000000..d9ab106 Binary files /dev/null and b/documentation/images/redemptionFlow.png differ diff --git a/documentation/ios_setup.md b/documentation/ios_setup.md index 1fb4310..11ebef6 100644 --- a/documentation/ios_setup.md +++ b/documentation/ios_setup.md @@ -15,7 +15,7 @@ Carthage looks at a file called `Cartfile` to determine which libraries to insta Add following entry in your `Cartfile` ```bash - github "ostdotcom/ost-wallet-sdk-ios" == 2.3.6 + github "ostdotcom/ost-wallet-sdk-ios" == 2.4.1 ``` Now to actually install everything run the following in your terminal: diff --git a/ios/ostwalletrnsdk/OstWalletRnSdkApi.m b/ios/ostwalletrnsdk/OstWalletRnSdkApi.m index 3469003..a3094b5 100644 --- a/ios/ostwalletrnsdk/OstWalletRnSdkApi.m +++ b/ios/ostwalletrnsdk/OstWalletRnSdkApi.m @@ -75,4 +75,25 @@ @implementation OstWalletRnSdkApi [OstJsonApi getCurrentDeviceForUserId:userId delegate:delegate]; } + +RCT_EXPORT_METHOD(getRedeemableSkus: (NSString * _Nonnull)userId + params: (NSDictionary * _Nullable)params + successCallback: (RCTResponseSenderBlock _Nonnull) successCallback + errorCallback: (RCTResponseSenderBlock _Nonnull) errorCallback ) +{ + OstJsonApiCallbackImpl *delegate = [[OstJsonApiCallbackImpl alloc]initWithSuccess:successCallback errorCallback:errorCallback]; + [OstJsonApi getRedeemableSkusWithUserId:userId params:params delegate:delegate]; +} + + +RCT_EXPORT_METHOD(getRedeemableSkuDetails: (NSString * _Nonnull)userId + skuId: (NSString * _Nonnull)skuId + params: (NSDictionary * _Nullable)params + successCallback: (RCTResponseSenderBlock _Nonnull) successCallback + errorCallback: (RCTResponseSenderBlock _Nonnull) errorCallback ) +{ + OstJsonApiCallbackImpl *delegate = [[OstJsonApiCallbackImpl alloc]initWithSuccess:successCallback errorCallback:errorCallback]; + [OstJsonApi getRedeemableSkuDetailsWithUserId:userId skuId:skuId params:params delegate: delegate]; +} + @end diff --git a/ios/ostwalletrnsdk/OstWorkFlowCallbackImpl.m b/ios/ostwalletrnsdk/OstWorkFlowCallbackImpl.m index e55f43b..47135fb 100644 --- a/ios/ostwalletrnsdk/OstWorkFlowCallbackImpl.m +++ b/ios/ostwalletrnsdk/OstWorkFlowCallbackImpl.m @@ -199,6 +199,7 @@ - (NSString *) getWorkflowTypeName: (OstWorkflowType) workflowType { case OstWorkflowTypeLogoutAllSessions: return @"LOGOUT_ALL_SESSIONS"; case OstWorkflowTypeUpdateBiometricPreference: return @"UPDATE_BIOMETRIC_PREFERENCE"; case OstWorkflowTypeShowDeviceQR: return @"SHOW_DEVICE_QR"; + case OstWorkflowTypeAuthorizeSessionWithQRCode: return @"AUTHORIZE_SESSION_WITH_QR_CODE"; default: return @"UNKNOWN"; } } diff --git a/ios/ostwalletrnsdk/sdkWithUI/OstUICallbackImpl.m b/ios/ostwalletrnsdk/sdkWithUI/OstUICallbackImpl.m index a9bfef5..7c93b19 100644 --- a/ios/ostwalletrnsdk/sdkWithUI/OstUICallbackImpl.m +++ b/ios/ostwalletrnsdk/sdkWithUI/OstUICallbackImpl.m @@ -144,6 +144,7 @@ - (NSString *) getWorkflowTypeName: (OstWorkflowType) workflowType { case OstWorkflowTypeLogoutAllSessions: return @"LOGOUT_ALL_SESSIONS"; case OstWorkflowTypeUpdateBiometricPreference: return @"UPDATE_BIOMETRIC_PREFERENCE"; case OstWorkflowTypeShowDeviceQR: return @"SHOW_DEVICE_QR"; + case OstWorkflowTypeAuthorizeSessionWithQRCode: return @"AUTHORIZE_SESSION_WITH_QR_CODE"; default: return @"UNKNOWN"; } } diff --git a/ios/ostwalletrnsdk/sdkWithUI/OstWalletRnSdkUI.m b/ios/ostwalletrnsdk/sdkWithUI/OstWalletRnSdkUI.m index d72ee65..1550651 100644 --- a/ios/ostwalletrnsdk/sdkWithUI/OstWalletRnSdkUI.m +++ b/ios/ostwalletrnsdk/sdkWithUI/OstWalletRnSdkUI.m @@ -159,26 +159,44 @@ @implementation OstWalletRnSdkUI } RCT_EXPORT_METHOD(scanQRCodeToAuthorizeDevice: (NSString * _Nonnull) userId + addDevicePayload: (NSString * _Nullable) addDevicePayload uuid:(NSString *)uuid) { OstUICallbackImpl *uiCallbackImpl = [[OstUICallbackImpl alloc]initWithId:uuid]; NSString *worklfowId = [OstWalletUI scanQRCodeToAuthorizeDeviceWithUserId: userId + addDevicePayload: addDevicePayload passphrasePrefixDelegate: uiCallbackImpl]; [OstWalletUI subscribeWithWorkflowId:worklfowId listner: uiCallbackImpl]; } RCT_EXPORT_METHOD(scanQRCodeToExecuteTransaction: (NSString * _Nonnull) userId + executeTransactionPayload: (NSString * _Nullable) executeTransactionPayload uuid:(NSString *)uuid) { OstUICallbackImpl *uiCallbackImpl = [[OstUICallbackImpl alloc]initWithId:uuid]; NSString *worklfowId = [OstWalletUI scanQRCodeToExecuteTransactionWithUserId: userId + executeTransactionPayload: executeTransactionPayload passphrasePrefixDelegate: uiCallbackImpl]; [OstWalletUI subscribeWithWorkflowId:worklfowId listner: uiCallbackImpl]; } + +RCT_EXPORT_METHOD(scanQRCodeToAuthorizeSession: (NSString * _Nonnull) userId + qrPayload: (NSString * _Nullable) qrPayload + uuid:(NSString *)uuid) { + + OstUICallbackImpl *uiCallbackImpl = [[OstUICallbackImpl alloc]initWithId:uuid]; + + NSString *worklfowId = [OstWalletUI scanQRCodeToAuthorizeSessionWithUserId: userId + qrPayload: qrPayload + passphrasePrefixDelegate: uiCallbackImpl]; + + [OstWalletUI subscribeWithWorkflowId:worklfowId listner: uiCallbackImpl]; +} + @end diff --git a/js/OstSdkErrorMessages.json b/js/OstSdkErrorMessages.json index d3234d5..d6ba519 100644 --- a/js/OstSdkErrorMessages.json +++ b/js/OstSdkErrorMessages.json @@ -3,6 +3,8 @@ }, "ADD_SESSION": { }, + "AUTHORIZE_SESSION_WITH_QR_CODE": { + }, "GET_DEVICE_MNEMONICS": { }, "PERFORM_QR_ACTION": { @@ -24,21 +26,22 @@ "EXECUTE_TRANSACTION": { }, "__DEFAULT_CONTEXT": { + "INVALID_SIGNATURE": "The QR code does not contain valid signature.", "USER_UNAUTHORIZED": "Device is not authorized. Please authorize device again.", "DEVICE_OUT_OF_SYNC": "Device time is out of sync. Please check the time on your device reflects current date and time.", "NETWORK_ERROR": "Request could not be executed due to cancellation, a connectivity problem or timeout.", "INVALID_MNEMONICS": "The 12 word passphrase you provided is incorrect. ", - "INVALID_QR_TRANSACTION_DATA": "The QR code for executing a transaction is not well formed. To know the data definition for QR code based on type of operations please visit https://dev.ost.com/platform ", + "INVALID_QR_TRANSACTION_DATA": "The QR code for executing a transaction is not well formed.", "INVALID_USER_PASSPHRASE": "The 6 digit PIN you entered is not correct.", "MAX_PASSPHRASE_VERIFICATION_LIMIT_REACHED": "The maximum number of 'authenticating with PIN' attempts has been reached. Please try again a bit later.", "DEVICE_CAN_NOT_BE_AUTHORIZED": "Unable to authorize this device. Please ensure the device is 'Registered' for this user with OST platform. Only a registered device can be authorized.", - "SESSION_NOT_FOUND": "The device doesn't has any active session. Please authorize a session before doing any transaction. Workflow details provided at https://dev.ost.com/platform/docs/sdk/references ", + "SESSION_NOT_FOUND": "The device doesn't has any active session. Please authorize a session before doing any transaction.", "INVALID_QR_CODE": "Incorrect QR code.", "RECOVERY_KEY_GENERATION_FAILED": "Failed to generate Recovery key. Inspect if a correct input values required are being sent and re-submit the request. ", "OUT_OF_MEMORY_ERROR": "Device is running low on memory. Reduce the number of App running on your device and re-enter the pin", "WORKFLOW_FAILED": "Something went wrong, please try again", "WORKFLOW_VIEW_DESTROYED": "The application interrupted the workflow. The view got terminated while performing the workflow", - "DEVICE_UNAUTHORIZED": "Unable to perform the operation as the device not authorized. For details on how to authorize a device please visit https://dev.ost.com/platform/docs/sdk/references ", + "DEVICE_UNAUTHORIZED": "Unable to perform the operation as the device not authorized.", "DEVICE_CAN_NOT_BE_REVOKED": "Cannot complete the revoke device operation. Only an authorized device can be revoked. Please ensure you are trying to revoke a valid device and re-submit the request.", "WORKFLOW_CANCELED": "WORKFLOW_CANCELLED", "WORKFLOW_CANCELLED": "WORKFLOW_CANCELLED" diff --git a/js/OstWalletSdkUI.js b/js/OstWalletSdkUI.js index c3d3709..1c33df4 100644 --- a/js/OstWalletSdkUI.js +++ b/js/OstWalletSdkUI.js @@ -54,27 +54,27 @@ class OstWalletRNSdkUI { } /** - * Initiate device recovery + * Initiate device recovery * @param {String} userId - Ost User id * @param {String} deviceAddressToRecover - Device address which wants to recover - * @param {OstWalletUIWorkflowCallback} uiCallback - callback implementation instances for application communication + * @param {OstWalletUIWorkflowCallback} uiCallback - callback implementation instances for application communication * @public */ initiateDeviceRecovery(userId, deviceAddressToRecover, uiCallback ) { let coreUiCallback = this._getCoreUiCallback(uiCallback); - OstWalletSdkUI.initiateDeviceRecovery( userId, deviceAddressToRecover, coreUiCallback.uuid ); + OstWalletSdkUI.initiateDeviceRecovery( userId, deviceAddressToRecover, coreUiCallback.uuid ); return coreUiCallback.uuid; } /** - * Abort device recovery + * Abort device recovery * @param {String} userId - Ost User id - * @param {OstWalletUIWorkflowCallback} uiCallback - callback implementation instances for application communication + * @param {OstWalletUIWorkflowCallback} uiCallback - callback implementation instances for application communication * @public */ abortDeviceRecovery( userId, uiCallback ) { let coreUiCallback = this._getCoreUiCallback(uiCallback); - OstWalletSdkUI.abortDeviceRecovery( userId, coreUiCallback.uuid); + OstWalletSdkUI.abortDeviceRecovery( userId, coreUiCallback.uuid); return coreUiCallback.uuid; } @@ -180,7 +180,45 @@ class OstWalletRNSdkUI { */ scanQRCodeToAuthorizeDevice(userId, uiCallback) { let coreUiCallback = this._getCoreUiCallback(uiCallback); - OstWalletSdkUI.scanQRCodeToAuthorizeDevice( userId, coreUiCallback.uuid ); + OstWalletSdkUI.scanQRCodeToAuthorizeDevice( userId, null, coreUiCallback.uuid ); + return coreUiCallback.uuid; + } + + /** + * Authorize device with QR code payload + * @param {String} userId - Ost User id + * @param {String} qrPayload - QR-Code payload + * @param {OstWalletUIWorkflowCallback} uiCallback - callback implementation instances for application communication + * @return {*|number} + */ + authorizeDeviceWithQRPayload(userId, qrPayload, uiCallback) { + let coreUiCallback = this._getCoreUiCallback(uiCallback); + OstWalletSdkUI.scanQRCodeToAuthorizeDevice( userId, qrPayload, coreUiCallback.uuid ); + return coreUiCallback.uuid; + } + + /** + * Authorize browser session via QR code + * @param {String} userId - Ost User id + * @param {OstWalletUIWorkflowCallback} uiCallback - callback implementation instances for application communication + * @public + */ + scanQRCodeToAuthorizeSession(userId, uiCallback) { + let coreUiCallback = this._getCoreUiCallback(uiCallback); + OstWalletSdkUI.scanQRCodeToAuthorizeSession( userId, null ,coreUiCallback.uuid ); + return coreUiCallback.uuid; + } + + /** + * Authorize browser session with QR code payload + * @param {String} userId - Ost User id + * @param {String} qrPayload - QR-Code payload + * @param {OstWalletUIWorkflowCallback} uiCallback - callback implementation instances for application communication + * @return {*|number} + */ + authorizeSessionWithQRPayload(userId, qrPayload, uiCallback) { + let coreUiCallback = this._getCoreUiCallback(uiCallback); + OstWalletSdkUI.scanQRCodeToAuthorizeSession( userId, qrPayload, coreUiCallback.uuid ); return coreUiCallback.uuid; } @@ -193,7 +231,20 @@ class OstWalletRNSdkUI { */ scanQRCodeToExecuteTransaction(userId, uiCallback) { let coreUiCallback = this._getCoreUiCallback(uiCallback); - OstWalletSdkUI.scanQRCodeToExecuteTransaction( userId, coreUiCallback.uuid ); + OstWalletSdkUI.scanQRCodeToExecuteTransaction( userId, null, coreUiCallback.uuid ); + return coreUiCallback.uuid; + } + + /** + * Execute transaction with QR code payload + * @param {String} userId - Ost User id + * @param {String} qrPayload - QR-Code payload + * @param {OstWalletUIWorkflowCallback} uiCallback + * @return {*|number} + */ + executeTransactionWithQRPayload(userId, qrPayload, uiCallback) { + let coreUiCallback = this._getCoreUiCallback(uiCallback); + OstWalletSdkUI.scanQRCodeToExecuteTransaction( userId, qrPayload, coreUiCallback.uuid ); return coreUiCallback.uuid; } @@ -280,9 +331,9 @@ class OstWalletRNSdkUI { _getCoreUiCallback(uiCallback) { if ( !uiCallback || !(uiCallback instanceof OstWalletUIWorkflowCallback) ) { let err = new Error('Invalid uiCallback. The argument \'uiCallback\' must be an instanceof OstWalletUIWorkflowCallback'); - } + } return new OstWalletUICoreCallback(uiCallback); } } -export default new OstWalletRNSdkUI(); \ No newline at end of file +export default new OstWalletRNSdkUI(); diff --git a/js/Redemptions/CommonComponents/AlertBox.js b/js/Redemptions/CommonComponents/AlertBox.js new file mode 100644 index 0000000..7818cd6 --- /dev/null +++ b/js/Redemptions/CommonComponents/AlertBox.js @@ -0,0 +1,30 @@ +import { Alert } from "react-native"; + +class AlertBox{ + + constructor(config){ + this.config = config || {}; + } + + showAlert(){ + Alert.alert( + this.config.title, + this.config.Message, + [ + { + text: this.config.cancelBtnText, + onPress: () => {this.config.cancelCallBack && this.config.cancelCallBack()}, + style: this.config.cancelStyle, + }, + { + text: this.config.successBtnText, + onPress: () => {this.config.successCallback && this.config.successCallback()} + }, + ], + {cancelable: false}, + ) + } + +} + +export default AlertBox; \ No newline at end of file diff --git a/js/Redemptions/CommonComponents/BackArrow.js b/js/Redemptions/CommonComponents/BackArrow.js new file mode 100644 index 0000000..8186d0a --- /dev/null +++ b/js/Redemptions/CommonComponents/BackArrow.js @@ -0,0 +1,10 @@ +import React from 'react'; +import {View , Image } from "react-native"; +import OstThemeConfigHelper from "../../helpers/OstThemeConfigHelper"; +import inlineStyle from "./styles"; + +export default () => ( + + + +); diff --git a/js/Redemptions/CommonComponents/HeaderRight.js b/js/Redemptions/CommonComponents/HeaderRight.js new file mode 100644 index 0000000..32178df --- /dev/null +++ b/js/Redemptions/CommonComponents/HeaderRight.js @@ -0,0 +1,16 @@ +import React from 'react'; +import { View, Image, Text } from 'react-native'; + +import OstRedemableCustomConfig from "../RedemableCustomConfig"; +import OstThemeConfigHelper from "../../helpers/OstThemeConfigHelper"; +import styles from './styles'; + +export default (props) => { + const walletIcon = OstRedemableCustomConfig.getWalletIconUri() + return ( + + {walletIcon ? : } + {props.balance} + + ) +} \ No newline at end of file diff --git a/js/Redemptions/CommonComponents/styles.js b/js/Redemptions/CommonComponents/styles.js new file mode 100644 index 0000000..0e9a340 --- /dev/null +++ b/js/Redemptions/CommonComponents/styles.js @@ -0,0 +1,33 @@ +import { StyleSheet } from 'react-native'; + +let stylesMap = StyleSheet.create({ + backArrowImageWrapper : { + width: 50, + height: 50, + justifyContent: 'center', + alignItems: 'flex-start', + paddingLeft: 10 + }, + backArrowStyle : { + width: 10, + height: 18, + aspectRatio: 1 + }, + walletImgSkipFont : { + width: 20, + height: 18, + aspectRatio: 1, + marginRight: 8 + }, + headerRightWrapper : { + paddingRight: 15, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center' + }, + balanceText: { + alignSelf: 'center' + } +}); + +export default styles = stylesMap; \ No newline at end of file diff --git a/js/Redemptions/MultipleClickHandler.js b/js/Redemptions/MultipleClickHandler.js new file mode 100644 index 0000000..871cb27 --- /dev/null +++ b/js/Redemptions/MultipleClickHandler.js @@ -0,0 +1,15 @@ +export default (func, wait = 500) => { + let tapCount = 0; + let handler; + + return function() { + if (tapCount === 0) { + tapCount++; + func(); + } + // Clear the previous timeout and set a new one. + clearTimeout(handler); + handler = setTimeout(() => (tapCount = 0), wait); + }; + }; + \ No newline at end of file diff --git a/js/Redemptions/RedeemableSkuDetails/index.js b/js/Redemptions/RedeemableSkuDetails/index.js new file mode 100644 index 0000000..2a7992a --- /dev/null +++ b/js/Redemptions/RedeemableSkuDetails/index.js @@ -0,0 +1,578 @@ +import React,{PureComponent} from 'react'; +import { + View, + Text, + Image, + ScrollView, + KeyboardAvoidingView, + TextInput, + TouchableOpacity, + ActivityIndicator, + Platform +} from 'react-native'; +import RNPickerSelect from 'react-native-picker-select'; + +import HeaderRight from "../CommonComponents/HeaderRight"; +import BackArrow from '../CommonComponents/BackArrow'; +import OstJsonApi from "../../OstJsonApi"; +import OstRedemptionTransactionHelper from "../RedemptionTransactionHelper"; +import {OstTransactionHelper} from "../../TransactionHelper/OstTransactionHelper"; +import OstWalletSdkUI from "../../OstWalletSdkUI"; +import OstThemeConfigHelper from '../../helpers/OstThemeConfigHelper'; +import OstRedmptionConfig from "../ost-redemption-config"; +import tokenHelper from "../../helpers/TokenHelper"; +import AlertBox from "../CommonComponents/AlertBox"; +import msgIcon from '../../../assets/msg-icon.png'; +import downArrow from '../../../assets/down-arrow.png'; +import multipleClickHandler from '../MultipleClickHandler'; +import {sdkErrorHelper} from "../../helpers/OstSdkErrorHelper"; +import OstWalletSdkHelper from "../../helpers/OstWalletSdkHelper"; +import OstRedemableCustomConfig from "../RedemableCustomConfig"; + +import {stylesMap} from './styles'; + +const errorMsgs = { + generalError: "Failed to redeem, please try again later.", + emailRequired: "Email address is required.", + inSufficientbalance : "Insufficient wallet balance.", + invalidAmount : "Given token amount is invalid." +} + +const apiErrorParameterKey = "redemption_meta"; + +function __getParam(navigation , paramName) { + if(navigation && navigation.getParam){ + return navigation.getParam(paramName); + } + return null; +} + + +class OstRedeemableSkuDetails extends PureComponent{ + static navigationOptions = ({ navigation }) => { + const balance = __getParam(navigation , "balance") || 0 , + isCustomBack = !!OstThemeConfigHelper.getBackArrowSource() + ; + let navigationOption = { + title: __getParam(navigation , "navTitle") || OstRedemableCustomConfig.getSkuDetailsScreenNavHeader(), + headerStyle: { + borderBottomWidth: 0, + shadowColor: '#000', + shadowOffset: { + width: 0, + height: 1 + }, + shadowOpacity: 0.1, + shadowRadius: 3 + }, + headerBackTitle: null, + headerRight: + }; + if( isCustomBack ){ + navigationOption["headerBackImage"] = ; + } + return Object.assign({}, navigationOption, OstThemeConfigHelper.getNavigationHeaderConfig()); + }; + + constructor(props){ + super(props); + this.navigation = props.navigation ; + this.initTheme(); + this.ostUserId = props.ostUserId || __getParam(props.navigation, "ostUserId") ; + this.ostWalletUIWorkflowCallback = props.ostWalletUIWorkflowCallback || __getParam(props.navigation, "ostWalletUIWorkflowCallback"); + this.skuDetails = props.redemptionSku || __getParam(props.navigation, "redemptionSku") || {}; + this.onPurchaseSuccess = props.onPurchaseSuccess || __getParam(props.navigation, "onPurchaseSuccess") ; + + if(!this.skuDetails) return; + + this.inputRefs = { + countryPicker : null, + currencyPicker : null, + emailIdInput : null + }; + + this.state={ + refreshing : true , + selectedAvailability : null, + selectedDenomination : null, + transactionSuccess: false, + errorText : "", + isPurchasing: false, + emailId : "" + }; + } + + __setState = (state={}) => { + this.setState(state); + }; + + componentDidMount(){ + this.init(); + } + + componentWillUnmount (){ + this.__setState = () =>{}; + this.inputRefs.countryPicker = null; + this.inputRefs.currencyPicker = null; + this.inputRefs.emailIdInput = null; + this.navigation = null ; + } + + initTheme(){ + OstThemeConfigHelper.updateConfig().then((res)=> { + this.props.navigation && this.props.navigation.setParams && this.props.navigation.setParams({"ostThemeUpdated": true}); + }).catch((error)=> {}) + } + + init(){ + if(!tokenHelper.token){ + tokenHelper.init(this.ostUserId).then(()=>{ + this.updateBalance(); + }).catch(()=> {}); + }else{ + this.updateBalance(); + } + this.fetchDetails(); + } + + updateBalance(){ + OstJsonApi.getBalanceForUserId(this.ostUserId, (res) => { + let balance = res.balance && res.balance.available_balance; + balance = tokenHelper.toBtPrecision(tokenHelper.fromDecimal(balance)); + this.navigation && this.navigation.setParams && this.navigation.setParams({ + balance + }) + }, () => {}); + } + + fetchDetails = () => { + if(this.skuDetails.id){ + OstJsonApi.getRedeemableSkuDetails(this.ostUserId, this.skuDetails.id ,{}, this.onDetailsSuccess , this.onDetailsError) + }else{ + this.__setState({ + refreshing : false, + }); + } + + }; + + onDetailsSuccess = (data={}) =>{ + const resultType = data["result_type"] || {}; + this.skuDetails = data[resultType] || {}; + this.setInitialAvailabilityAndDenomination(); + this.__setState({ + refreshing : false, + }) + }; + + onDetailsError =( error)=> { + this.__setState({ + refreshing : false + }) + }; + + getBtnText = () => { + if(this.state.isPurchasing){ + return "Processing..."; + }else{ + return `Purchase for ${this.getSelectedAmountInBT()} ${tokenHelper.getTokenSymbol()}` + } + }; + + setInitialAvailabilityAndDenomination = () => { + this.state.selectedAvailability = this.skuDetails.availability && this.skuDetails.availability[0] || {} ; + const denominations = this.state.selectedAvailability["denominations"] || []; + this.state.selectedDenomination = denominations[0] || {}; + } + + getAvailableCountryList = () =>{ + let availabilityData = (this.skuDetails && this.skuDetails.availability) || [], + countryData = []; + if(!availabilityData) return countryData; + for(let cnt = 0; cnt< availabilityData.length ; cnt++){ + let country = availabilityData[cnt] || {} , + countryName = country && country.country; + countryData.push({label: countryName, value: country}); + } + return countryData; + }; + + getAvailableCurrencyData = ( ) =>{ + if(!this.state.selectedAvailability) return []; + let denominationsArray = this.state.selectedAvailability.denominations || [], + currencyIsoCode = this.state.selectedAvailability.currency_iso_code, + currencyItems = [] , sanitizedCurrencyItems = [] + ; + for(let cnt = 0 ; cnt < denominationsArray.length ; cnt ++){ + let currentDenomination = denominationsArray[cnt] || {}, + btAmount = currentDenomination.amount_in_wei, + label = `${currentDenomination.amount_in_fiat} ${currencyIsoCode}`, + value = currentDenomination ; + if(!!OstTransactionHelper.getValidBucket(btAmount, tokenHelper.getDecimals())){ + sanitizedCurrencyItems.push({label, value}); + } + currencyItems.push({label, value}); + } + if(sanitizedCurrencyItems.length > 0){ + return sanitizedCurrencyItems; + } + return currencyItems; + } + + + onCountryChange = (value) =>{ + this.__setState({ + selectedAvailability: value + }); + } + + onDenominationChange = ( value ) =>{ + this.__setState({ + selectedDenomination : value + }) + } + + onEmailChange = (text) =>{ + this.__setState({ + emailId:text + }) + this.onFormChange(); + } + + onPurchaseClick = () =>{ + if(this.isInputValid()){ + this.showConfirmationAlert(); + } + }; + + showConfirmationAlert = () =>{ + let config ={ + title : "", + Message : `Confirm email address: ${this.state.emailId}`, + cancelBtnText : "Cancel", + successBtnText : "Confirm", + cancelCallback : this.onAlertCancel, + successCallback : this.onAlertConfirm, + cancelStyle : 'cancel' + } + let alertBox = new AlertBox(config); + alertBox.showAlert(); + } + + campareBalAndPurchaseValue = () =>{ + let purchaseValueInBt = tokenHelper.toBtPrecision( tokenHelper.fromDecimal(this.getSelectedAmountInWei()), 2), + balance = __getParam(this.props.navigation,"balance"); + return tokenHelper.isBalSufficient(balance,purchaseValueInBt); + } + + isInputValid = () =>{ + if(this.state.emailId == '' ){ + this.__setState({ + errorText: errorMsgs.emailRequired + }); + return false; + } + let comparisonResult = this.campareBalAndPurchaseValue(); + if(comparisonResult === -1){ + this.__setState({ + errorText: errorMsgs.inSufficientbalance + }); + return false; + }else if(comparisonResult === null){ + this.__setState({ + errorText: errorMsgs.invalidAmount + }); + } + return true + } + + onAlertCancel = () => {} + + onAlertConfirm = () => { + this.__setState({ + isPurchasing: true + }) + this.executeTranscaction(); + } + + onTransactionSuccess = () => { + this.__setState({ + isPurchasing: false, + transactionSuccess :true + }) + this.onPurchaseSuccess && this.onPurchaseSuccess(); + } + + onTransactionError =( error)=> { + this.__setState({ + isPurchasing: false, + errorText : error, + transactionSuccess :false + }) + } + + onFormChange = () => { + this.__setState({ + isPurchasing: false, + errorText : "", + transactionSuccess : false + }); + } + + setCountryPickerRef = (ref) =>{ + this.inputRefs.countryPicker = ref; + } + + setDenominationPickerRef = (ref) =>{ + this.inputRefs.currencyPicker = ref; + } + + setEmailINputPickerRef = (ref) =>{ + this.inputRefs.emailIdInput = ref; + } + + onDownArrowClickCountry = () =>{ + this.inputRefs.currencyPicker && this.inputRefs.currencyPicker.togglePicker(); + } + + onDownArrowClickCurrency = () =>{ + this.inputRefs.emailIdInput && this.inputRefs.emailIdInput.focus(); + } + + onUpArrowClickCurrency = () =>{ + this.inputRefs.countryPicker && this.inputRefs.countryPicker.togglePicker(); + } + + getPickerIcon = () =>{ + return ( + + + + ) + } + + getImage = () =>{ + if(this.skuDetails.images && this.skuDetails.images.detail && this.skuDetails.images.detail.original && this.skuDetails.images.detail.original.url){ + return ( + + + ) + } + } + + getDescription = () =>{ + if(this.skuDetails.description && this.skuDetails.description.text){ + return( + + {this.skuDetails.description.text} + + ) + } + } + + getName = () =>{ + if(this.skuDetails.name){ + return( + {this.skuDetails.name} + ) + } + } + + getRNPickerStyles = () => { + let styles = {...stylesMap.input, ...OstThemeConfigHelper.getFormFieldConfig()}; + return { + inputIOS : styles, + inputAndroid : styles + } + } + + render(){ + return( + + + {this.getName()} + {this.getImage()} + {this.getDescription()} + + {(!this.skuDetails.availability && !this.state.refreshing) && ( + + Oops! Failed to load available options or no options available. + + + )} + {this.skuDetails.availability && ( + + + Select Country + + + + + Card Amount + + + + + Your mail id + + + {this.state.transactionSuccess && + + + + We have received your order and will send an email shortly to {this.state.emailId} + + + } + {!this.state.transactionSuccess && + { + this.onPurchaseClick() + })} + style={[stylesMap.purchaseBtn, OstThemeConfigHelper.getB1Config()]} + disabled = {this.state.isPurchasing}> + + {this.getBtnText()} + + + } + + {this.state.errorText} + + + + )} + + + ) + } + + getSelectedAmountInBT() { + return tokenHelper.toBtPrecision( tokenHelper.fromDecimal(this.getSelectedAmountInWei()), 2); + } + + getSelectedAmountInWei(){ + return this.state.selectedDenomination && this.state.selectedDenomination["amount_in_wei"] || 0; + } + + getSelectedAmountInFiat(){ + return this.state.selectedDenomination && this.state.selectedDenomination["amount_in_fiat"] || 0; + } + + getSelectedCountryISOCode(){ + return this.state.selectedAvailability && this.state.selectedAvailability["country_iso_code"] || ""; + } + + getSelectedCurrencyISOCode(){ + return this.state.selectedAvailability && this.state.selectedAvailability["currency_iso_code"] || ""; + } + + getTxMeta(){ + const config = JSON.parse(JSON.stringify(OstRedmptionConfig)) || {}; + return config["transactionMeta"] || {}; + } + + getRedemptionMeta(){ + return { + "redeemable_sku_id": this.skuDetails.id, + "amount_in_fiat": this.getSelectedAmountInFiat(), + "country_iso_code": this.getSelectedCountryISOCode(), + "currency_iso_code": this.getSelectedCurrencyISOCode(), + "email": this.state.emailId + } + } + + executeTranscaction = () => { + const amounts = [tokenHelper.fromDecimal(this.getSelectedAmountInWei())] , + address = [tokenHelper.getTokenHolderAddress()] + ; + const uuid = OstRedemptionTransactionHelper.executeDirectTransfer( this.ostUserId, + amounts, + address, + this.getTxMeta(), + this.getRedemptionMeta(), + this.ostWalletUIWorkflowCallback ); + + + OstWalletSdkUI.subscribe(uuid, OstWalletSdkUI.EVENTS.requestAcknowledged, (workflowContext, contextEntity) => { + this.requestAcknowledged(); + const requestAcknowledged = this.ostWalletUIWorkflowCallback["requestAcknowledged"] ; + requestAcknowledged && requestAcknowledged.call(this.ostWalletUIWorkflowCallback , workflowContext, contextEntity); + }); + + OstWalletSdkUI.subscribe(uuid, OstWalletSdkUI.EVENTS.flowInterrupt, (workflowContext, ostError) => { + this.flowInterrupt(workflowContext, ostError); + const flowInterrupt = this.ostWalletUIWorkflowCallback["flowInterrupt"] ; + flowInterrupt && flowInterrupt.call(this.ostWalletUIWorkflowCallback , workflowContext, ostError); + }); + + OstWalletSdkUI.subscribe(uuid, OstWalletSdkUI.EVENTS.flowComplete, (workflowContext, contextEntity) => { + this.ostWalletUIWorkflowCallback["flowComplete"] && this.ostWalletUIWorkflowCallback["flowComplete"](workflowContext, contextEntity); + }); + + } + + requestAcknowledged = () => { + this.onTransactionSuccess(); + this.updateBalance(); + } + + flowInterrupt = ( workflowContext, error) => { + let errorMsg = errorMsgs.generalError; + if(error && error.isApiError()){ + const errData = error.getApiErrorData() || [] , + errObj = errData[0] || {}; + if(errObj["parameter"] == apiErrorParameterKey){ + errorMsg = errObj["msg"]; + } + }else if( workflowContext ){ + if(OstWalletSdkHelper.isUserCancelled(error)){ + errorMsg = ""; + } else { + errorMsg = sdkErrorHelper.getErrorMessage(workflowContext, error); + } + } + this.onTransactionError(errorMsg); + } + +} + +export default OstRedeemableSkuDetails; + diff --git a/js/Redemptions/RedeemableSkuDetails/styles.js b/js/Redemptions/RedeemableSkuDetails/styles.js new file mode 100644 index 0000000..3ecfabf --- /dev/null +++ b/js/Redemptions/RedeemableSkuDetails/styles.js @@ -0,0 +1,101 @@ +import { StyleSheet } from 'react-native'; + +let stylesMap = StyleSheet.create({ + container:{ + flex:1, + flexDirection:'column' + }, + scrollViewContainer:{ + padding:20, + }, + imageStyle:{ + width:"100%", + aspectRatio:16/9, + marginBottom:20, + backgroundColor:'#DBDBDB' + }, + heading:{ + marginBottom:20 + }, + descText:{ + marginBottom:30 + }, + labelStyle:{ + marginBottom:5 + }, + wrapperFormInput:{ + marginBottom:20 + }, + errorContainer:{ + flex:1, + justifyContent:'center', + alignItems:'center', + }, + errorText:{ + fontSize:13, + color:'red', + marginTop:10, + marginBottom:50, + textAlign:'center' + }, + iconWrapper:{ + paddingRight : 17, + flex:1, + marginTop: 17 // [(inputbBoxHeight /2 ) - (heightOfArrow/2 )] where inputbBoxHeight = 46 and heightOfArrow = 12 + }, + downArrow : { + height : 12, + width :20 + }, + purchaseBtn:{ + alignItems:'center', + justifyContent : 'center', + width:'100%', + height:46, + flex:1, + flexDirection:'column', + borderRadius:5 + }, + purchaseBtnText:{ + + }, + successMessageWrapper : { + borderWidth: 1, + borderColor: '#2a293b', + flex:1, + flexDirection:'row', + padding:10, + borderRadius:8, + alignItems:'center', + justifyContent:'center' + }, + imageSuccessMessage:{ + height:60, + width:60, + marginRight : 10 + }, + successText:{ + flex:1 + }, + input : { + fontSize: 15, + paddingVertical: 13, + paddingLeft: 21, + paddingRight: 54, // iconWidth(20) + iconPaddingRight(17) + iconPaddingLeft(17) + borderWidth: 1, + borderRadius:5, + height:46, + }, + emptyProductDetailsWrapper : { + flex:1, + alignItems:'center', + justifyContent:'center', + borderWidth: 1, + borderColor: '#2a293b', + padding:10, + borderRadius:8, + borderStyle: 'dashed' + } +}); + +export {stylesMap}; \ No newline at end of file diff --git a/js/Redemptions/RedeemableSkus/SkusList.js b/js/Redemptions/RedeemableSkus/SkusList.js new file mode 100644 index 0000000..b196c88 --- /dev/null +++ b/js/Redemptions/RedeemableSkus/SkusList.js @@ -0,0 +1,158 @@ +import React from 'react'; +import { FlatList, ActivityIndicator, View, Image, Text, TouchableWithoutFeedback } from 'react-native'; + +import RedemptionSkusModel from "../../services/OstJsonApiPagination/RedemptionSkusModel"; +import Pagination from '../../services/OstJsonApiPagination/Pagination'; +import OstRedemableCustomConfig from "../RedemableCustomConfig"; +import multipleClickHandler from '../MultipleClickHandler'; +import OstThemeConfigHelper from '../../helpers/OstThemeConfigHelper'; + +import styles from './styles'; + +class SkusList extends React.PureComponent{ + constructor( props ){ + super(props); + this.pagination = null; + this.redemptionSkusModel = null ; + this.state = { + list: null, + loadingNext: false, + refreshing: false + }; + this.noDataCell = { + isEmpty: true + } + } + + __setState = (state) => { + if(!state) return; + this.setState(state); + } + + componentDidMount(){ + this.init(); + } + + componentWillUnmount(){ + this.__setState = () => {}; + } + + init(){ + this.redemptionSkusModel = new RedemptionSkusModel(this.props.ostUserId); + this.pagination = new Pagination( this.redemptionSkusModel ,{ + beforeRefresh : this.beforeRefresh , + onRefresh : this.onRefresh , + onRefreshError: this.onRefreshError, + beforeNext: this.beforeNext, + onNext: this.onNext, + onNextError : this.onNextError + } ); + this.pagination && this.pagination.initPagination(); + } + + onItemClick = (item , index) => { + this.props.onItemClick && this.props.onItemClick(item , index); + } + + _renderItem = ({item, index}) => { + if(item.isEmpty){ + return ( + No items selected for redemption. + ); + } + let imageUrl = (item.images && item.images.cover && item.images.cover.original.url) || null; + return ( + { + this.onItemClick(item , index) + })} + > + + + {imageUrl && } + + + + ); + }; + + getResults = () => { + let results = this.pagination.modelFetch.getAllResults(); + return results.length == 0 ? [this.noDataCell] : results; + } + + beforeRefresh = ( ) => { + this.__setState({ refreshing : true }); + } + + onRefresh = ( res ) => { + this.__setState({ refreshing : false, list : this.getResults() }); + } + + onRefreshError = ( error ) => { + this.__setState({ refreshing : false }); + } + + beforeNext =() => { + this.__setState({ loadingNext : true }); + } + + onNext = ( res ) => { + this.__setState({ loadingNext : false , list : this.getResults() }); + } + + onNextError = ( error ) => { + this.__setState({ loadingNext : false }); + } + + getNext = () => { + this.pagination.getNext(); + } + + refresh = () => { + this.pagination.refresh(); + } + + _keyExtractor = (item, index)=> { + return `id_${index}` + } + + renderFooter = () => { + if (!this.state.loadingNext) return null; + return ; + }; + + render = () => { + if(!this.state.list) return null; + return ( + + ) + } +} + + +const ListHeaderComponent = (props) => { + const storeLogo = OstRedemableCustomConfig.getStoreIconUri() , + header = OstRedemableCustomConfig.getHeader(), + description = OstRedemableCustomConfig.getDescription() + ; + return ( + {storeLogo && } + {header.length > 0 && {header} } + {description.length > 0 && {description} } + ) +} + +export default SkusList; \ No newline at end of file diff --git a/js/Redemptions/RedeemableSkus/index.js b/js/Redemptions/RedeemableSkus/index.js new file mode 100644 index 0000000..87453bc --- /dev/null +++ b/js/Redemptions/RedeemableSkus/index.js @@ -0,0 +1,136 @@ +import React from 'react'; +import { SafeAreaView } from 'react-native'; +import OstJsonApi from '../../OstJsonApi';; + +import HeaderRight from "../CommonComponents/HeaderRight"; +import OstRedemableCustomConfig from "../RedemableCustomConfig"; +import OstThemeConfigHelper from '../../helpers/OstThemeConfigHelper'; +import OstWalletUIWorkflowCallback from '../../OstWalletUIWorkflowCallback'; +import styles from './styles'; +import SkusList from './SkusList'; +import BackArrow from '../CommonComponents/BackArrow'; +import tokenHelper from "../../helpers/TokenHelper"; + +function __getParam(navigation , paramName) { + if(navigation && navigation.getParam){ + return navigation.getParam(paramName); + } + return null; +} + +class OstRedeemableSkus extends React.PureComponent { + + static navigationOptions = ({ navigation }) => { + const balance = __getParam(navigation , "balance") || 0 , + isCustomBack = !!OstThemeConfigHelper.getBackArrowSource() + ; + let navigationOption = { + title: __getParam(navigation , "navTitle") || OstRedemableCustomConfig.getSkuListNavHeader() , + headerStyle: { + borderBottomWidth: 0, + shadowColor: '#000', + shadowOffset: { + width: 0, + height: 1 + }, + shadowOpacity: 0.1, + shadowRadius: 3 + }, + headerBackTitle: null, + headerRight: + }; + if( isCustomBack ){ + navigationOption["headerBackImage"] = ; + } + + return Object.assign({}, navigationOption, OstThemeConfigHelper.getNavigationHeaderConfig()); + }; + + constructor( props ){ + super(props); + + this.initTheme(); + + this.ostUserId = props.ostUserId || __getParam(props.navigation , "ostUserId"); + this.ostWalletUIWorkflowCallback = props.ostWalletUIWorkflowCallback || __getParam(props.navigation , "ostWalletUIWorkflowCallback"); + this.onItemClick = props.onItemClick || __getParam(props.navigation , "onItemClick"); + this.balanceInBt = 0; + + if( !this.ostUserId ) { + let err = new Error("ostUserId can not be null"); + throw err; + } + + if( !this.ostWalletUIWorkflowCallback || !(this.ostWalletUIWorkflowCallback instanceof OstWalletUIWorkflowCallback) ) { + let err = new Error("ostWalletUIWorkflowCallback can not be null and must be an instanceof OstWalletUIWorkflowCallback"); + throw err; + } + + if(!props.navigation){ + console.warn("navigation is required for ost redemption ui sdk flow."); + } + + this.init(); + } + + initTheme(){ + OstThemeConfigHelper.updateConfig().then((res)=> { + this.props.navigation && this.props.navigation.setParams && this.props.navigation.setParams({"ostThemeUpdated": true}); + }).catch((error)=> {}) + } + + init(){ + if(!tokenHelper.token){ + tokenHelper.init(this.ostUserId).then(()=>{ + this.updateBalance(); + }).catch(()=>{}) + }else{ + this.updateBalance(); + } + } + + updateBalance(){ + OstJsonApi.getBalanceForUserId(this.ostUserId, (res) => { + let balance = res.balance && res.balance.available_balance; + balance = tokenHelper.toBtPrecision(tokenHelper.fromDecimal(balance)); + this.balanceInBt = balance; + this.props.navigation && this.props.navigation.setParams && this.props.navigation.setParams({ + balance + }) + }, () => {}); + } + + componentWillUnmount(){ + this.__setState = () => {}; + } + + __setState = (state) => { + if(!state) return; + this.setState(state); + } + + __onItemClick = (item , index) => { + if(typeof "function" == this.onItemClick){ + this.onItemClick(item , index); + } else { + this.props.navigation && this.props.navigation.push && this.props.navigation.push('RedeemableSkuDetails', {'redemptionSku': item, + 'ostUserId':this.ostUserId, + 'ostWalletUIWorkflowCallback': this.ostWalletUIWorkflowCallback, + 'balance': this.balanceInBt, + 'onPurchaseSuccess': this.onPurchaseSuccess + }); + } + } + + onPurchaseSuccess = ()=> { + this.updateBalance(); + } + + render(){ + return ( + + + );} +} + +export default OstRedeemableSkus; \ No newline at end of file diff --git a/js/Redemptions/RedeemableSkus/styles.js b/js/Redemptions/RedeemableSkus/styles.js new file mode 100644 index 0000000..de18ce5 --- /dev/null +++ b/js/Redemptions/RedeemableSkus/styles.js @@ -0,0 +1,63 @@ +import { StyleSheet } from 'react-native'; + +//@TODO cleanup + +let stylesMap = StyleSheet.create({ + container : { + flex: 1, + paddingVertical: 4, + marginTop: 10 + }, + headingWrapper : { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + padding: 30, + marginBottom: 50 + }, + logoSkipFont : { + // width: 100, + // height: 100, + alignSelf: 'center' + }, + title : { + alignSelf: 'center', + paddingHorizontal: 50, + marginBottom: 10 + }, + description : { + marginTop: 5, + paddingHorizontal: 50, + textAlign: 'center', + marginBottom: 30 + }, + item:{ + flex: 1, + margin:10, + backgroundColor: '#DBDBDB', + alignItems:'center', + justifyContent: 'center' + }, + itemWrapper: { + flex: 0.5, + aspectRatio:1 + }, + list:{ + marginHorizontal:10, + marginBottom : 10 + }, + noDataWrapper : { + flex:1, + justifyContent:'center', + alignItems:'center', + borderWidth: 1, + borderColor: '#2a293b', + padding:10, + borderRadius:8, + borderStyle: 'dashed' + } +}); + +export default styles = stylesMap; + + diff --git a/js/Redemptions/RedemableCustomConfig.js b/js/Redemptions/RedemableCustomConfig.js new file mode 100644 index 0000000..b1dfc1b --- /dev/null +++ b/js/Redemptions/RedemableCustomConfig.js @@ -0,0 +1,46 @@ +import OstRedemptionConfig from "./ost-redemption-config"; +import objectMerge from "lodash.merge"; + +class OstRedemableCustomConfig { + constructor(){ + const config = JSON.parse(JSON.stringify(OstRedemptionConfig)) || {}; + this.defaultConfig = config["themeingConfig"]; + this.config = {}; + } + + setConfig = (externalConfig={}) => { + this.config = objectMerge(this.defaultConfig, externalConfig); + } + + getBackArrowUri = () => { + return this.config && this.config.common && this.config.common["backArrow"]; + } + + getWalletIconUri = () => { + return this.config && this.config.common && this.config.common["walletIcon"]; + } + + getStoreIconUri = () => { + return this.config && this.config.common && this.config.common["storeIcon"]; + } + + getHeader = () => { + return this.config && this.config.skuListScreen && this.config.skuListScreen["header"]; + } + + getDescription = () => { + return this.config && this.config.skuListScreen && this.config.skuListScreen["description"]; + } + + getSkuListNavHeader = () => { + return this.config && this.config.skuListScreen && this.config.skuListScreen["navHeader"]; + } + + getSkuDetailsScreenNavHeader = () => { + return this.config && this.config.skuDetailsScreen && this.config.skuDetailsScreen["navHeader"]; + } + + +} + +export default new OstRedemableCustomConfig(); \ No newline at end of file diff --git a/js/Redemptions/RedemptionTransactionHelper.js b/js/Redemptions/RedemptionTransactionHelper.js new file mode 100644 index 0000000..057f34e --- /dev/null +++ b/js/Redemptions/RedemptionTransactionHelper.js @@ -0,0 +1,55 @@ +import TransactionHelper from "../TransactionHelper/OstTransactionHelper" +import {OstTransactionHelper , OstTransactionExecutor} from "../TransactionHelper/OstTransactionHelper"; +import OstWalletSdk from '../OstWalletSdk'; + +const duration = 60*10; + + +class OstRedemptionTransactionHelper extends OstTransactionHelper{ + constructor(){ + super(); + } + + executeDirectTransfer(userId, amounts, addresses, txMeta, redemptionDetails, transferDelegate) { + let obj = new OstRedemptionTransactionExecutor(userId, 'direct transfer', amounts, addresses, txMeta, redemptionDetails, transferDelegate); + obj.perform(); + return obj.uuid; + } +} + +class OstRedemptionTransactionExecutor extends OstTransactionExecutor { + + constructor(userId, ruleName, amounts, addresses, txMeta, redemptionDetails, transferDelegate ){ + super(userId, ruleName, amounts, addresses, txMeta, transferDelegate); + this.redemptionDetails = redemptionDetails ; + } + + getSpedingLimitAndExpiryTimeBucket(){ + let validBucket = super.getSpedingLimitAndExpiryTimeBucket(); + if (!TransactionHelper.isExternalConfig ) { + if( !validBucket ){ + validBucket = { + spending_limit: this.totalTxAmount, + expiration_time: duration + } + } + } + return validBucket; + } + + callExecuteTransfer(delegate){ + OstWalletSdk.executeTransaction( + this.userId, + this.addresses, + this.decimalAmounts, + this.ruleName, + this.txMeta, + delegate, + {"redemption_meta": this.redemptionDetails} + ) + } + +} + + +export default new OstRedemptionTransactionHelper() ; \ No newline at end of file diff --git a/js/Redemptions/index.js b/js/Redemptions/index.js new file mode 100644 index 0000000..7942657 --- /dev/null +++ b/js/Redemptions/index.js @@ -0,0 +1,5 @@ +import OstRedeemableSkus from "./RedeemableSkus"; +import OstRedeemableSkuDetails from "./RedeemableSkuDetails"; +import OstRedemableCustomConfig from "./RedemableCustomConfig" + +export { OstRedeemableSkus , OstRedeemableSkuDetails , OstRedemableCustomConfig} ; \ No newline at end of file diff --git a/js/Redemptions/ost-redemption-config.json b/js/Redemptions/ost-redemption-config.json new file mode 100644 index 0000000..fe03e9b --- /dev/null +++ b/js/Redemptions/ost-redemption-config.json @@ -0,0 +1,22 @@ +{ + "themeingConfig": { + "common": { + "backArrow": null, + "walletIcon": null, + "storeIcon": null + }, + "skuListScreen": { + "navHeader": null, + "header": null, + "description": null + }, + "skuDetailsScreen": { + "navHeader": null + } + }, + "transactionMeta" : { + "name": "redemption", + "type": "user_to_company" + } +} + diff --git a/js/TransactionHelper/OstTransactionHelper.js b/js/TransactionHelper/OstTransactionHelper.js index 85f43ab..4abba60 100644 --- a/js/TransactionHelper/OstTransactionHelper.js +++ b/js/TransactionHelper/OstTransactionHelper.js @@ -1,7 +1,6 @@ import BigNumber from 'bignumber.js'; import OstWalletSdk from '../OstWalletSdk'; import OstWalletSdkUI from '../OstWalletSdkUI'; -import {sdkErrorHelper, USER_UNAUTHORIZED, DEFAULT_CONTEXT} from '../helpers/OstSdkErrorHelper'; import OstWalletUIDelegate from '../OstWalletUIWorkflowCallback' import OstWalletDelegate from '../OstWalletWorkFlowCallback' import EventEmitter from 'eventemitter3'; @@ -12,13 +11,16 @@ import { setInstance } from '../callbackHandlers/OstWalletSdkUICallbackManager'; import OstWalletUIWorkFlowCallback from "../OstWalletUICoreCallback"; import defaultTransactionConfig from "./ost-transaction-config"; -import OstWalletUIWorkflowCallback from "@ostdotcom/ost-wallet-sdk-react-native/js/OstWalletUIWorkflowCallback"; -import OstRNError from "@ostdotcom/ost-wallet-sdk-react-native/js/OstRNError/OstRNError"; +import OstWalletUIWorkflowCallback from "../OstWalletUIWorkflowCallback"; +import OstRNError from "../OstRNError/OstRNError"; +import TokenHelper from "../helpers/TokenHelper"; let transactionConfig = {}; class OstTransactionHelper { + constructor() { + this.isExternalConfig = false; this.setTxConfig(); } @@ -34,6 +36,7 @@ class OstTransactionHelper { setTxConfig(externalConfig) { externalConfig = externalConfig || {}; + this.isExternalConfig = !!Object.keys(externalConfig).length; let masterConfig = JSON.parse(JSON.stringify(defaultTransactionConfig)); // Deep Merge. @@ -51,6 +54,28 @@ class OstTransactionHelper { return obj.uuid; } + + /** + * @param {*} amount - wei + * @param {*} decimal - Number + */ + static getValidBucket(amount , decimal){ + let validBucket = null; + if(!(amount instanceof BigNumber)){ + amount = new BigNumber(amount); + } + for(bucket of transactionConfig.session_buckets) { + let decimalSpedingLimit = TokenHelper.toDecimal(bucket.spending_limit , decimal) ; + let bucketSpendingLimit = new BigNumber(decimalSpedingLimit); + if (amount.lte(bucketSpendingLimit)) { + validBucket = {}; + Object.assign(validBucket, bucket) + validBucket.spending_limit = decimalSpedingLimit + break; + } + } + return validBucket + } } class OstTransactionExecutor { @@ -104,11 +129,15 @@ class OstTransactionExecutor { this.executeTransfer() }catch(err) { - let eName = OstWalletUIWorkFlowCallback.EVENTS.flowInterrupt; - this.ee.emit(eName, err.ostWorkflowContext , err.ostError); + this.onPerformCatch(err); } } + onPerformCatch(err){ + let eName = OstWalletUIWorkFlowCallback.EVENTS.flowInterrupt; + this.ee.emit(eName, err.ostWorkflowContext , err.ostError); + } + getOstUser() { return new Promise((resolve, reject) => { OstWalletSdk.getUser(this.userId, (ostUser) => { @@ -136,7 +165,7 @@ class OstTransactionExecutor { let totalAmount = new BigNumber('0'); for (amount of this.amounts) { - let decimalAmount = this.toDecimal(amount); + let decimalAmount = TokenHelper.toDecimal(amount, this.getTokenDecimal()); this.decimalAmounts.push(decimalAmount) let bnAmount = new BigNumber(decimalAmount); @@ -196,25 +225,7 @@ class OstTransactionExecutor { } getSpedingLimitAndExpiryTimeBucket() { - let validBucket = null; - for(bucket of transactionConfig.session_buckets) { - let decimalSpedingLimit = this.toDecimal(bucket.spending_limit) - let bucketSpendingLimit = new BigNumber(decimalSpedingLimit); - if (this.totalTxAmount.lte(bucketSpendingLimit)) { - validBucket = {}; - Object.assign(validBucket, bucket) - validBucket.spending_limit = decimalSpedingLimit - break; - } - } - - return validBucket - } - - toDecimal(val) { - val = BigNumber(val); - let exp = BigNumber(10).exponentiatedBy(this.getTokenDecimal()); - return val.multipliedBy(exp).toString(10); + return OstTransactionHelper.getValidBucket(this.totalTxAmount ,this.getTokenDecimal()); } executeTransfer() { @@ -234,6 +245,10 @@ class OstTransactionExecutor { this.ee.emit(eName, workflowContext , contextEntity); } + this.callExecuteTransfer(executeTxDelegate); + } + + callExecuteTransfer( executeTxDelegate ){ OstWalletSdk.executeTransaction( this.userId, this.addresses, @@ -249,4 +264,7 @@ class OstTransactionExecutor { } } -export default new OstTransactionHelper() \ No newline at end of file +export {OstTransactionExecutor , OstTransactionHelper }; + +export default new OstTransactionHelper(); + diff --git a/js/WalletSettings/OstWalletSettings.js b/js/WalletSettings/OstWalletSettings.js index 2d65530..6e5825b 100644 --- a/js/WalletSettings/OstWalletSettings.js +++ b/js/WalletSettings/OstWalletSettings.js @@ -3,7 +3,7 @@ import objectMerge from "lodash.merge"; import {sdkErrorHelper} from '../helpers/OstSdkErrorHelper' -const ItemsThatNeedCamera = ["add_another_device"]; +const ItemsThatNeedCamera = ["add_another_device", "authorize_browser_session"]; class OstWalletSettingsClass { constructor() { diff --git a/js/WalletSettings/SettingsComponent.js b/js/WalletSettings/SettingsComponent.js index 80fe455..06d2b8c 100644 --- a/js/WalletSettings/SettingsComponent.js +++ b/js/WalletSettings/SettingsComponent.js @@ -1,7 +1,7 @@ import React, {PureComponent} from 'react'; -import {Alert, FlatList, Linking, Platform, Text, TouchableWithoutFeedback, View} from 'react-native'; -import OstWalletSdkHelper from "../helpers/OstWalletSdkHelper"; -import WalletDetails from './WalletDetails' +import {FlatList, Text, TouchableWithoutFeedback, View} from 'react-native'; +import WalletDetails from './WalletDetails'; +import BackArrow from '../Redemptions/CommonComponents/BackArrow'; import inlineStyle from './styles' @@ -12,6 +12,7 @@ import OstWalletSettings from "@ostdotcom/ost-wallet-sdk-react-native/js/WalletS class SettingsComponent extends PureComponent { static navigationOptions = ({ navigation, navigationOptions }) => { + const isCustomBack = !!OstThemeConfigHelper.getBackArrowSource() ; let navigationParams = { title: navigation.getParam('navTitle', 'Wallet Settings'), headerStyle: { @@ -26,6 +27,10 @@ class SettingsComponent extends PureComponent { } }; + if(isCustomBack){ + navigationParams["headerBackImage"] = ; + } + return Object.assign(navigationParams, OstThemeConfigHelper.getNavigationHeaderConfig()); }; @@ -39,7 +44,7 @@ class SettingsComponent extends PureComponent { this.flatListLayout = null - OstThemeConfigHelper.updateConfig(); + this.initTheme(); let ostUserId = this.props.ostUserId; let delegate = this.props.ostWalletUIWorkflowCallback; @@ -54,6 +59,12 @@ class SettingsComponent extends PureComponent { this.controller = new WalletSettingsController(ostUserId, delegate); } + initTheme(){ + OstThemeConfigHelper.updateConfig().then((res)=> { + this.props.navigation && this.props.navigation.setParams && this.props.navigation.setParams({"ostThemeUpdated": true}); + }).catch((error)=> {}) + } + componentDidMount() { this.refreshList(); } diff --git a/js/WalletSettings/WalletSettingsController.js b/js/WalletSettings/WalletSettingsController.js index 1ecc8af..8d074fa 100644 --- a/js/WalletSettings/WalletSettingsController.js +++ b/js/WalletSettings/WalletSettingsController.js @@ -18,6 +18,7 @@ const optionIds = { viewMnemonics: "show_mnemonics", authorizeWithMnemonics: "authorize_device_with_mnemonics", authorizeWithQR: "add_another_device", + authorizeSessionWithQR: "authorize_browser_session", showQR: "show_device_qr_code", enableBiometrics: "enable_biometrics", disableBiometrics: "disable_biometrics", @@ -163,6 +164,7 @@ class WalletSettingsController { this._updateOptionsData(optionIds.addSession, false, true); this._updateOptionsData(optionIds.viewMnemonics, false, true); this._updateOptionsData(optionIds.authorizeWithQR, false, true); + this._updateOptionsData(optionIds.authorizeSessionWithQR, false, true); this._updateOptionsData(optionIds.resetPin, false, true); this._updateOptionsData(optionIds.revokeDevice, false, true); @@ -366,6 +368,10 @@ class WalletSettingsController { workflowId = OstWalletSdkUI.scanQRCodeToAuthorizeDevice(userId, delegate); break; + case optionIds.authorizeSessionWithQR: + workflowId = OstWalletSdkUI.scanQRCodeToAuthorizeSession(userId, delegate); + break; + case optionIds.showQR: workflowId = OstWalletSdkUI.getAddDeviceQRCode(userId, delegate); break; diff --git a/js/WalletSettings/ost-wallet-settings-config.json b/js/WalletSettings/ost-wallet-settings-config.json index b6d9bef..2fb06ee 100644 --- a/js/WalletSettings/ost-wallet-settings-config.json +++ b/js/WalletSettings/ost-wallet-settings-config.json @@ -2,14 +2,15 @@ "item_display_order": [ "activate_user", "wallet_details", - "recover_device", + "recover_device", "abort_recovery", - "add_session", - "reset_pin", - "show_mnemonics", + "add_session", + "reset_pin", + "show_mnemonics", "authorize_device_with_mnemonics", "revoke_device", "add_another_device", + "authorize_browser_session", "show_device_qr_code", "enable_biometrics", "disable_biometrics" @@ -21,7 +22,7 @@ "description": "View your wallet details." }, "config": { - "ost_view_endpoint": '' + "ost_view_endpoint": "" } }, "activate_user": { @@ -86,6 +87,12 @@ "description": "Scan QR Code to add another device." } }, + "authorize_browser_session": { + "content_config": { + "heading": "Authorize Browser Session", + "description": "Scan QR Code to authorize browser session." + } + }, "show_device_qr_code": { "content_config": { "heading": "Show Device QR Code", @@ -105,4 +112,4 @@ } } } -} \ No newline at end of file +} diff --git a/js/helpers/OstThemeConfigHelper.js b/js/helpers/OstThemeConfigHelper.js index 77e397a..0d5047d 100644 --- a/js/helpers/OstThemeConfigHelper.js +++ b/js/helpers/OstThemeConfigHelper.js @@ -3,7 +3,7 @@ import OstWalletSdkUI from '../OstWalletSdkUI' class OstThemeConfigHelper { constructor(){ this.themeConfig = {} - this.updateConfig() + this.updateConfig(); } getThemeConfig() { @@ -11,7 +11,7 @@ class OstThemeConfigHelper { } updateConfig() { - this._getThemeConfig() + return this._getThemeConfig() .then((themeConfig) => { this.themeConfig = themeConfig }) @@ -20,6 +20,7 @@ class OstThemeConfigHelper { _getThemeConfig() { return new Promise((resolve, reject) => { OstWalletSdkUI.getThemeConfig((themeConfig) => { + console.log("====", themeConfig); resolve(themeConfig) }) }); @@ -39,20 +40,83 @@ class OstThemeConfigHelper { } } + getH1Config() { + let h1Config = this.themeConfig.h1 + return { + color: h1Config.color, + fontSize: h1Config.size, + alignSelf: this.getTextAlignment(h1Config.alignment), + fontFamily: h1Config.font + } + } + + getH2Config() { + let h2Config = this.themeConfig.h2 + return { + color: h2Config.color, + fontSize: h2Config.size, + alignSelf: this.getTextAlignment(h2Config.alignment), + fontFamily: h2Config.font + } + } + + getH3Config() { + let h3Config = this.themeConfig.h3 + return { + color: h3Config.color, + fontSize: h3Config.size, + alignSelf: this.getTextAlignment(h3Config.alignment), + fontFamily: h3Config.font + } + } + + getH4Config() { + let h4Config = this.themeConfig.h4 + return { + color: h4Config.color, + fontSize: h4Config.size, + alignSelf: this.getTextAlignment(h4Config.alignment), + fontFamily: h4Config.font + } + } + getC1Config() { let c1Config = this.themeConfig.c1 - return {color: c1Config.color, - fontSize: c1Config.size, - alignSelf: this.getTextAlignment(c1Config.alignment), - fontFamily: c1Config.font} + return { + color: c1Config.color, + fontSize: c1Config.size, + alignSelf: this.getTextAlignment(c1Config.alignment), + fontFamily: c1Config.font + } } getC2Config() { let c2Config = this.themeConfig.c2; - return {color: c2Config.color, - fontSize: c2Config.size, - alignSelf: this.getTextAlignment(c2Config.alignment), - fontFamily: c2Config.font} + return { + color: c2Config.color, + fontSize: c2Config.size, + alignSelf: this.getTextAlignment(c2Config.alignment), + fontFamily: c2Config.font + } + } + + getB1Config() { + let b1Config = this.themeConfig.b1; + return { + color: b1Config.color, + fontSize: b1Config.size, + backgroundColor: b1Config.background_color, + fontFamily: b1Config.font + } + } + + getB1TextConfig() { + let b1Config = this.themeConfig.b1; + return { + color: b1Config.color, + fontSize: b1Config.size, + fontFamily: b1Config.font + } } getLinkConfig() { @@ -91,6 +155,23 @@ class OstThemeConfigHelper { headerTintColor: headerConfig.tint_color } } + + getFormFieldConfig() { + let nsConfig = this.themeConfig.form_field; + return { + color: nsConfig.color, + fontSize: nsConfig.size, + fontFamily: nsConfig.font, + borderColor: nsConfig.border_color, + textAlign: nsConfig.alignment + } + } + + getBackArrowSource(){ + const icons = this.themeConfig.icons || {} , + back = icons.back || {}; + return back.source; + } } export default new OstThemeConfigHelper() \ No newline at end of file diff --git a/js/helpers/TokenHelper.js b/js/helpers/TokenHelper.js new file mode 100644 index 0000000..097763c --- /dev/null +++ b/js/helpers/TokenHelper.js @@ -0,0 +1,97 @@ +import OstWalletSdk from '../OstWalletSdk'; +import BigNumber from 'bignumber.js'; + +class TokenHelper { + + constructor(){ + this.ostUserId = null; + this.tokenId = null; + this.user = null; + this.token = null; + } + + init(ostUserId) { + if(!ostUserId) return Promise.reject(); + this.ostUserId = ostUserId; + return this.getOstUser(ostUserId).then((user)=> { + const tokenId = user && user["token_id"]; + this.tokenId = tokenId; + return this.getOstToken(tokenId).then((token)=> { + this.token = token; + return { user , token }; + }).catch((error)=> { + //Ignore as there is no reject + }) + }).catch((error)=>{ + //Ignore as there is no reject + }); + } + + getOstUser(id) { + return new Promise((resolve, reject) => { + OstWalletSdk.getUser(id, (user) => { + if (user) { + this.user = user; + resolve(user); + } + }) + }) + } + + getOstToken(id) { + id = id && id.toString() || ""; + return new Promise((resolve, reject) => { + OstWalletSdk.getToken(id, (token) => { + this.token = token; + resolve(token) + }); + }); + } + + getTokenHolderAddress(){ + const auxChain = this.token && this.token.auxiliary_chains , + firstAuxChain = auxChain && auxChain[0], + companyTokenHolders = firstAuxChain && firstAuxChain["company_token_holders"] ; + return companyTokenHolders && companyTokenHolders[0] || ""; + } + + getDecimals(){ + return this.token["decimals"] + } + + getTokenSymbol(){ + return this.token["symbol"]; + } + + fromDecimal(val, decimals){ + decimals = decimals || this.getDecimals(); + if (!val || !decimals) return ''; + val = BigNumber(val); + let exp = BigNumber(10).exponentiatedBy(decimals); + return val.dividedBy(exp).toString(10); + } + + toDecimal(val, decimals){ + decimals = decimals || this.getDecimals(); + if (!val || !decimals) return ''; + val = BigNumber(val); + let exp = BigNumber(10).exponentiatedBy(decimals); + return val.multipliedBy(exp).toString(10); + } + + toBtPrecision(bt , precession=2){ + if (!bt) return ''; + bt = String(bt); + bt = BigNumber(bt); + return bt.decimalPlaces(precession, 1).toString(10); + } + + isBalSufficient(bal,value){ + let balance = new BigNumber(bal); + let purchaseValue = new BigNumber(value); + return balance.comparedTo(purchaseValue); + } + +} + +export default new TokenHelper(); \ No newline at end of file diff --git a/js/index.js b/js/index.js index 4b096e6..839fe3d 100644 --- a/js/index.js +++ b/js/index.js @@ -19,6 +19,7 @@ import OstWalletSdkUI from './OstWalletSdkUI'; import {OstWalletSettings, OstWalletSettingsComponent, OstWalletSettingsDefaultConfig} from './WalletSettings'; import OstUserStatus from "./constants/UserStatus"; import OstDeviceStatus from "./constants/DeviceStatus"; +import {OstRedeemableSkus, OstRedeemableSkuDetails, OstRedemableCustomConfig} from "./Redemptions"; import OstTransactionHelper from "./TransactionHelper/OstTransactionHelper"; export { @@ -35,5 +36,8 @@ export { OstWalletSettingsComponent, OstUserStatus, OstDeviceStatus, - OstTransactionHelper + OstTransactionHelper, + OstRedeemableSkus, + OstRedeemableSkuDetails, + OstRedemableCustomConfig } diff --git a/js/services/OstJsonApiPagination/BaseModel.js b/js/services/OstJsonApiPagination/BaseModel.js new file mode 100644 index 0000000..3fa38c4 --- /dev/null +++ b/js/services/OstJsonApiPagination/BaseModel.js @@ -0,0 +1,138 @@ +const VCErrors = { + AlreadyFetchingError: 'AlreadyFetchingError', + NoMoreRecords: 'NoMoreRecords', + InvalidApiResponse: 'InvalidApiResponse' +}; + +let idCnt = 1; + +let defaultOptions = { + removeDuplicate: true +}; + +class BaseModel { + constructor(params, id = 'ost_json_api_' + String(idCnt++), options = {}) { + this.id = id; + this.extraParams = params; + this.options = Object.assign({}, defaultOptions, options); + this.initVals(); + } + + initVals() { + this.isFetching = false; + this.hasNextPage = true; + this.nextPagePayload = null; + this.results = []; + this.meta = null; + this.resultMap = {}; + } + + refresh() { + this.initVals(); + return this.fetch(); + } + + getParams() { + let params = {}; + if (this.extraParams) { + Object.assign(params, this.extraParams); + } + if (this.nextPagePayload) { + Object.assign(params, this.nextPagePayload); + } + return params; + } + + fetch() { + if (this.isFetching) { + return Promise.reject({ + code_error: VCErrors.AlreadyFetchingError + }); + } + + if (!this.hasNextPage) { + return Promise.reject({ + code_error: VCErrors.NoMoreRecords + }); + } + + this.isFetching = true; + return this.fetchFromJsonApi(); + } + + fetchFromJsonApi(){ + //Overwrite + throw "fetchFromJsonApi"; + } + + dataReceived(response) { + let meta = response.meta; + this.nextPagePayload = meta ? meta.next_page_payload : null; + this.meta = meta; + this.hasNextPage = this.nextPagePayload ? true : false; + let dataToAppend = this.processData(response); + this.isFetching = false; + return dataToAppend; + } + + processData(response) { + let resultType = response.result_type; + if (!resultType || !response[resultType]) { + response.code_error = VCErrors.InvalidApiResponse; + // Invalid response. + throw response; + } + let results = response[resultType]; + if (!(results instanceof Array)) { + response.code_error = VCErrors.InvalidApiResponse; + // Invalid response. + throw response; + } + return this.processResults( results, response ); + } + + processResults(results, response) { + let cleanedUpList = []; + let cnt = 0, + len = results.length; + for (; cnt < len; cnt++) { + let result = results[cnt]; + let resultId = result.id; + + // Format response. + result = this.formatResult(result, response); + if (!result) { + // Some wrong entry. + continue; + } + let existingResult = this.resultMap[resultId]; + // Update existing result if available. + if (existingResult && this.options.removeDuplicate) { + Object.assign(existingResult, result); + continue; + } + + // Add new result. + this.resultMap[resultId] = result; + this.results.push(result); + cleanedUpList.push(result); + } + return cleanedUpList; + } + + formatResult(result, response) { + return result; + } + + getAllResults() { + return this.results; + } + + clone() { + //Overwrite + throw "clone"; + } + +} + +export { BaseModel, VCErrors }; diff --git a/js/services/OstJsonApiPagination/Pagination.js b/js/services/OstJsonApiPagination/Pagination.js new file mode 100644 index 0000000..0cdc2d6 --- /dev/null +++ b/js/services/OstJsonApiPagination/Pagination.js @@ -0,0 +1,100 @@ +class Pagination { + + /** + * @param {*} modelFetch + * @param {*} callbacks + * NOTE params were added later m ideally it should be second parameter to the constructor + * but can risk it now to change the signature everywhere + */ + constructor(modelFetch, callbacks) { + this.modelFetch = modelFetch ; + this.callbacks = callbacks || {}; + this.refreshing = false ; + this.loadingNext = false ; + } + + initPagination() { + this.refresh(this.modelFetch); + } + + refresh(modelFetch) { + if (this.refreshing) return; + if (modelFetch) { + this.modelFetch = modelFetch; + } else { + this.modelFetch = this.modelFetch && this.modelFetch.clone(); + } + if(!this.modelFetch) return; + this.beforeRefresh(); + this.modelFetch + .refresh() + .then((res) => { + this.onRefresh(res); + }) + .catch((error) => { + this.onRefreshError(error); + }); + }; + + beforeRefresh() { + this.refreshing = true ; + this.callbacks.beforeRefresh && this.callbacks.beforeRefresh(); + } + + onRefresh(res) { + this.refreshing = false ; + this.callbacks.onRefresh && this.callbacks.onRefresh( res ); + } + + onRefreshError(error) { + this.refreshing = false ; + this.callbacks.onRefreshError && this.callbacks.onRefreshError(error); + } + + /** + * getNext monitors for 4 different checkpoints + * 1. It wont call next page if allready fetching data of previous page + * 2. Wont next page when pull to refresh is done + * 3. Will stop pagination if next page payload is not present + */ + getNext(){ + if ( + this.loadingNext || + this.refreshing || + !this.modelFetch.hasNextPage + ) + return; + this.beforeNext(); + this.modelFetch + .fetch() + .then((res) => { + this.onNext(res); + }) + .catch((error) => { + this.onNextError(error); + }); + }; + + beforeNext() { + this.loadingNext = true ; + this.callbacks.beforeNext && this.callbacks.beforeNext(); + } + + onNext(res) { + this.loadingNext = false ; + this.callbacks.onNext && this.callbacks.onNext( res ); + } + + onNextError(error) { + this.loadingNext = false ; + this.callbacks.onNextError && this.callbacks.onNextError(error); + } + + getResults(){ + return this.modelFetch.getAllResults(); + } + + }; + + +export default Pagination ; \ No newline at end of file diff --git a/js/services/OstJsonApiPagination/RedemptionSkusModel.js b/js/services/OstJsonApiPagination/RedemptionSkusModel.js new file mode 100644 index 0000000..b1106e2 --- /dev/null +++ b/js/services/OstJsonApiPagination/RedemptionSkusModel.js @@ -0,0 +1,30 @@ +import OstJsonApi from "../../OstJsonApi"; +import {BaseModel} from "./BaseModel"; + +class RedemptionSkusModel extends BaseModel { + + constructor(userId , params, id, options ){ + super(params, id, options); + this.userId = userId; + } + + fetchFromJsonApi(){ + return new Promise((resolve, reject) => { + OstJsonApi.getRedeemableSkus(this.userId, this.getParams() , (respones) => { + this.isFetching = false; + return resolve(this.dataReceived(respones)); + }, (error)=> { + this.isFetching = false; + return reject(error); + }) + }); + } + + clone() { + let Constructor = this.constructor; + return new Constructor(this.userId , this.extraParams, this.id, this.options); + } + +} + +export default RedemptionSkusModel; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..a7f7e08 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,21 @@ +{ + "name": "@ostdotcom/ost-wallet-sdk-react-native", + "version": "2.3.14", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + }, + "react-native-picker-select": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/react-native-picker-select/-/react-native-picker-select-6.5.0.tgz", + "integrity": "sha512-VuzQAzs3h1PvKH91OAomWqCqzimSttUFHj1jNzC9K2FZ5OG/OuTo2ri3KkBdPU1PjtakPg6OMxxNBANAXgmceA==", + "requires": { + "lodash.isequal": "^4.5.0" + } + } + } +} diff --git a/package.json b/package.json index 14bb376..71ab591 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ostdotcom/ost-wallet-sdk-react-native", - "version": "2.3.14", + "version": "2.4.1", "description": "OST Wallet SDK for React Native applications.", "main": "./js/index.js", "repository": { @@ -24,9 +24,10 @@ "author": "OST.com Inc.", "license": " Apache-2.0", "peerDependencies": { - "react-native": "^0.60.0", - "eventemitter3": "^4.0.0", - "lodash.merge": "^4.6.2", - "bignumber.js": "^8.1.1" + "react-native": "0.60.0", + "eventemitter3": "4.0.0", + "lodash.merge": "4.6.2", + "bignumber.js": "8.1.1", + "react-native-picker-select": "6.5.0" } }