Skip to content

Feat: headless ios#37

Open
Shivam25092001 wants to merge 3 commits into
refactor-apisfrom
feat-headless-ios
Open

Feat: headless ios#37
Shivam25092001 wants to merge 3 commits into
refactor-apisfrom
feat-headless-ios

Conversation

@Shivam25092001

Copy link
Copy Markdown
Contributor

ADDED

  • Headless methods ios.

Usage GUIDE

1. Initialize the SDK

import { HyperInit } from '@juspay-tech/react-native-hyperswitch';

// Initialize once at app startup
const hyper = HyperInit({
  publishableKey: 'YOUR_PUBLISHABLE_KEY',
  profileId: 'YOUR_PROFILE_ID',
  // Optional configuration
  customConfig: {
    customBackendUrl: 'https://your-backend.com',
    customLogUrl: 'https://your-logs.com',
  },
});

2. Wrap Your App with HyperElements

import { HyperElements } from '@juspay-tech/react-native-hyperswitch';

function App() {
  return (
    <HyperElements
      hyper={hyper}
      options={{
        clientSecret: 'PAYMENT_INTENT_CLIENT_SECRET',
        sdkAuthorisation: 'SDK_AUTH_TOKEN', // optional
      }}
    >
      <PaymentScreen />
    </HyperElements>
  );
}

Headless Payments

For custom UIs, use the PaymentSession API for headless (UI-less) payment processing.

import { initPaymentSession } from '@juspay-tech/react-native-hyperswitch';

async function handleHeadlessPayment() {
  // Initialize payment session
  // Note: This internally initializes the handler and prepares the session
  const paymentSession = await initPaymentSession(
    hyper,
    'PAYMENT_INTENT_CLIENT_SECRET'
  );

  // Get default payment method
  const defaultMethod = await paymentSession.getCustomerDefaultSavedPaymentMethodData();
  console.log('Default method:', defaultMethod);

  // Get last used payment method
  const lastUsedMethod = await paymentSession.getCustomerLastUsedPaymentMethodData();
  console.log('Last used method:', lastUsedMethod);

  // Confirm with default payment method
  const result = await paymentSession.confirmWithCustomerDefaultPaymentMethod();
  console.log('Confirmation result:', result);

  // Or confirm with last used payment method
  const result2 = await paymentSession.confirmWithCustomerLastUsedPaymentMethod();
  console.log('Confirmation result:', result2);
}

@Shivam25092001 Shivam25092001 changed the base branch from main to refactor-apis March 26, 2026 21:03
@Shivam25092001 Shivam25092001 requested review from 25harsh, Copilot and sh-iv-am and removed request for 25harsh, Copilot and sh-iv-am March 26, 2026 21:03

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a headless (UI-less) payment session API intended for iOS, wiring new native methods through the React Native module and exposing a PaymentSession interface to JS, plus an example app screen demonstrating usage.

Changes:

  • Introduces headless response parsing (ResponseHandler) and a JS-facing initPaymentSession that returns a PaymentSession object with headless methods.
  • Adds iOS native bridge methods for saved payment methods + confirming with default/last-used methods.
  • Updates the example app to include a “Headless Mode” tab and UI for exercising the new APIs.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/@juspay-tech/react-native-hyperswitch/src/utils/ResponseHandler.res Adds parsing helpers for standardized native headless responses.
