Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 64 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,14 @@ In `Runner/Info.plist` add or modify the `LSApplicationQueriesSchemes` key so it
#### Get list of installed apps

```dart
final List<ApplicationMeta> appMetaList = await UpiPay.getInstalledUpiApps();
final List<ApplicationMeta> appMetaList = await UpiPay.getInstalledUpiApplications();
```

Filter for mandate-capable apps:

```dart
final List<ApplicationMeta> mandateApps =
await UpiPay.getInstalledUpiApplications(isForMandateApps: true);
```

#### Show an app's details
Expand Down Expand Up @@ -96,6 +103,61 @@ Future doUpiTransation(ApplicationMeta appMeta) {
}
```

Send a mandate request (uses `upi://mandate`):

```dart
Future doUpiMandate(ApplicationMeta appMeta) {
return UpiPay.initiateTransaction(
amount: '100.00',
app: appMeta.application,
receiverName: 'John Doe',
receiverUpiAddress: 'john@doe',
transactionRef: 'UPIMANDATE0001',
transactionNote: 'UPI Mandate',
isForMandate: true,
);
}
```

#### Mandate parameters

Mandate requests can include additional parameters when `isForMandate: true`.
Only non-null values are sent to the UPI app.

```dart
Future doUpiMandateWithParams(ApplicationMeta appMeta) {
return UpiPay.initiateTransaction(
amount: '250.00',
app: appMeta.application,
receiverName: 'Acme Subscriptions',
receiverUpiAddress: 'acme@upi',
transactionRef: 'UPIMANDATE0002',
transactionNote: 'Monthly plan',
isForMandate: true,
// Mandate-specific parameters
amountRule: 'MAX', // amrule
blockFlag: 'Y', // block
merchantName: 'ACME', // mn
mode: 'UPI', // mode
orgId: 'ACME001', // orgid
purpose: '00', // purpose
recurrence: 'MONTHLY', // recur
recurrenceType: 'BEFORE', // recurtype
recurrenceValue: '1', // recurvalue
revocable: 'Y', // rev
transactionId: 'TID12345', // tid
txnType: 'CREATE', // txnType
validityStart: '01012025', // validitystart
validityEnd: '31122025', // validityend
);
}
```

Mandate-capable apps can be filtered via
`UpiPay.getInstalledUpiApplications(isForMandateApps: true)`. Availability
varies by app and platform, so verify with the target UPI app before production
use.

## Behaviour, Limitations & Measures

### Android
Expand All @@ -116,7 +178,7 @@ It is advised that you implement a server-side payment verification on top of th
#### Flow

- On iOS, the [UPI Deep Linking And Proximity Integration Specification](https://github.com/reeteshranjan/upi_pay/files/6338127/UPI.Linking.Specs_ver.1.6.pdf) is implemented using iOS custom schemes.
- Each UPI payment app can listen to a payment request of the form `upi://pay?...` sent by a caller app to iOS.
- Each UPI payment app can listen to a payment request of the form `upi://pay?...` or `upi://mandate?...` sent by a caller app to iOS.
- The specification does not let you specify the target app's identifier in this request. On iOS, there is no other disambiguation measure available such as any ordering of the UPI payment apps that can be retrieved using any iOS APIs. Hence, it's impossible to know which UPI payment app will be invoked.
- One of the applicable apps gets invoked and it processes the payment. The custom schemes mechanism has no way to return a transaction status to your calling code. The calling code can only know if a UPI payment app was launched successfully or not.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class UpiPayPlugin: FlutterPlugin, MethodCallHandler, ActivityAware, PluginRegis

when (call.method) {
"initiateTransaction" -> this.initiateTransaction(call)
"getInstalledUpiApps" -> this.getInstalledUpiApps()
"getInstalledUpiApps" -> this.getInstalledUpiApps(call)
else -> result.notImplemented()
}
}
Expand All @@ -55,6 +55,23 @@ class UpiPayPlugin: FlutterPlugin, MethodCallHandler, ActivityAware, PluginRegis
val am: String? = call.argument("am")
val cu: String? = call.argument("cu")
val url: String? = call.argument("url")
val isForMandate: Boolean? = call.argument("isForMandate")

// Mandate-specific parameters
val amrule: String? = call.argument("amrule")
val block: String? = call.argument("block")
val mn: String? = call.argument("mn")
val mode: String? = call.argument("mode")
val orgid: String? = call.argument("orgid")
val purpose: String? = call.argument("purpose")
val recur: String? = call.argument("recur")
val recurtype: String? = call.argument("recurtype")
val recurvalue: String? = call.argument("recurvalue")
val rev: String? = call.argument("rev")
val tid: String? = call.argument("tid")
val txnType: String? = call.argument("txnType")
val validitystart: String? = call.argument("validitystart")
val validityend: String? = call.argument("validityend")

try {
/*
Expand All @@ -63,7 +80,8 @@ class UpiPayPlugin: FlutterPlugin, MethodCallHandler, ActivityAware, PluginRegis
* 'abc 40upi' by these apps. The URI building logic is changed to avoid URL encoding
* of the value of 'pa' parameter. - Prince
*/
var uriStr: String? = "upi://pay?pa=" + pa +
val authority = if (isForMandate == true) "mandate" else "pay"
var uriStr: String? = "upi://$authority?pa=" + pa +
"&pn=" + Uri.encode(pn) +
"&tr=" + Uri.encode(tr) +
"&am=" + Uri.encode(am) +
Expand All @@ -77,7 +95,56 @@ class UpiPayPlugin: FlutterPlugin, MethodCallHandler, ActivityAware, PluginRegis
if(tn != null) {
uriStr += ("&tn=" + Uri.encode(tn))
}
uriStr += "&mode=00" // &orgid=000000"

// Add mandate-specific parameters if available
if(isForMandate == true) {
if(amrule != null) {
uriStr += ("&amrule=" + Uri.encode(amrule))
}
if(block != null) {
uriStr += ("&block=" + Uri.encode(block))
}
if(mn != null) {
uriStr += ("&mn=" + Uri.encode(mn))
}
if(mode != null) {
uriStr += ("&mode=" + Uri.encode(mode))
}
if(orgid != null) {
uriStr += ("&orgid=" + Uri.encode(orgid))
}
if(purpose != null) {
uriStr += ("&purpose=" + Uri.encode(purpose))
}
if(recur != null) {
uriStr += ("&recur=" + Uri.encode(recur))
}
if(recurtype != null) {
uriStr += ("&recurtype=" + Uri.encode(recurtype))
}
if(recurvalue != null) {
uriStr += ("&recurvalue=" + Uri.encode(recurvalue))
}
if(rev != null) {
uriStr += ("&rev=" + Uri.encode(rev))
}
if(tid != null) {
uriStr += ("&tid=" + Uri.encode(tid))
}
if(txnType != null) {
uriStr += ("&txnType=" + Uri.encode(txnType))
}
if(validitystart != null) {
uriStr += ("&validitystart=" + Uri.encode(validitystart))
}
if(validityend != null) {
uriStr += ("&validityend=" + Uri.encode(validityend))
}
} else {
// For non-mandate transactions, add default mode if not provided
uriStr += "&mode=00"
}

val uri = Uri.parse(uriStr)
Log.d("flutter_upi_india", "initiateTransaction URI: " + uri.toString())

Expand All @@ -97,9 +164,14 @@ class UpiPayPlugin: FlutterPlugin, MethodCallHandler, ActivityAware, PluginRegis
}
}

private fun getInstalledUpiApps() {
private fun getInstalledUpiApps(call: MethodCall) {
val isForMandateApps: Boolean? = call.argument("isForMandateApps")
val uriBuilder = Uri.Builder()
uriBuilder.scheme("upi").authority("pay")
if(isForMandateApps == true){
uriBuilder.scheme("upi").authority("mandate")
} else {
uriBuilder.scheme("upi").authority("pay")
}

val uri = uriBuilder.build()
val intent = Intent(Intent.ACTION_VIEW, uri)
Expand Down Expand Up @@ -197,4 +269,4 @@ class UpiPayPlugin: FlutterPlugin, MethodCallHandler, ActivityAware, PluginRegis
activity = null
}

}
}
5 changes: 2 additions & 3 deletions doc/api/flutter_upi_india/UpiPay-class.html
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ <h2>Operators</h2>
<h2>Static Methods</h2>
<dl class="callables">
<dt id="getInstalledUpiApplications" class="callable">
<span class="name"><a href="../flutter_upi_india/UpiPay/getInstalledUpiApplications.html">getInstalledUpiApplications</a></span><span class="signature">(<wbr><span class="parameter" id="getInstalledUpiApplications-param-paymentType">{<span class="type-annotation"><a href="../flutter_upi_india/UpiApplicationDiscoveryAppPaymentType.html">UpiApplicationDiscoveryAppPaymentType</a></span> <span class="parameter-name">paymentType</span> = <span class="default-value">UpiApplicationDiscoveryAppPaymentType.nonMerchant</span>, </span><span class="parameter" id="getInstalledUpiApplications-param-statusType"><span class="type-annotation"><a href="../flutter_upi_india/UpiApplicationDiscoveryAppStatusType.html">UpiApplicationDiscoveryAppStatusType</a></span> <span class="parameter-name">statusType</span> = <span class="default-value">UpiApplicationDiscoveryAppStatusType.working</span>}</span>)
<span class="name"><a href="../flutter_upi_india/UpiPay/getInstalledUpiApplications.html">getInstalledUpiApplications</a></span><span class="signature">(<wbr><span class="parameter" id="getInstalledUpiApplications-param-paymentType">{<span class="type-annotation"><a href="../flutter_upi_india/UpiApplicationDiscoveryAppPaymentType.html">UpiApplicationDiscoveryAppPaymentType</a></span> <span class="parameter-name">paymentType</span> = <span class="default-value">UpiApplicationDiscoveryAppPaymentType.nonMerchant</span>, </span><span class="parameter" id="getInstalledUpiApplications-param-statusType"><span class="type-annotation"><a href="../flutter_upi_india/UpiApplicationDiscoveryAppStatusType.html">UpiApplicationDiscoveryAppStatusType</a></span> <span class="parameter-name">statusType</span> = <span class="default-value">UpiApplicationDiscoveryAppStatusType.working</span>, </span><span class="parameter" id="getInstalledUpiApplications-param-isForMandateApps"><span class="type-annotation"><a href="https://api.flutter.dev/flutter/dart-core/bool-class.html">bool</a></span> <span class="parameter-name">isForMandateApps</span> = <span class="default-value">false</span>}</span>)
<span class="returntype parameter">&#8594; <a href="https://api.flutter.dev/flutter/dart-async/Future-class.html">Future</a><span class="signature">&lt;<wbr><span class="type-parameter"><a href="https://api.flutter.dev/flutter/dart-core/List-class.html">List</a><span class="signature">&lt;<wbr><span class="type-parameter"><a href="../flutter_upi_india/ApplicationMeta-class.html">ApplicationMeta</a></span>&gt;</span></span>&gt;</span></span>
</span>

Expand All @@ -183,7 +183,7 @@ <h2>Static Methods</h2>
</dd>

<dt id="initiateTransaction" class="callable">
<span class="name"><a href="../flutter_upi_india/UpiPay/initiateTransaction.html">initiateTransaction</a></span><span class="signature">(<wbr><span class="parameter" id="initiateTransaction-param-app">{<span>required</span> <span class="type-annotation"><a href="../flutter_upi_india/UpiApplication-class.html">UpiApplication</a></span> <span class="parameter-name">app</span>, </span><span class="parameter" id="initiateTransaction-param-receiverUpiAddress"><span>required</span> <span class="type-annotation"><a href="https://api.flutter.dev/flutter/dart-core/String-class.html">String</a></span> <span class="parameter-name">receiverUpiAddress</span>, </span><span class="parameter" id="initiateTransaction-param-receiverName"><span>required</span> <span class="type-annotation"><a href="https://api.flutter.dev/flutter/dart-core/String-class.html">String</a></span> <span class="parameter-name">receiverName</span>, </span><span class="parameter" id="initiateTransaction-param-transactionRef"><span>required</span> <span class="type-annotation"><a href="https://api.flutter.dev/flutter/dart-core/String-class.html">String</a></span> <span class="parameter-name">transactionRef</span>, </span><span class="parameter" id="initiateTransaction-param-amount"><span>required</span> <span class="type-annotation"><a href="https://api.flutter.dev/flutter/dart-core/String-class.html">String</a></span> <span class="parameter-name">amount</span>, </span><span class="parameter" id="initiateTransaction-param-url"><span class="type-annotation"><a href="https://api.flutter.dev/flutter/dart-core/String-class.html">String</a>?</span> <span class="parameter-name">url</span>, </span><span class="parameter" id="initiateTransaction-param-transactionNote"><span class="type-annotation"><a href="https://api.flutter.dev/flutter/dart-core/String-class.html">String</a>?</span> <span class="parameter-name">transactionNote</span>}</span>)
<span class="name"><a href="../flutter_upi_india/UpiPay/initiateTransaction.html">initiateTransaction</a></span><span class="signature">(<wbr><span class="parameter" id="initiateTransaction-param-app">{<span>required</span> <span class="type-annotation"><a href="../flutter_upi_india/UpiApplication-class.html">UpiApplication</a></span> <span class="parameter-name">app</span>, </span><span class="parameter" id="initiateTransaction-param-receiverUpiAddress"><span>required</span> <span class="type-annotation"><a href="https://api.flutter.dev/flutter/dart-core/String-class.html">String</a></span> <span class="parameter-name">receiverUpiAddress</span>, </span><span class="parameter" id="initiateTransaction-param-receiverName"><span>required</span> <span class="type-annotation"><a href="https://api.flutter.dev/flutter/dart-core/String-class.html">String</a></span> <span class="parameter-name">receiverName</span>, </span><span class="parameter" id="initiateTransaction-param-transactionRef"><span>required</span> <span class="type-annotation"><a href="https://api.flutter.dev/flutter/dart-core/String-class.html">String</a></span> <span class="parameter-name">transactionRef</span>, </span><span class="parameter" id="initiateTransaction-param-amount"><span>required</span> <span class="type-annotation"><a href="https://api.flutter.dev/flutter/dart-core/String-class.html">String</a></span> <span class="parameter-name">amount</span>, </span><span class="parameter" id="initiateTransaction-param-url"><span class="type-annotation"><a href="https://api.flutter.dev/flutter/dart-core/String-class.html">String</a>?</span> <span class="parameter-name">url</span>, </span><span class="parameter" id="initiateTransaction-param-merchantCode"><span class="type-annotation"><a href="https://api.flutter.dev/flutter/dart-core/String-class.html">String</a>?</span> <span class="parameter-name">merchantCode</span>, </span><span class="parameter" id="initiateTransaction-param-transactionNote"><span class="type-annotation"><a href="https://api.flutter.dev/flutter/dart-core/String-class.html">String</a>?</span> <span class="parameter-name">transactionNote</span>, </span><span class="parameter" id="initiateTransaction-param-isForMandate"><span class="type-annotation"><a href="https://api.flutter.dev/flutter/dart-core/bool-class.html">bool</a></span> <span class="parameter-name">isForMandate</span> = <span class="default-value">false</span>, </span><span class="parameter" id="initiateTransaction-param-amountRule"><span class="type-annotation"><a href="https://api.flutter.dev/flutter/dart-core/String-class.html">String</a>?</span> <span class="parameter-name">amountRule</span>, </span><span class="parameter" id="initiateTransaction-param-blockFlag"><span class="type-annotation"><a href="https://api.flutter.dev/flutter/dart-core/String-class.html">String</a>?</span> <span class="parameter-name">blockFlag</span>, </span><span class="parameter" id="initiateTransaction-param-merchantName"><span class="type-annotation"><a href="https://api.flutter.dev/flutter/dart-core/String-class.html">String</a>?</span> <span class="parameter-name">merchantName</span>, </span><span class="parameter" id="initiateTransaction-param-mode"><span class="type-annotation"><a href="https://api.flutter.dev/flutter/dart-core/String-class.html">String</a>?</span> <span class="parameter-name">mode</span>, </span><span class="parameter" id="initiateTransaction-param-orgId"><span class="type-annotation"><a href="https://api.flutter.dev/flutter/dart-core/String-class.html">String</a>?</span> <span class="parameter-name">orgId</span>, </span><span class="parameter" id="initiateTransaction-param-purpose"><span class="type-annotation"><a href="https://api.flutter.dev/flutter/dart-core/String-class.html">String</a>?</span> <span class="parameter-name">purpose</span>, </span><span class="parameter" id="initiateTransaction-param-recurrence"><span class="type-annotation"><a href="https://api.flutter.dev/flutter/dart-core/String-class.html">String</a>?</span> <span class="parameter-name">recurrence</span>, </span><span class="parameter" id="initiateTransaction-param-recurrenceType"><span class="type-annotation"><a href="https://api.flutter.dev/flutter/dart-core/String-class.html">String</a>?</span> <span class="parameter-name">recurrenceType</span>, </span><span class="parameter" id="initiateTransaction-param-recurrenceValue"><span class="type-annotation"><a href="https://api.flutter.dev/flutter/dart-core/String-class.html">String</a>?</span> <span class="parameter-name">recurrenceValue</span>, </span><span class="parameter" id="initiateTransaction-param-revocable"><span class="type-annotation"><a href="https://api.flutter.dev/flutter/dart-core/String-class.html">String</a>?</span> <span class="parameter-name">revocable</span>, </span><span class="parameter" id="initiateTransaction-param-transactionId"><span class="type-annotation"><a href="https://api.flutter.dev/flutter/dart-core/String-class.html">String</a>?</span> <span class="parameter-name">transactionId</span>, </span><span class="parameter" id="initiateTransaction-param-txnType"><span class="type-annotation"><a href="https://api.flutter.dev/flutter/dart-core/String-class.html">String</a>?</span> <span class="parameter-name">txnType</span>, </span><span class="parameter" id="initiateTransaction-param-validityStart"><span class="type-annotation"><a href="https://api.flutter.dev/flutter/dart-core/String-class.html">String</a>?</span> <span class="parameter-name">validityStart</span>, </span><span class="parameter" id="initiateTransaction-param-validityEnd"><span class="type-annotation"><a href="https://api.flutter.dev/flutter/dart-core/String-class.html">String</a>?</span> <span class="parameter-name">validityEnd</span>}</span>)
<span class="returntype parameter">&#8594; <a href="https://api.flutter.dev/flutter/dart-async/Future-class.html">Future</a><span class="signature">&lt;<wbr><span class="type-parameter"><a href="../flutter_upi_india/UpiTransactionResponse-class.html">UpiTransactionResponse</a></span>&gt;</span></span>
</span>

Expand Down Expand Up @@ -290,4 +290,3 @@ <h5>flutter_upi_india library</h5>
</body>

</html>

Loading