sorokin0andrey / react-native-apay

React Native bridge for Apple Pay
55 stars 30 forks source link

Patch to add newer network types and payment capabilities #42

Open rakeshta opened 1 month ago

rakeshta commented 1 month ago

Hi! 👋

Firstly, thanks for your work on this project! 🙂

Today I used patch-package to patch react-native-apay@1.3.1 for the project I'm working on.

I've added support for all of the latest network types and payment capabilities supported by Apple Pay.

Here is the diff:

diff --git a/node_modules/react-native-apay/index.d.ts b/node_modules/react-native-apay/index.d.ts
index a49977e..d4cde67 100644
--- a/node_modules/react-native-apay/index.d.ts
+++ b/node_modules/react-native-apay/index.d.ts
@@ -1,26 +1,68 @@
-export type APayAllowedCardNetworkType = "amex" | "mastercard"| "visa" | "privatelabel" | "chinaunionpay" | "interac" | "jcb" | "suica" | "cartebancaires" | "idcredit" | "quicpay" | "maestro"
+export type APayAllowedCardNetworkType =
+  | 'amex'
+  | 'bancontact'
+  | 'barcode'
+  | 'cartesbancaires'
+  | 'chinaunionpay'
+  | 'dankort'
+  | 'discover'
+  | 'eftpos'
+  | 'electron'
+  | 'elo'
+  | 'girocard'
+  | 'idcredit'
+  | 'interac'
+  | 'jcb'
+  | 'mada'
+  | 'maestro'
+  | 'mastercard'
+  | 'mir'
+  | 'nanaco'
+  | 'postfinance'
+  | 'privatelabel'
+  | 'quicpay'
+  | 'suica'
+  | 'visa'
+  | 'vpay'
+  | 'waon';

-export type APayPaymentStatusType = number
+/** Capabilities for processing payment
+ *  @see {@link https://developer.apple.com/documentation/passkit_apple_pay_and_wallet/pkmerchantcapability} */
+export type APayMerchantCapability = 'InstantFundsOut' | '3DS' | 'EMV' | 'Credit' | 'Debit';
+
+export type APayPaymentStatusType = number;

 export interface APayPaymentSummaryItemType {
-  label: string
-  amount: string
+  label: string;
+  amount: string;
 }

 export interface APayRequestDataType {
-  merchantIdentifier: string
-  supportedNetworks: APayAllowedCardNetworkType[]
-  countryCode: string
-  currencyCode: string
-  paymentSummaryItems: APayPaymentSummaryItemType[]
+  merchantIdentifier: string;
+  /**
+   * The `3DS` and `EMV` values of {@link APayMerchantCapability} specify the supported cryptographic payment protocols. At least
+   * one of these two values is required.
+   *
+   * Check with your payment processors about the cryptographic payment protocols they support. As a general rule,
+   * if you want to support China UnionPay cards, you use `EMV`. To support cards from other networks—like
+   * American Express, Visa, or Mastercard—use `3DS`.
+   *
+   * To filter the types of cards to make available for the transaction, pass the `Credit` and `Debit` values.
+   * If neither is passed, all card types will be available.
+   */
+  merchantCapabilities: APayMerchantCapability[];
+  supportedNetworks: APayAllowedCardNetworkType[];
+  countryCode: string;
+  currencyCode: string;
+  paymentSummaryItems: APayPaymentSummaryItemType[];
 }

 declare class ApplePay {
-  static SUCCESS: APayPaymentStatusType
-  static FAILURE: APayPaymentStatusType
-  static canMakePayments: boolean
-  static requestPayment: (requestData: APayRequestDataType) => Promise<string>
-  static complete: (status: APayPaymentStatusType) => Promise<void>
+  static SUCCESS: APayPaymentStatusType;
+  static FAILURE: APayPaymentStatusType;
+  static canMakePayments: boolean;
+  static requestPayment: (requestData: APayRequestDataType) => Promise<string>;
+  static complete: (status: APayPaymentStatusType) => Promise<void>;
 }

-export { ApplePay }
\ No newline at end of file
+export { ApplePay };
diff --git a/node_modules/react-native-apay/ios/RNApplePay.m b/node_modules/react-native-apay/ios/RNApplePay.m
index c6cba6c..a1d1633 100644
--- a/node_modules/react-native-apay/ios/RNApplePay.m
+++ b/node_modules/react-native-apay/ios/RNApplePay.m
@@ -2,7 +2,9 @@
 #import "RNApplePay.h"
 #import <React/RCTUtils.h>

-@implementation RNApplePay
+@implementation RNApplePay {
+    NSDictionary *_supportedNetworksMapping;
+}

 - (dispatch_queue_t)methodQueue
 {
@@ -27,7 +29,7 @@ - (NSDictionary *)constantsToExport

 RCT_EXPORT_METHOD(requestPayment:(NSDictionary *)props promiseWithResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
     PKPaymentRequest *paymentRequest = [[PKPaymentRequest alloc] init];
-    paymentRequest.merchantCapabilities = PKMerchantCapability3DS;
+    paymentRequest.merchantCapabilities = [self getMerchantCapabilities:props];
     paymentRequest.merchantIdentifier = props[@"merchantIdentifier"];
     paymentRequest.countryCode = props[@"countryCode"];
     paymentRequest.currencyCode = props[@"currencyCode"];
@@ -55,44 +57,88 @@ - (NSDictionary *)constantsToExport
     }
 }

-- (NSArray *_Nonnull)getSupportedNetworks:(NSDictionary *_Nonnull)props
-{
-    NSMutableDictionary *supportedNetworksMapping = [[NSMutableDictionary alloc] init];
+- (NSDictionary *_Nonnull)supportedNetworksMapping {
+    if (_supportedNetworksMapping == nil) {
+        NSMutableDictionary *mapping = [[NSMutableDictionary alloc] init];

-    if (@available(iOS 8, *)) {
-        [supportedNetworksMapping setObject:PKPaymentNetworkAmex forKey:@"amex"];
-        [supportedNetworksMapping setObject:PKPaymentNetworkMasterCard forKey:@"mastercard"];
-        [supportedNetworksMapping setObject:PKPaymentNetworkVisa forKey:@"visa"];
-    }
+        if (@available(iOS 8, *)) {
+            mapping[@"amex"] = PKPaymentNetworkAmex;
+            mapping[@"mastercard"] = PKPaymentNetworkMasterCard;
+            mapping[@"visa"] = PKPaymentNetworkVisa;
+        }

-    if (@available(iOS 9, *)) {
-        [supportedNetworksMapping setObject:PKPaymentNetworkDiscover forKey:@"discover"];
-        [supportedNetworksMapping setObject:PKPaymentNetworkPrivateLabel forKey:@"privatelabel"];
-    }
+        if (@available(iOS 9, *)) {
+            mapping[@"discover"] = PKPaymentNetworkDiscover;
+            mapping[@"privatelabel"] = PKPaymentNetworkPrivateLabel;
+        }

-    if (@available(iOS 9.2, *)) {
-        [supportedNetworksMapping setObject:PKPaymentNetworkChinaUnionPay forKey:@"chinaunionpay"];
-        [supportedNetworksMapping setObject:PKPaymentNetworkInterac forKey:@"interac"];
-    }
+        if (@available(iOS 9.2, *)) {
+            mapping[@"chinaunionpay"] = PKPaymentNetworkChinaUnionPay;
+            mapping[@"interac"] = PKPaymentNetworkInterac;
+        }

-    if (@available(iOS 10.1, *)) {
-        [supportedNetworksMapping setObject:PKPaymentNetworkJCB forKey:@"jcb"];
-        [supportedNetworksMapping setObject:PKPaymentNetworkSuica forKey:@"suica"];
-    }
+        if (@available(iOS 10.1, *)) {
+            mapping[@"jcb"] = PKPaymentNetworkJCB;
+            mapping[@"suica"] = PKPaymentNetworkSuica;
+        }

-    if (@available(iOS 10.3, *)) {
-        [supportedNetworksMapping setObject:PKPaymentNetworkCarteBancaire forKey:@"cartebancaires"];
-        [supportedNetworksMapping setObject:PKPaymentNetworkIDCredit forKey:@"idcredit"];
-        [supportedNetworksMapping setObject:PKPaymentNetworkQuicPay forKey:@"quicpay"];
-    }
+        if (@available(iOS 10.3, *)) {
+            mapping[@"idcredit"] = PKPaymentNetworkIDCredit;
+            mapping[@"quicpay"] = PKPaymentNetworkQuicPay;
+        }

-    if (@available(iOS 11.0, *)) {
-        [supportedNetworksMapping setObject:PKPaymentNetworkCarteBancaires forKey:@"cartebancaires"];
-    }
+        if (@available(iOS 11.2, *)) {
+            // PKPaymentNetworkCarteBancaire was deprecated & replaced with PKPaymentNetworkCartesBancaires
+            mapping[@"cartebancaires"]  = PKPaymentNetworkCartesBancaires;
+            mapping[@"cartesbancaires"] = PKPaymentNetworkCartesBancaires;
+        }
+
+        if (@available(iOS 12.0, *)) {
+            mapping[@"eftpos"] = PKPaymentNetworkEftpos;
+            mapping[@"electron"] = PKPaymentNetworkElectron;
+            mapping[@"maestro"] = PKPaymentNetworkMaestro;
+            mapping[@"vpay"] = PKPaymentNetworkVPay;
+        }
+
+        if (@available(iOS 12.1.1, *)) {
+            mapping[@"elo"] = PKPaymentNetworkElo;
+            mapping[@"mada"] = PKPaymentNetworkMada;
+        }
+
+        if (@available(iOS 14.0, *)) {
+            mapping[@"barcode"] = PKPaymentNetworkBarcode;
+            mapping[@"girocard"] = PKPaymentNetworkGirocard;
+        }
+
+        if (@available(iOS 14.5, *)) {
+            mapping[@"mir"] = PKPaymentNetworkMir;
+        }

-    if (@available(iOS 12.0, *)) {
-        [supportedNetworksMapping setObject:PKPaymentNetworkMaestro forKey:@"maestro"];
+        if (@available(iOS 15.0, *)) {
+            mapping[@"nanaco"] = PKPaymentNetworkNanaco;
+            mapping[@"waon"] = PKPaymentNetworkWaon;
+        }
+
+        if (@available(iOS 15.1, *)) {
+            mapping[@"dankort"] = PKPaymentNetworkDankort;
+        }
+
+        if (@available(iOS 16.0, *)) {
+            mapping[@"bancontact"] = PKPaymentNetworkBancontact;
+        }
+
+        if (@available(iOS 16.4, *)) {
+            mapping[@"postfinance"] = PKPaymentNetworkPostFinance;
+        }
+
+        _supportedNetworksMapping = mapping;
     }
+    return _supportedNetworksMapping;
+}
+
+- (NSArray *_Nonnull)getSupportedNetworks:(NSDictionary *_Nonnull)props
+{
+    NSDictionary *supportedNetworksMapping = [self supportedNetworksMapping];

     NSArray *supportedNetworksProp = props[@"supportedNetworks"];
     NSMutableArray *supportedNetworks = [NSMutableArray array];
@@ -103,6 +149,32 @@ - (NSArray *_Nonnull)getSupportedNetworks:(NSDictionary *_Nonnull)props
     return supportedNetworks;
 }

+- (PKMerchantCapability)getMerchantCapabilities:(NSDictionary *_Nonnull)props {
+    NSArray<NSString *> *merchantCapabilities = props[@"merchantCapabilities"];
+
+    PKMerchantCapability mask = 0;
+
+    if (@available(iOS 17.0, *)) {
+        if ([merchantCapabilities containsObject:@"InstantFundsOut"]) {
+            mask |= PKMerchantCapabilityInstantFundsOut;
+        }
+    }
+    if ([merchantCapabilities containsObject:@"3DS"]) {
+        mask |= PKMerchantCapability3DS;
+    }
+    if ([merchantCapabilities containsObject:@"EMV"]) {
+        mask |= PKMerchantCapabilityEMV;
+    }
+    if ([merchantCapabilities containsObject:@"Credit"]) {
+        mask |= PKMerchantCapabilityCredit;
+    }
+    if ([merchantCapabilities containsObject:@"Debit"]) {
+        mask |= PKMerchantCapabilityDebit;
+    }
+
+    return  mask;
+}
+
 - (NSArray<PKPaymentSummaryItem *> *_Nonnull)getPaymentSummaryItems:(NSDictionary *_Nonnull)props
 {
     NSMutableArray <PKPaymentSummaryItem *> * paymentSummaryItems = [NSMutableArray array];
rakeshta commented 1 month ago

Just in case, I've attached the patch generated by patch-package in this comment. I hope you can use this to update your package. react-native-apay+1.3.1.patch