packages/@juspay-tech/react-native-hyperswitch/src/types/HyperTypes.res Adds headlessResponse and paymentSession types for JS consumption.
packages/@juspay-tech/react-native-hyperswitch/src/specs/NativeHyperswitchSdkReactNative.ts Extends the native spec with headless methods.
packages/@juspay-tech/react-native-hyperswitch/src/modules/NativeHyperswitchSdk.res Adds ReScript externals/types for headless native methods and saved PM data shapes.
packages/@juspay-tech/react-native-hyperswitch/src/index.tsx Exposes initPaymentSession and headless-related types from the package entrypoint.
packages/@juspay-tech/react-native-hyperswitch/src/hooks/useWidget.res Tightens typing around presentPaymentSheet promise resolution.
packages/@juspay-tech/react-native-hyperswitch/src/core/Hyper.res Implements initPaymentSession returning a JS PaymentSession wrapper.
packages/@juspay-tech/react-native-hyperswitch/ios/Modules/ReactNative/HyperswitchSdkReactNative.mm Exports new iOS headless methods to React Native.
packages/@juspay-tech/react-native-hyperswitch/ios/Modules/ReactNative/HyperswitchModule.swift Implements iOS headless method behavior and response payloads.
example/src/styles.ts Adds styles for headless UI.
example/src/UIScreen.tsx Renames/updates UI-mode example screen.
example/src/HeadlessScreen.tsx Adds a new headless-mode example screen using PaymentSession.
example/src/App.tsx Adds a simple tab switcher between UI and headless examples.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +74 to +76
self.paymentSession?.getCustomerSavedPaymentMethods(initSavedPaymentMethodSessionCallback)

resolve(["status": "success", "message": "Payment methods initialized"])

Copilot AI Mar 26, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getCustomerSavedPaymentMethods resolves the promise immediately after calling paymentSession?.getCustomerSavedPaymentMethods(...), but PaymentSessionHandler is provided asynchronously via PaymentSession.headlessCompletion (see PaymentSession+UIKit.swift). This can race with JS calling getCustomerDefaultSavedPaymentMethodData/confirm... and hit the "handler not initialized" path even though initialization was reported as success. Consider resolving only after the callback sets paymentSessionHandler (or return an error if paymentSession is nil).

Suggested change
self.paymentSession?.getCustomerSavedPaymentMethods(initSavedPaymentMethodSessionCallback)
resolve(["status": "success", "message": "Payment methods initialized"])
guard let paymentSession = self.paymentSession else {
resolve([
"status": "error",
"code": "UNKNOWN",
"message": "Payment session not initialized."
])
return
}
paymentSession.getCustomerSavedPaymentMethods { [weak self] handler in
self?.initSavedPaymentMethodSessionCallback(handler: handler)
resolve(["status": "success", "message": "Payment methods initialized"])
}

Copilot uses AI. Check for mistakes.
Comment thread packages/@juspay-tech/react-native-hyperswitch/src/core/Hyper.res
headlessResponseStatus as HeadlessResponseStatus,
cardDetails as CardDetails,
savedPaymentMethod as SavedPaymentMethod,
} from './modules/NativeHyperswitchSdk.gen';

Copilot AI Mar 26, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HeadlessResponse / HeadlessResponseStatus are exported from NativeHyperswitchSdk.gen, but PaymentSession methods actually return HyperTypes.headlessResponse (status strings like "success"/"failed"/"cancelled"/"error" with optional code/data). The exported status union ("succeeded"/"requires_action"/...) doesn’t match the iOS responses implemented in HyperswitchModule.swift, and conflicts with the example usage checking for status === "success". Export the headless response type from HyperTypes.gen (or align the native/status model so both definitions match).

Suggested change
} from './modules/NativeHyperswitchSdk.gen';
} from './types/HyperTypes.gen';

Copilot uses AI. Check for mistakes.
let status =
dict
->Js.Dict.get("status")
->Belt.Option.flatMap(Js.Json.decodeString)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use core rescript methods, remove Belt and Js methods

public func getCustomerSavedPaymentMethods(
withResolve resolve: @escaping RCTPromiseResolveBlock,
reject: @escaping RCTPromiseRejectBlock
) -> Void {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove -> Void

Comment on lines +96 to +115
if let jsonData = try? JSONEncoder().encode(paymentMethod),
let jsonDict = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any] {
resolve([
"status": "success",
"message": "Default payment method retrieved",
"data": jsonDict
])
} else {
resolve([
"status": "error",
"code": "ENCODE_ERROR",
"message": "Failed to encode payment method data"
])
}
case .failure(let error):
resolve([
"status": "failed",
"code": error.code,
"message": error.message
])

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can try to make it passthrough

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants