STEP 3 Enroll payment methods

Once a user has an active wallet account and login credentials set up, the next necessary component is at least one method of payment. One of the strongest aspects of Paydiant's security is the fact that a user's payment information is not stored on the phone and is not passed to the POS during a transaction. Paydiant facilitates payment authorization through a secure, tokenized transmission directly from Paydiant's platform to the processor's server. Paydiant can interface with virtually any payment processor, so whether the tender is based on a credit card, debit card, store credit, gift card, loyalty reward, PayPal account, or other type of payment, Paydiant's mobile wallet can support it.

The Payment Account enrollment steps are:

  1. Get Issuer supported tender types
  2. Evaluate supported tender types
  3. Capture user data values
  4. Submit enrollment request

1. Get Issuer supported tender types

While Paydiant supports virtually any type of payment method, each mobile wallet issuer can specify which payment types it will support. Some issuers may only allow gift cards, while others may support major credit cards and their own private label card.

Additionally, each different instrument of payment can have a distinct set of data that is required to both authenticate it for use in the wallet and use it as the source of payment in a transaction. For example, many credit cards require the billing zip code and security code for verification and use, while a gift card may only require the card number.

Consequently, it is important to retrieve the set of supported tenders so the app can prevent users from enrolling payment methods that are prohibited and collect the right data for each form of payment the user wishes to enroll.

IMPLEMENTATION STEPS

  1. Initialize the Tender Type facade.

  2. Invoke retrieveSupportedTenderTypes.

  3. Handle the success and failure blocks.

         -(void) getAllTenderTypes {
     (1)     PDTenderTypeCoordinator *tenderTypeCoordinator = [[PDTenderTypeCoordinator alloc] init];
     (2)     [tenderTypeCoordinator retrieveSupportedTenderTypes:NO completionBlock: ^(NSArray *tenderTypes) {
     (3)         if (tenderTypes.count < 0) {
                     NSMutableDictionary *uniqueTenderTypes = [[NSMutableDictionary alloc] init];
                     for (PDTenderType *tenderType in tenderTypes)
                     {
                         // Check if tender types already exist
                         if ([uniqueTenderTypes  objectForKey:tenderType.paymentAccountType.paymentAccountTypeUri]) {
                             NSMutableArray *tenderTypesArray = uniqueTenderTypes[tenderType.paymentAccountType.paymentAccountTypeUri];
                             [tenderTypesArray addObject:tenderType];
                         }
                         else {
                             NSMutableArray *tenderTypesArray = [[NSMutableArray alloc] init];
                             [tenderTypesArray addObject:tenderType];
                             uniqueTenderTypes[tenderType.paymentAccountType.paymentAccountTypeUri] = tenderTypesArray;
                         }
                     }
                     self.uniqueTenderTypes = uniqueTenderTypes;
                     [self.tableView reloadData];
                     return;
                 }
                 else {
                     [CommonUtil displayMessageWithTitle:@"Error" 
                                 andDescription:@"Tender types are not available"];
                 }
             } failureBlock: ^(PDPaydiantError *tenderTypeError) {
                 [CommonUtil displayError:tenderTypeError];
             }];
         }
     

2. Evaluate supported tender types

The retrieval of supported tender types returns an array of tender type objects, each of which defines the set of data properties that are required or relevant for that particular tender type.

Before allowing a user to enroll a payment account of a particular type, first evaluate the attributes of that tender type in order to:

  • present only supported tenders to the user for election
  • invoke the appropriate enrollment process
  • collect the right data from the user

Enrollment Screen Flow

Once the user elects to add a payment account of a particular supported type, evaluate the attributes of the PDTenderType instance corresponding to the user's selection to determine which process to invoke. For example, if the value of the supportsMultiStepEnrollment attribute is YES, only processes initiated by startPaymentAccountEnrollment are valid. If multi-step is not supported, check to see whether provisioning is supported, etc., and proceed accordingly with either the addPaymentAccount or provisionAccount calls, as appropriate.

Note: Within each standard enrollment type, the process for any supported tender often varies slightly depending on the specific configuration of that tender and what the network processor requires. For example, PayPal requires a client token to call it's vZero SDK to facilitate user login, while some debit networks require double deposit verification. Therefore, most apps configure each tender enrollment as a separate segment to be called when the corresponding dependencies are met.

IMPLEMENTATION STEPS

  1. Initialize the Payment Instrument facade.

     PDManagePaymentInstrumentsCoordinator *pdManagePaymentInstrumentsCoordinator = [[PDManagePaymentInstrumentsCoordinator alloc] init];
     
  2. Check the PDTenderType attributes to see which enrollment is applicable.

     -(void)enrollSelectedTender:(PDTenderType *)tender {
         if (tender.supportsMultiStepEnrollment) {
             if ([tender.paymentAccountNetworkType.paymentAccountNetworkTypeUri isEqualToString:@"paydiant:payment-account-management.networktype#PayPal"]) {
                 [self addPayPal:tender];
             } else {
                 [self startMutliStepEnrollment:tender];
             }
         } else {
             if ([tender.paymentAccountNetworkType.paymentAccountNetworkTypeUri isEqualToString:@"paydiant:payment-account-management.networktype#ApplePay"]) {
                 [self addApplePay:tender];
         } else if (tender.supportsProvisioning) {
             [self provisionAccount:tender];
             } else {
                 [self addPaymentAccount:tender];
             }
         }
     return;
     }
    

3. Capture User Data Values

Once the applicable enrollment process is established, build the UI form to collect the values for required and optional fields for the tender based on the PDTenderTypeMetaData instances for the tender.

NSMutableArray *metaDataArray = [[NSMutableArray alloc] init];
    for (PDTenderTypeMetaData *tenderMetaData in tender.metaDataList) {
        if ([tenderMetaData.key isEqualToString:@"CARD_NUMBER"]) {
            // Create a PDMetaData object.
            PDMetaData *metaData = [[PDMetaData alloc] init];
            metaData.key = tenderMetaData.key;
            metaData.value = kCardNumber;
            [metaDataArray addObject:metaData];
            NSLog(@"Card numebr datatype %@", tenderMetaData.dataType);
        }
        else if ([tenderMetaData.key isEqualToString:@"EXPIRY_DATE"]) {
            // Create a PDMetaData object.
            PDMetaData *metaData = [[PDMetaData alloc] init];
            metaData.key = tenderMetaData.key;
            metaData.value = kExpiration;
            [metaDataArray addObject:metaData];
        }
        else if ([tenderMetaData.key isEqualToString:@"NICK_NAME"]) {
            // Create a PDMetaData object.
            PDMetaData *metaData = [[PDMetaData alloc] init];
            metaData.key = tenderMetaData.key;
            metaData.value = kNickname;
            [metaDataArray addObject:metaData];
        }
        else if ([tenderMetaData.key isEqualToString:@"SECURITY_CODE"]) {
            // Create a PDMetaData object.
            PDMetaData *metaData = [[PDMetaData alloc] init];
            metaData.key = tenderMetaData.key;
            metaData.value = kCVV;
            [metaDataArray addObject:metaData];
        }
        if ([tenderMetaData.dataGroup isEqualToString:@"BILLING_INFORMATION"]) {
            if ([tenderMetaData.key isEqualToString:@"ZIP_CODE"]) {
                // Create a PDMetaData object.
                PDMetaData *metaData = [[PDMetaData alloc] init];
                metaData.key = tenderMetaData.key;
                metaData.value = kBillingZip;
                [metaDataArray addObject:metaData];
            }
            else if ([tenderMetaData.key isEqualToString:@"HOLDER_NAME"]) {
                // Create a PDMetaData object.
                PDMetaData *metaData = [[PDMetaData alloc] init];
                metaData.key = tenderMetaData.key;
                metaData.value = kCardholder;
                [metaDataArray addObject:metaData];
            }
        }
        if ([tenderMetaData.dataGroup isEqualToString:@"ISSUER_PROVIDED"]) {
            if ([tenderMetaData.key isEqualToString:@"IMAGE_PATH"]) {
                // Create a PDMetaData object.
                PDMetaData *metaData = [[PDMetaData alloc] init];
                metaData.key = tenderMetaData.key;
                metaData.value = kCardLogo;
                [metaDataArray addObject:metaData];
            } else if ([tenderMetaData.key isEqualToString:@"EMAIL"]) {
                // Create a PDMetaData object.
                PDMetaData *metaData = [[PDMetaData alloc] init];
                metaData.key = tenderMetaData.key;
                metaData.value = kUSER_NAME;
                [metaDataArray addObject:metaData];
            } else if ([tenderMetaData.key isEqualToString:@"CURRENCY_CODE"]) {
                // Create a PDMetaData object.
                PDMetaData *metaData = [[PDMetaData alloc] init];
                metaData.key = tenderMetaData.key;
                metaData.value = kCurrency;
                [metaDataArray addObject:metaData];
            } else
                [metaDataArray addObject:tenderMetaData];
        }
    }

Note: Paydiant recommends implementing field-level validation in the app to ensure that the user's input complies with the data specifications for each field, such as whether a value is required and what format is expected, etc. This will ensure that the enrollment call does not fail due to invalid input and also allows the app to provide specific feedback about what is expected. If validation is not implemented and the enrollment call fails, the failure response will not indicate which field(s) contained invalid or missing values, possibly resulting in a poor user experience.

4. Submit enrollment request

Use the captured user data to populate the metadata object instance that is expected by the enrollment request and submit it with the corresponding method.

startPaymentAccountEnrollment

This call is used to initiate a payment tender enrollment process that may take one or more steps to complete. Some external authorizing entities need to evaluate the first set of passed data in order to determine whether more information is required. If so, the call returns an enrollmentState of MORE_INFO and typically includes additional data related to the requirements. Otherwise, the call returns an enrollmentState of END and the enrollment is complete.

The PayPal tender is one of the tenders that requires multiple steps, and is therefore used as the code sample for multi-step tender enrollment.

Note: The sample excerpts shown are for demonstration purposes and do not represent the entire body of code associated with PayPal tender enrollment. Refer to the PayPal Integration Solution Guide for more comprehensive information.

  1. Call the startPaymentAccountEnrollment, passing the request object, which includes the tender identification values, the enrollmentType, and an additionalInfo array of the property values collected from the user.

     -(void) addPayPal:(PDTenderType *)tender {
         PDStartPaymentAccountEnrollmentRequest *request = [[PDStartPaymentAccountEnrollmentRequest alloc] init];
         request.enrollmentType = kPDEnrollmentTypeProvision;
         request.paymentAccountTypeUri = tender.paymentAccountType.paymentAccountTypeUri;
         request.paymentAccountNetworkTypeUri = tender.paymentAccountNetworkType.paymentAccountNetworkTypeUri;
         NSMutableArray *additionalInfo = [[NSMutableArray alloc] init];
         PDStartPaymentAccountEnrollmentAdditionalData *instances =  [[PDStartPaymentAccountEnrollmentAdditionalData alloc] init];
     instances.additionalDataKey =  @"NICK_NAME";
     instances.additionalDataValue = @"PayPal";
         [additionalInfo addObject:instances];
     request.additionalData = additionalInfo;
         PDManagePaymentInstrumentsCoordinator *managePayInstr = [[PDManagePaymentInstrumentsCoordinator alloc] init];
         [CommonUtil showProgressIndicatorOnView:self];
         [managePayInstr startPaymentAccountEnrollment:request completionBlock:^(PDStartPaymentAccountEnrollmentResponse *response) {
             NSString *paydiantCorrelationId = response.paymentAccountEnrollment.paydiantCorrelationId;
             NSString *externalCorrelationId = response.paymentAccountEnrollment.externalCorrelationId;
             NSArray\* additionalData = response.paymentAccountEnrollment.additionalEnrollmentData;
             if (additionalData.count > 0) {
                 PDAdditionalEnrollmentData * pdData = additionalData [0];
                 NSString *btClientToken  = pdData.additionalEnrollmentDataValue;
                 BTAPIClient *apiClient = [[BTAPIClient alloc] initWithAuthorization:btClientToken];
                 BTPayPalDriver *btPayPalDriver = [[BTPayPalDriver alloc] initWithAPIClient:apiClient];
                 btPayPalDriver.appSwitchDelegate = (id)self;
                 btPayPalDriver.viewControllerPresentingDelegate = (id)self;
                 BTPayPalRequest *checkout = [[BTPayPalRequest alloc] init];
                 [btPayPalDriver requestBillingAgreement:checkout completion:^(BTPayPalAccountNonce * _Nullable tokenizedPayPalCheckout, NSError * _Nullable error) {
                     if (error) {
                         NSLog(@"ERROR = %@", error);
                         [CommonUtil hideProgressIndicatorOnView:self];
                         [CommonUtil displayMessageWithTitle:@"Error" 
                                     andDescription:@"Your PayPal account could not be linked at this time. Please try again."];
                 } else if (tokenizedPayPalCheckout) {
                     NSString *nonce = tokenizedPayPalCheckout.nonce;
                     NSLog(@"Got a nonce: %@", nonce);
     
  2. If the response indicates an enrollmentState of MORE_INFO, configure the app to capture any relevant data returned and use it to provide the additional data requested.

     PDContinuePaymentAccountEnrollmentRequest *continuePaymentAccountEnrollmentRequest = [[PDContinuePaymentAccountEnrollmentRequest alloc] init];
     NSMutableArray *additionalInfo = [[NSMutableArray alloc] init];
     PDAdditionalEnrollmentData *instances =  [[PDAdditionalEnrollmentData alloc] init];
             instances =  [[PDAdditionalEnrollmentData alloc] init];
             instances.additionalEnrollmentDataKey = @"PAYMENT_METHOD_NONCE";
             instances.additionalEnrollmentDataValue = nonce;
             [additionalInfo addObject:instances];
             continuePaymentAccountEnrollmentRequest.paydiantCorrelationId =  paydiantCorrelationId;
             continuePaymentAccountEnrollmentRequest.externalCorrelationId =  externalCorrelationId;
             continuePaymentAccountEnrollmentRequest.additionalEnrollmentData = additionalInfo;
     
  3. Configure the secondary response in the same way as the starting response; if the enrollmentState is END, complete the enrollment.

           continuePaymentAccountEnrollment:continuePaymentAccountEnrollmentRequest completionBlock:^(PDContinuePaymentAccountEnrollmentResponse *response) {
             if( response.paymentAccountEnrollment.enrollmentState == kPDEnrollmentStateEnd) {
                  [CommonUtil hideProgressIndicatorOnView:self];
                  [CommonUtil displayMessageWithTitle:@"Success" 
                              andDescription:@"Your PayPal Account has been added to your mobile wallet successfully."];
                  [[NSNotificationCenter defaultCenter] postNotificationName:kPaymentAccountsChangedNotification object:self];
                         }
                  } failureBlock:^(PDPaydiantError *managePaymentInstrumentsError) {
                         [CommonUtil hideProgressIndicatorOnView:self];
                         [CommonUtil displayError:managePaymentInstrumentsError];
                  }];
                  } else {
                     [CommonUtil hideProgressIndicatorOnView:self];
                     [CommonUtil displayMessageWithTitle:@"Error" 
                                 andDescription:@"PayPal account linking cancelled: your account has not been added to your wallet."];
                 }
             }];
     }
     

addPaymentAccount

Use this method to register a payment account as a valid tender in the wallet where, once verified, Paydiant becomes the system of record for the account, storing the PAN data within the secure platform Payment Account Manager database and managing payment authorization. This process is typically used for open-loop style tenders, such as credit and debit cards.

PDAddPaymentAccountRequest *addRequest = [[PDAddPaymentAccountRequest alloc] init];
    addRequest.paymentAccountTypeUri = tender.paymentAccountType.paymentAccountTypeUri;
    addRequest.paymentAccountNetworkTypeUri = tender.paymentAccountNetworkType.paymentAccountNetworkTypeUri;
    [CommonUtil showProgressIndicatorOnView:self];
    PDManagePaymentInstrumentsCoordinator *pdManagePaymentInstrumentsCoordinator = [[PDManagePaymentInstrumentsCoordinator alloc] init];
    NSMutableArray *metaDataArray = [[NSMutableArray alloc] init];
    { // Create a PDMetaData object.
    PDMetaData *metaData = [[PDMetaData alloc] init];
    metaData.key = @"CARD_NUMBER";
    metaData.value = @"1234567890";
    [metaDataArray addObject:metaData];
    }
    { // Create a PDMetaData object.
    PDMetaData *metaData = [[PDMetaData alloc] init];
    metaData.key = @"NICK_NAME";
    metaData.value = @"test card";
    [metaDataArray addObject:metaData];
    }
    addRequest.metaData = metaDataArray;
    [pdManagePaymentInstrumentsCoordinator addPaymentAccount:addRequest completionBlock: ^(PDPaymentAccount *addedPaymentAccount, PDAddPaymentAccountResponse *response) {
        [CommonUtil hideProgressIndicatorOnView:self];
        [CommonUtil displayMessageWithTitle:@"Success" 
                    andDescription:@"Apple Pay Added !"];
        [[NSNotificationCenter defaultCenter] postNotificationName:kPaymentAccountsChangedNotification object:self];
    } failureBlock: ^(PDPaydiantError *error) {
        [CommonUtil hideProgressIndicatorOnView:self];
        [CommonUtil displayError:error];
    }];
    }

provisionPaymentAccount

Use this method to create a new payment account in the wallet user's name and add it as a valid tender in the wallet. This method is most often used to issue a new loyalty card to the wallet user, which can be used as tender once enough rewards points have been earned.

Refer to the Paydiant iOS SDK Guide for documentation related to this method.

NEXT STEP 4 Configure a payment

Feedback

Have feedback?

Let us know.