Adyen / adyen-react-native

Adyen React Native
https://docs.adyen.com/checkout
MIT License
44 stars 32 forks source link

[NEED ASSIST] Customization options in Adyen3DS Screen #8

Closed ahmetgsu closed 1 year ago

ahmetgsu commented 2 years ago

In adyen-3ds2-ios and adyen-3ds2-android repositories, we can customize header text and color. Can we do that in adyen-react-native repo? If possible, could you please guide me on how to do that?

2022-04-19_10-21-47

descorp commented 2 years ago

Hey @ahmetgsu

Thanks for reaching out! It is not yet possible, unfortunately. The only way right now - fork the repo or make a PR yourself 😅

Customisation and styling are in our backlog - right now we are gathering feedback to prioritise and decide what to work next.

arnaudschlupp commented 1 year ago

Hi, regarding customization, on the Android side, adding the correct XML entries works quite fine on our side. Now regarding iOS, I wonder if we could have a similar solution by providing a class like this:

import Foundation
import IdensicMobileSDK

class IdensicMobileSDKCustomization: NSObject {

  @objc static func apply(_ sdk: SNSMobileSDK) {

    let primaryColor = UIColor(red: 229.0/255.0, green: 25.0/255.0, blue: 25.0/255.0, alpha: 1.0)
    let primaryDarkColor = UIColor(red: 0.77, green: 0.07, blue: 0.13, alpha: 1.00)
    let primaryContrastColor = UIColor.white
    let grey50Color = UIColor(white: 0.5, alpha: 1.0)
...

We use this solution for another package we are using in our app. It does the trick. Of course, a style object where you can map everything would be even handier.

descorp commented 1 year ago

Hey @arnaudschlupp

Customisation is indeed a pressing issue, since iOS does not have static overridable resources like Android does.

providing a class like this

Since we have to handle customisations per platform, it could be a feasible solution. Thanks for a very fresh idea 💚

iyanushow commented 1 year ago

Hi @descorp @arnaudschlupp, I'm also looking to style the checkout component, specifically the accent color and the pay button background color so it aligns with our app design . Is there a way we could do this?

I came across the AdyenAppearance swift class,

import Foundation
import Adyen
import adyen_react_native

/// Please make sure that the name of the class exactly matches.
/// SDK will use reflection to find the class with this exact name.
class AdyenAppearance: AdyenAppearanceProvider {
  static func createStyle() -> Adyen.DropInComponent.Style {
    return .init(tintColor: .systemTeal)
  }
}

But I haven't had success implementing it yet. Could you provide a working example of how we could style the checkout components?

descorp commented 1 year ago

Hey @iyanushow @arnaudschlupp

We have created a "workaround" to allow customisation on iOS. It is available on develop, and wasn't yet publicly released.

Some description in Customization.md

ahmetgsu commented 1 year ago

Hey @iyanushow,

As a workaround, you can also change the styling in AdyenDropIn.swift file as follows:

 var dropInComponentStyle = DropInComponent.Style()
 dropInComponentStyle.formComponent.textField.title.color = activeStateColor
 dropInComponentStyle.formComponent.textField.tintColor = activeStateColor
 dropInComponentStyle.formComponent.textField.errorColor = errorColor
 dropInComponentStyle.formComponent.mainButtonItem.button.backgroundColor = activeStateColor
 dropInComponentStyle.formComponent.mainButtonItem.button.title.color = .white

Hope it helps

descorp commented 1 year ago

Thanks @ahmetgsu

This is indeed an alternative, but it only works if one checking in Pods folder and never update Cocoapods :)

ahmetgsu commented 1 year ago

Hey @descorp, I forgot to mention that a patch file should be created after this implementation. This allows us to have the same configurations after any update.

iyanushow commented 1 year ago

dropInComponentStyle.formComponent.mainButtonItem.button.backgroundColor = activeStateColor

Thank you for this @descorp. I tried implementing this, but it the dropin component doesn't pick it up

  import Adyen
  import adyen_react_native

  class AdyenAppearance: AdyenAppearanceProvider {
    static func createStyle() -> Adyen.DropInComponent.Style {
       return .init(tintColor: .systemTeal)
    }
  }

is there any further configuration or linking I need to get this to work?. If you could provide a working example, that would really be helpful. I'm not familiar with swift so I'm not certain I have the styles written correctly

iyanushow commented 1 year ago

Hey @iyanushow,

As a workaround, you can also change the styling in AdyenDropIn.swift file as follows:

 var dropInComponentStyle = DropInComponent.Style()
 dropInComponentStyle.formComponent.textField.title.color = activeStateColor
 dropInComponentStyle.formComponent.textField.tintColor = activeStateColor
 dropInComponentStyle.formComponent.textField.errorColor = errorColor
 dropInComponentStyle.formComponent.mainButtonItem.button.backgroundColor = activeStateColor
 dropInComponentStyle.formComponent.mainButtonItem.button.title.color = .white

Hope it helps

Thank you for this also @ahmetgsu . I also tried this but then the app fails to build without any specific errors. I have it in the _ paymentMethodsDict function where the dropin component is presented

       let dropInComponentStyle = AdyenAppearanceLoader.findStyle() ?? DropInComponent.Style()
      dropInComponentStyle.formComponent.mainButtonItem.button.backgroundColor = activeStateColor
          let component = DropInComponent(paymentMethods: paymentMethods,
                                          configuration: config,
                                          style: dropInComponentStyle)
          dropInComponent = component
          dropInComponent?.delegate = self
          present(component: component)
descorp commented 1 year ago

Hey @iyanushow

And also the implementation for the simple components rather that the dropin

DropInComponent.Style used as umbrella class for styling. The style will be applied to standalone component as well.

If you could provide a working example, that would really be helpful.

It works for example app on develop.

To try it out in your app you need to: 1) clone this repo; 2) run yarn to build lib folder; 3) provide local path repo in "@adyen/react-native": "../adyen-react-native", in your project.json.

Make sure to add swift file with AdyenAppearance code in your Xcode to the project.

iyanushow commented 1 year ago

Hey @iyanushow

And also the implementation for the simple components rather that the dropin

DropInComponent.Style used as umbrella class for styling. The style will be applied to standalone component as well.

If you could provide a working example, that would really be helpful.

It works for example app on develop.

To try it out in your app you need to:

  1. clone this repo;
  2. run yarn to build lib folder;
  3. provide local path repo in "@adyen/react-native": "../adyen-react-native", in your project.json.

Make sure to add swift file with AdyenAppearance code in your Xcode to the project.

Thanks for the help @descorp It works now. We had an issue with the bundle identifier name.. which is why the swift class wasn't read correctly before.

Are there any steps to allow full styling options in the near future?, it'll be very helpful to us. Maybe overload functions on the init method in AppearanceLoader that gives us extended abilities in terms of styling.

descorp commented 1 year ago

@iyanushow

Are there any steps to allow full styling options in the near future?

You can modify every aspect of Adyen.DropInComponent.Style

what is still missing?

iyanushow commented 1 year ago

@iyanushow

Are there any steps to allow full styling options in the near future?

You can modify every aspect of Adyen.DropInComponent.Style

what is still missing?

@descorp Yes you're right, but Adyen.DropInComponent.Style only allows us specify the tint color

From what I understand in it The AdyenAppearance protocol only provides us an interface for calling the init function specified in Adyen.DropInComponent.Style

       /// Initializes the instance of DropIn style with the default values.
        public init() {}

        /// Initializes the instance of DropIn style with the default values.
        public init(tintColor: UIColor) {
            formComponent = FormComponentStyle(tintColor: tintColor)
            navigation.tintColor = tintColor
        }

These are the only overloads for the init function, and it allows us to style the FormComponent tintColor and the navigation. But it doesn't give us access to the other overloads available on FormComponent for example which would allow us have more control over the styles by specifying things like the borderRadius, placeHolder Text , custom button styles e.t.c

 public init(textField: FormTextItemStyle,
                toggle: FormToggleItemStyle,
                mainButton: FormButtonItemStyle,
                secondaryButton: FormButtonItemStyle,
                helper: TextStyle,
                sectionHeader: TextStyle) {
        self.textField = textField
        self.toggle = toggle
        self.mainButtonItem = mainButton
        self.secondaryButtonItem = secondaryButton
        self.hintLabel = helper
        self.sectionHeader = sectionHeader
    }
descorp commented 1 year ago

@iyanushow

You can modify Adyen.DropInComponent.Style whenever you like:

class AdyenAppearance: AdyenAppearanceProvider {
  static func createStyle() -> Adyen.DropInComponent.Style {
    var style = Adyen.DropInComponent.Style()
    style.formComponent.mainButtonItem.button.title.color = .white
    style.formComponent.mainButtonItem.button.backgroundColor = .black

    return style
  }
}
Screenshot 2023-06-12 at 19 21 29
iyanushow commented 1 year ago

@iyanushow

You can modify Adyen.DropInComponent.Style whenever you like:

class AdyenAppearance: AdyenAppearanceProvider {
  static func createStyle() -> Adyen.DropInComponent.Style {
    var style = Adyen.DropInComponent.Style()
    style.formComponent.mainButtonItem.button.title.color = .white
    style.formComponent.mainButtonItem.button.backgroundColor = .black

    return style
  }
}
Screenshot 2023-06-12 at 19 21 29

Nice ... this actually would work pretty well Thank you for the help @descorp and the quick responses

descorp commented 1 year ago

Thanks for confirmation @iyanushow 💚

Customisation was released on 1.0.0 I am going to close this thread, feel free to continue discussion or reopen it if necessary.

ahmetgsu commented 1 year ago

Hey @descorp

With the existing AdyenAppearanceLoader.findStyle() method, our AdyenAppearance.swift file is not hit. However, if we replace how we get the bundleName with the following one, it worked:

let bundleName = Bundle.main.infoDictionary?["CFBundleName"] as? String ?? ""

We thought that we need to use the bundle name instead of the bundle identifier.

What do you think of this?

internal class AdyenAppearanceLoader: NSObject {

    private static let expectedClassName = "AdyenAppearance"

    static func findStyle() -> Adyen.DropInComponent.Style? {
        // let bundleName = Bundle.main.bundleIdentifier?.components(separatedBy: ".").last ?? ""
        let bundleName = Bundle.main.infoDictionary?["CFBundleName"] as? String ?? ""
        guard let nsClass = NSClassFromString("\(bundleName).\(expectedClassName)"),
              let appearanceProvider = nsClass as? AdyenAppearanceProvider.Type else {
            adyenPrint("AdyenAppearance: class \("\(bundleName).\(expectedClassName)") not found or does not conform to AdyenAppearanceProvider protocol")
            return nil
        }
        return appearanceProvider.createStyle()
    }
}
descorp commented 1 year ago

Hey @ahmetgsu

CFBundleIdentifier is expected to present for a real market-ready app. CFBundleName is rather cosmetic and might not necessary represent Bundle's Identifier.

Does your Bundle.main.bundleIdentifier contains multiple comma separated sections?

ahmetgsu commented 1 year ago

Hey @descorp,

Yes, we have multiple dot separated sections in our bundleIdentifier. Do we need to take into consideration

descorp commented 1 year ago

Hey @ahmetgsu

Your original comment was correct! Sorry for confusion.

After small research I can confirm that "CFBundleName" is the right value to use! Thanks for pointing this out! 💚

descorp commented 1 year ago

After further investigation, I am inclining to use CFBundleExecutable instead. Applied in #255

Sorry @slhodson969 😅

descorp commented 1 year ago

Fixed on 1.1.0